8296907: VMError: add optional callstacks, siginfo for secondary errors

Reviewed-by: aboldtch, rschmelter
This commit is contained in:
Thomas Stuefe 2022-12-05 07:10:31 +00:00
parent a57392390b
commit 3b3bbe5487
5 changed files with 102 additions and 34 deletions

View File

@ -517,6 +517,9 @@ const int ObjectAlignmentInBytes = 8;
"error log in case of a crash.") \
range(0, (uint64_t)max_jlong/1000) \
\
product(bool, ErrorLogSecondaryErrorDetails, false, DIAGNOSTIC, \
"If enabled, show details on secondary crashes in the error log") \
\
develop(intx, TraceDwarfLevel, 0, \
"Debug levels for the dwarf parser") \
range(0, 4) \

View File

@ -745,7 +745,7 @@ extern "C" JNIEXPORT void pns(void* sp, void* fp, void* pc) { // print native st
Thread* t = Thread::current_or_null();
// Call generic frame constructor (certain arguments may be ignored)
frame fr(sp, fp, pc);
VMError::print_native_stack(tty, fr, t, false, buf, sizeof(buf));
VMError::print_native_stack(tty, fr, t, false, -1, buf, sizeof(buf));
}
//
@ -765,7 +765,7 @@ extern "C" JNIEXPORT void pns2() { // print native stack
} else {
Thread* t = Thread::current_or_null();
frame fr = os::current_frame();
VMError::print_native_stack(tty, fr, t, false, buf, sizeof(buf));
VMError::print_native_stack(tty, fr, t, false, -1, buf, sizeof(buf));
}
}
#endif

View File

@ -347,14 +347,14 @@ static frame next_frame(frame fr, Thread* t) {
}
}
void VMError::print_native_stack(outputStream* st, frame fr, Thread* t, bool print_source_info, char* buf, int buf_size) {
void VMError::print_native_stack(outputStream* st, frame fr, Thread* t, bool print_source_info, int max_frames, char* buf, int buf_size) {
// see if it's a valid frame
if (fr.pc()) {
st->print_cr("Native frames: (J=compiled Java code, j=interpreted, Vv=VM code, C=native code)");
const int limit = max_frames == -1 ? StackPrintLimit : MIN2(max_frames, (int)StackPrintLimit);
int count = 0;
while (count++ < StackPrintLimit) {
while (count++ < limit) {
fr.print_on_error(st, buf, buf_size);
if (fr.pc()) { // print source file and line, if available
char filename[128];
@ -374,11 +374,10 @@ void VMError::print_native_stack(outputStream* st, frame fr, Thread* t, bool pri
}
}
if (count > StackPrintLimit) {
if (count > limit) {
st->print_cr("...<more frames>...");
}
st->cr();
}
}
@ -818,7 +817,7 @@ void VMError::report(outputStream* st, bool _verbose) {
frame fr = _context ? os::fetch_frame_from_context(_context)
: os::current_frame();
print_native_stack(st, fr, _thread, true, buf, sizeof(buf));
print_native_stack(st, fr, _thread, true, -1, buf, sizeof(buf));
_print_native_stack_used = true;
}
print_native_stack_succeeded = true;
@ -827,7 +826,7 @@ void VMError::report(outputStream* st, bool _verbose) {
st->cr();
st->print_cr("Retrying call stack printing without source information...");
frame fr = _context ? os::fetch_frame_from_context(_context) : os::current_frame();
print_native_stack(st, fr, _thread, false, buf, sizeof(buf));
print_native_stack(st, fr, _thread, false, -1, buf, sizeof(buf));
_print_native_stack_used = true;
STEP_IF("printing Java stack", _verbose && _thread && _thread->is_Java_thread())
@ -1501,27 +1500,49 @@ void VMError::report_and_die(int id, const char* message, const char* detail_fmt
// Watcherthread is about to call os::die. Lets just wait.
os::infinite_sleep();
} else {
// Crash or assert during error reporting. Lets continue reporting with the next step.
stringStream ss(buffer, sizeof(buffer));
// A secondary error happened. Print brief information, but take care, since crashing
// here would just recurse endlessly.
// Any information (signal, context, siginfo etc) printed here should use the function
// arguments, not the information stored in *this, since those describe the primary crash.
static char tmp[256]; // cannot use global scratch buffer
// Note: this string does get parsed by a number of jtreg tests,
// see hotspot/jtreg/runtime/ErrorHandling.
ss.print("[error occurred during error reporting (%s), id 0x%x",
st->print("[error occurred during error reporting (%s), id 0x%x",
_current_step_info, id);
char signal_name[64];
if (os::exception_name(id, signal_name, sizeof(signal_name))) {
ss.print(", %s (0x%x) at pc=" PTR_FORMAT, signal_name, id, p2i(pc));
if (os::exception_name(id, tmp, sizeof(tmp))) {
st->print(", %s (0x%x) at pc=" PTR_FORMAT, tmp, id, p2i(pc));
} else {
if (should_report_bug(id)) {
ss.print(", Internal Error (%s:%d)",
st->print(", Internal Error (%s:%d)",
filename == NULL ? "??" : filename, lineno);
} else {
ss.print(", Out of Memory Error (%s:%d)",
st->print(", Out of Memory Error (%s:%d)",
filename == NULL ? "??" : filename, lineno);
}
}
ss.print("]");
st->print_raw_cr(buffer);
st->cr();
st->print_cr("]");
if (ErrorLogSecondaryErrorDetails) {
static bool recursed = false;
if (!recursed) {
recursed = true;
// Print even more information for secondary errors. This may generate a lot of output
// and possibly disturb error reporting, therefore its optional and only available in debug builds.
if (siginfo != nullptr) {
st->print("[");
os::print_siginfo(st, siginfo);
st->print_cr("]");
}
st->print("[stack: ");
frame fr = context ? os::fetch_frame_from_context(context) : os::current_frame();
// Subsequent secondary errors build up stack; to avoid flooding the hs-err file with irrelevant
// call stacks, limit the stack we print here (we are only interested in what happened before the
// last assert/fault).
const int max_stack_size = 15;
print_native_stack(st, fr, _thread, true, max_stack_size, tmp, sizeof(tmp));
st->print_cr("]");
} // !recursed
recursed = false; // Note: reset outside !recursed
}
}
}
}

View File

@ -105,8 +105,11 @@ class VMError : public AllStatic {
// public for use by the internal non-product debugger.
NOT_PRODUCT(public:)
// print_source_info: if true, we try to resolve the source information on platforms that support it
// (useful but may slow down, timeout or misfunction in error situations)
// max_frames: if not -1, overrides StackPrintLimit
static void print_native_stack(outputStream* st, frame fr, Thread* t, bool print_source_info,
char* buf, int buf_size);
int max_frames, char* buf, int buf_size);
NOT_PRODUCT(private:)
static const char* get_filename_only() {

View File

@ -25,21 +25,28 @@
/*
* @test
* @bug 8065895
* @summary Synchronous signals during error reporting may terminate or hang VM process
* @summary Check secondary error handling
* @library /test/lib
* @requires vm.debug
* @requires os.family != "windows"
* @author Thomas Stuefe (SAP)
* @modules java.base/jdk.internal.misc
* java.management
* @run driver SecondaryErrorTest
* @run driver SecondaryErrorTest no_callstacks
*/
/*
* @test
* @summary Check secondary error handling
* @library /test/lib
* @requires vm.debug
* @requires os.family != "windows"
* @modules java.base/jdk.internal.misc
* java.management
* @run driver SecondaryErrorTest with_callstacks
*/
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.regex.Pattern;
import jdk.test.lib.process.OutputAnalyzer;
@ -49,12 +56,35 @@ public class SecondaryErrorTest {
public static void main(String[] args) throws Exception {
boolean with_callstacks = false;
if (args.length != 1) {
throw new IllegalArgumentException("Missing argument");
} else if (args[0].equals("with_callstacks")) {
with_callstacks = true;
} else if (args[0].equals("no_callstacks")) {
with_callstacks = false;
} else {
throw new IllegalArgumentException("unknown argument (" + args[0] + ")");
}
// How this works:
// The test will fault with SIGFPE (ErrorHandlerTest=15) and then, during error handling,
// fault twice with SIGSEGV (TestCrashInErrorHandler=14). The point is not only to test
// secondary crashes, but secondary crashes with a *different* error signal. This should
// be handled correctly and not hang/end the process (so the signal mask must be set correctly).
// See JDK-8065895.
// We do this twice, to check that secondary signal handling works repeatedly.
// We also check, optionally, that +ErrorLogSecondaryErrorDetails produces callstacks for
// the secondary error.
ProcessBuilder pb = ProcessTools.createJavaProcessBuilder(
"-XX:+UnlockDiagnosticVMOptions",
"-Xmx100M",
"-XX:-CreateCoredumpOnCrash",
"-XX:ErrorHandlerTest=15",
"-XX:TestCrashInErrorHandler=14",
"-XX:" + (with_callstacks ? "+" : "-") + "ErrorLogSecondaryErrorDetails",
"-version");
OutputAnalyzer output_detail = new OutputAnalyzer(pb.start());
@ -72,12 +102,23 @@ public class SecondaryErrorTest {
// which is an end marker written in the last step and proves that hs-err file was
// completely written.
Pattern [] pattern = new Pattern[] {
Pattern.compile("Will crash now \\(TestCrashInErrorHandler=14\\)..."),
Pattern.compile("\\[error occurred during error reporting \\(test secondary crash 1\\).*\\]"),
Pattern.compile("Will crash now \\(TestCrashInErrorHandler=14\\)..."),
Pattern.compile("\\[error occurred during error reporting \\(test secondary crash 2\\).*\\]"),
};
ArrayList<Pattern> patternlist = new ArrayList<>();
patternlist.add(Pattern.compile("Will crash now \\(TestCrashInErrorHandler=14\\)..."));
patternlist.add(Pattern.compile("\\[error occurred during error reporting \\(test secondary crash 1\\).*\\]"));
if (with_callstacks) {
patternlist.add(Pattern.compile("\\[siginfo:.*\\(SIGSEGV\\).*\\]"));
patternlist.add(Pattern.compile("\\[stack: Native frames:.*"));
patternlist.add(Pattern.compile(".*VMError::controlled_crash.*"));
}
// and again, to see that repeated error reporting steps work
patternlist.add(Pattern.compile("Will crash now \\(TestCrashInErrorHandler=14\\)..."));
patternlist.add(Pattern.compile("\\[error occurred during error reporting \\(test secondary crash 2\\).*\\]"));
if (with_callstacks) {
patternlist.add(Pattern.compile("\\[siginfo:.*\\(SIGSEGV\\).*\\]"));
patternlist.add(Pattern.compile("\\[stack: Native frames:.*"));
patternlist.add(Pattern.compile(".*VMError::controlled_crash.*"));
}
Pattern[] pattern = patternlist.toArray(new Pattern[] {});
HsErrFileUtils.checkHsErrFileContent(hs_err_file, pattern, false);