125f743223
Reviewed-by: dholmes, lmesnik
281 lines
13 KiB
Java
281 lines
13 KiB
Java
/*
|
|
* Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
/*
|
|
* @test
|
|
* @bug 8242181
|
|
* @library / /test/lib
|
|
* @summary Test DWARF parser with various crashes if debug symbols are available. If the libjvm debug symbols are not
|
|
* in the same directory as the libjvm.so file, in a subdirectory called .debug, or in the path specified
|
|
* by the environment variable _JVM_DWARF_PATH, then no verification of the hs_err_file is done for libjvm.so.
|
|
* @requires vm.debug == true & vm.flagless & vm.compMode != "Xint" & os.family == "linux" & !vm.graal.enabled & vm.gc.G1
|
|
* @modules java.base/jdk.internal.misc
|
|
* @run main/native/othervm -Xbootclasspath/a:. -XX:-CreateCoredumpOnCrash TestDwarf
|
|
*/
|
|
|
|
import jdk.test.lib.Asserts;
|
|
import jdk.test.lib.Platform;
|
|
import jdk.test.lib.process.OutputAnalyzer;
|
|
import jdk.test.lib.process.ProcessTools;
|
|
|
|
import sun.misc.Unsafe;
|
|
|
|
import java.io.BufferedReader;
|
|
import java.io.File;
|
|
import java.io.FileReader;
|
|
import java.lang.reflect.Field;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
|
|
public class TestDwarf {
|
|
static {
|
|
System.loadLibrary("TestDwarf");
|
|
}
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
if (args.length != 0) {
|
|
switch (args[0]) {
|
|
case "unsafeAccess" -> {
|
|
crashUnsafeAccess();
|
|
Asserts.fail("Should crash in crashUnsafeAccess()");
|
|
}
|
|
case "outOfMemory" -> {
|
|
crashOutOfMemory();
|
|
Asserts.fail("Should crash in crashOutOfMemory()");
|
|
}
|
|
case "abortVMOnException" -> {
|
|
crashAbortVmOnException();
|
|
Asserts.fail("Should crash in crashAbortVmOnException()");
|
|
}
|
|
case "nativeDivByZero" -> {
|
|
crashNativeDivByZero();
|
|
Asserts.fail("Should crash in crashNativeDivByZero()");
|
|
}
|
|
case "nativeMultipleMethods" -> {
|
|
crashNativeMultipleMethods(1);
|
|
crashNativeMultipleMethods(2);
|
|
crashNativeMultipleMethods(3);
|
|
Asserts.fail("Should crash in crashNativeMultipleMethods()");
|
|
crashNativeMultipleMethods(4);
|
|
}
|
|
case "nativeDereferenceNull" -> {
|
|
crashNativeDereferenceNull();
|
|
Asserts.fail("Should crash in crashNativeDereferenceNull()");
|
|
}
|
|
}
|
|
} else {
|
|
try {
|
|
test();
|
|
} catch (UnsupportedDwarfVersionException e) {
|
|
System.out.println("Skip test due to a DWARF section that is in an unsupported version by the parser.");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Crash the VM in different ways in order to verify that DWARF parsing is able to print the source information
|
|
// in the hs_err_files for each VM and C stack frame.
|
|
private static void test() throws Exception {
|
|
runAndCheck(new Flags("-Xcomp", "-XX:+CICountNative", "-XX:CICrashAt=1", "--version"));
|
|
runAndCheck(new Flags("-Xmx100M", "-XX:ErrorHandlerTest=15", "-XX:TestCrashInErrorHandler=14", "--version"));
|
|
runAndCheck(new Flags("-XX:+CrashGCForDumpingJavaThread", "--version"));
|
|
runAndCheck(new Flags("-Xmx10m", "-XX:+CrashOnOutOfMemoryError", TestDwarf.class.getCanonicalName(), "outOfMemory"));
|
|
runAndCheck(new Flags(TestDwarf.class.getCanonicalName(), "unsafeAccess"));
|
|
runAndCheck(new Flags("-XX:+UnlockDiagnosticVMOptions", "-XX:AbortVMOnException=MyException",
|
|
TestDwarf.class.getCanonicalName(), "abortVMOnException"));
|
|
if (Platform.isX64() || Platform.isX86()) {
|
|
// Not all platforms raise SIGFPE but x86_32 and x86_64 do.
|
|
runAndCheck(new Flags(TestDwarf.class.getCanonicalName(), "nativeDivByZero"),
|
|
new DwarfConstraint(0, "Java_TestDwarf_crashNativeDivByZero", "libTestDwarf.c", 59));
|
|
runAndCheck(new Flags(TestDwarf.class.getCanonicalName(), "nativeMultipleMethods"),
|
|
new DwarfConstraint(0, "foo", "libTestDwarf.c", 42),
|
|
new DwarfConstraint(1, "Java_TestDwarf_crashNativeMultipleMethods", "libTestDwarf.c", 70));
|
|
}
|
|
runAndCheck(new Flags(TestDwarf.class.getCanonicalName(), "nativeDereferenceNull"),
|
|
new DwarfConstraint(0, "dereference_null", "libTestDwarfHelper.h", 46));
|
|
}
|
|
|
|
// A full pattern could check for lines like:
|
|
// V [libjvm.so+0x8f4ed8] report_fatal(VMErrorType, char const*, int, char const*, ...)+0x78 (debug.cpp:212)
|
|
// but the decoder is not reliably working at the moment (see JDK-8305489). We therefore use a pattern that only
|
|
// checks that lines have the following structure with source information:
|
|
// V [libjvm.so+0x8f4ed8] (debug.cpp:212)
|
|
private static final String NO_DECODER_PATTERN ="[CV][\\s\\t]+\\[([a-zA-Z0-9_.]+)\\+0x.+].*\\([a-zA-Z0-9_.]+\\.[a-z]+:[1-9][0-9]*\\)";
|
|
|
|
private static void runAndCheck(Flags flags, DwarfConstraint... constraints) throws Exception {
|
|
OutputAnalyzer crashOut;
|
|
crashOut = ProcessTools.executeProcess(ProcessTools.createTestJavaProcessBuilder(flags.getFlags()));
|
|
String crashOutputString = crashOut.getOutput();
|
|
Asserts.assertNotEquals(crashOut.getExitValue(), 0, "Crash JVM should not exit gracefully");
|
|
System.out.println(crashOutputString);
|
|
|
|
File hs_err_file = HsErrFileUtils.openHsErrFileFromOutput(crashOut);
|
|
try (FileReader fr = new FileReader(hs_err_file);
|
|
BufferedReader reader = new BufferedReader(fr)) {
|
|
String line;
|
|
boolean foundNativeFrames = false;
|
|
int matches = 0;
|
|
int frameIdx = 0;
|
|
|
|
Pattern pattern = Pattern.compile(NO_DECODER_PATTERN);
|
|
|
|
// Check all stack entries after the line starting with "Native frames" in the hs_err_file until an empty line
|
|
// is found which denotes the end of the stack frames.
|
|
while ((line = reader.readLine()) != null) {
|
|
if (foundNativeFrames) {
|
|
if (line.isEmpty()) {
|
|
// Done with the entire stack.
|
|
break;
|
|
} else if ((line.startsWith("C") || line.startsWith("V"))) {
|
|
// Could be VM or native C frame. There are usually no symbols available for libpthread.so.
|
|
matches++;
|
|
// File and library names are non-empty and may contain English letters, underscores, dots or numbers ([a-zA-Z0-9_.]+).
|
|
// Line numbers have at least one digit and start with non-zero ([1-9][0-9]*).
|
|
Matcher matcher = pattern.matcher(line);
|
|
if (!matcher.find()) {
|
|
checkMissingElement(crashOutputString, line);
|
|
}
|
|
|
|
// Check additional DWARF constraints
|
|
if (constraints != null) {
|
|
int finalFrameIdx = frameIdx;
|
|
String finalLine = line;
|
|
Arrays.stream(constraints).forEach(c -> c.checkConstraint(finalFrameIdx, finalLine));
|
|
}
|
|
}
|
|
frameIdx++;
|
|
} else if (line.startsWith("Native frames")) {
|
|
// Stack starts after this line.
|
|
foundNativeFrames = true;
|
|
}
|
|
}
|
|
Asserts.assertGreaterThan(matches, 0, "Could not find any stack frames");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* After we failed to match the pattern, try to determine what element was missing.
|
|
* There are some valid cases where we cannot find source information.
|
|
*/
|
|
private static void checkMissingElement(String crashOutputString, String line) {
|
|
// First check if we got the library name.
|
|
Pattern pattern = Pattern.compile("[CV][\\s\\t]+\\[([a-zA-Z0-9_.-]+)\\+0x.+]");
|
|
Matcher matcher = pattern.matcher(line);
|
|
Asserts.assertTrue(matcher.find(), "Must find library name in \"" + line + "\"");
|
|
|
|
// Check if there are symbols available for library. If not, then we cannot find any source information for this library.
|
|
// This can happen if this test is run without any JDK debug symbols at all but also for some libraries like libpthread.so
|
|
// which usually has no symbols available.
|
|
String library = matcher.group(1);
|
|
pattern = Pattern.compile("Failed to load DWARF file for library.*" + library + ".*or find DWARF sections directly inside it");
|
|
matcher = pattern.matcher(crashOutputString);
|
|
if (!matcher.find()) {
|
|
bailoutIfUnsupportedDwarfVersion(crashOutputString);
|
|
Asserts.fail("Could not find filename or line number in \"" + line + "\"");
|
|
}
|
|
// We should always find symbols for libTestDwarf.so.
|
|
Asserts.assertFalse(library.equals("libTestDwarf.so"), "Could not find filename or line number in \"" + line + "\" for libTestDwarf.so");
|
|
System.out.println("Did not find symbols for " + library + ". If they are not in the same directory as " + library + " consider setting " +
|
|
"the environmental variable _JVM_DWARF_PATH to point to the debug symbols directory.");
|
|
}
|
|
|
|
/**
|
|
* Some older GCC versions might emit DWARF sections in an old format that is not supported by the DWARF parser.
|
|
* If this is the case, skip this entire test by throwing UnsupportedDwarfVersionException.
|
|
*/
|
|
private static void bailoutIfUnsupportedDwarfVersion(String crashOutputString) {
|
|
Pattern pattern = Pattern.compile(".debug_\\S+ in unsupported DWARF version \\d+");
|
|
Matcher matcher = pattern.matcher(crashOutputString);
|
|
if (matcher.find()) {
|
|
throw new UnsupportedDwarfVersionException();
|
|
}
|
|
}
|
|
|
|
// Crash with SIGSEGV.
|
|
private static void crashUnsafeAccess() throws Exception {
|
|
Field f = Unsafe.class.getDeclaredField("theUnsafe");
|
|
f.setAccessible(true);
|
|
Unsafe unsafe = (Unsafe)f.get(null);
|
|
unsafe.putAddress(0, 0); // Crash
|
|
}
|
|
|
|
// Crash with Internal Error: Java heap space.
|
|
private static void crashOutOfMemory() {
|
|
Object[] o = null;
|
|
|
|
// Loop endlessly and consume memory until we run out. Will crash due to -XX:+CrashOnOutOfMemoryError.
|
|
while (true) {
|
|
o = new Object[] {o};
|
|
}
|
|
}
|
|
|
|
// Crash with Internal Error: Saw java.lang.RuntimeException, aborting.
|
|
// Crash happens due to an exception raised in combination with -XX:AbortVMOnException.
|
|
private static void crashAbortVmOnException() {
|
|
throw new MyException();
|
|
}
|
|
|
|
private static native void crashNativeDivByZero();
|
|
private static native void crashNativeDereferenceNull();
|
|
private static native void crashNativeMultipleMethods(int x);
|
|
}
|
|
|
|
class UnsupportedDwarfVersionException extends RuntimeException { }
|
|
|
|
class MyException extends RuntimeException { }
|
|
|
|
class Flags {
|
|
private final List<String> listOfOptions = new ArrayList<>();
|
|
|
|
Flags(String... flags) {
|
|
listOfOptions.add("-XX:TraceDwarfLevel=2"); // Always add debug flag
|
|
listOfOptions.add("-XX:-CreateCoredumpOnCrash"); // Never create dumps
|
|
listOfOptions.addAll(Arrays.asList(flags));
|
|
}
|
|
|
|
public List<String> getFlags() {
|
|
return listOfOptions;
|
|
}
|
|
|
|
}
|
|
class DwarfConstraint {
|
|
private final int frameIdx;
|
|
private final String methodName;
|
|
private final String dwarfInfo;
|
|
|
|
DwarfConstraint(int frameIdx, String methodName, String fileName, int lineNo) {
|
|
this.frameIdx = frameIdx;
|
|
this.methodName = methodName;
|
|
this.dwarfInfo = "(" + fileName + ":" + lineNo + ")";
|
|
}
|
|
|
|
public void checkConstraint(int currentFrameIdx, String line) {
|
|
if (frameIdx == currentFrameIdx) {
|
|
Asserts.assertTrue(line.contains(methodName), "Could not find method name " + methodName + " in \"" + line + "\"");
|
|
Asserts.assertTrue(line.contains(dwarfInfo) , "Could not find DWARF info " + dwarfInfo + " in \"" + line + "\"");
|
|
}
|
|
}
|
|
}
|