From 5b605857763d8d9415b35825bb1496e54b18993b Mon Sep 17 00:00:00 2001 From: Dominik Mascherbauer Date: Wed, 6 Aug 2025 15:11:08 +0200 Subject: [PATCH 1/5] Properly handle static fields and compiled methods from the base layer. Fix crash in CStructTests. Make sure params are not optimized out in PrettyPrinterTest. Only emit register locations for leaf ranges. --- .../objectfile/debugentry/MethodEntry.java | 8 +++- .../elf/dwarf/DwarfInfoSectionImpl.java | 6 +-- .../elf/dwarf/DwarfLineSectionImpl.java | 5 +++ .../core/debug/SharedDebugInfoProvider.java | 42 ++++++++++++++----- .../image/NativeImageDebugInfoProvider.java | 16 ++++++- .../oracle/svm/test/debug/CStructTests.java | 21 +++++++--- .../test/debug/helper/PrettyPrinterTest.java | 23 +++++----- .../helper/RuntimeCompileDebugInfoTest.java | 3 -- 8 files changed, 89 insertions(+), 35 deletions(-) diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MethodEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MethodEntry.java index 9f09a30dbe21..0bee08bd6387 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MethodEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MethodEntry.java @@ -48,18 +48,20 @@ public record Local(LocalEntry localEntry, int line) { private boolean isInlined; private final boolean isOverride; private final boolean isConstructor; + private final boolean isCompiledInPriorLayer; private final int vtableOffset; private final String symbolName; public MethodEntry(FileEntry fileEntry, int line, String methodName, StructureTypeEntry ownerType, TypeEntry valueType, int modifiers, List params, String symbolName, boolean isDeopt, boolean isOverride, boolean isConstructor, - int vtableOffset, int lastParamSlot) { + boolean isCompiledInPriorLayer, int vtableOffset, int lastParamSlot) { super(fileEntry, line, methodName, ownerType, valueType, modifiers); this.params = params; this.symbolName = symbolName; this.isDeopt = isDeopt; this.isOverride = isOverride; this.isConstructor = isConstructor; + this.isCompiledInPriorLayer = isCompiledInPriorLayer; this.vtableOffset = vtableOffset; this.lastParamSlot = lastParamSlot; this.locals = new ArrayList<>(); @@ -208,6 +210,10 @@ public boolean isConstructor() { return isConstructor; } + public boolean isCompiledInPriorLayer() { + return isCompiledInPriorLayer; + } + public boolean isVirtual() { return vtableOffset >= 0; } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfInfoSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfInfoSectionImpl.java index f917e6a586e1..e6b4f0f37960 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfInfoSectionImpl.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfInfoSectionImpl.java @@ -605,7 +605,7 @@ private int writeInstanceClasses(DebugContext context, byte[] buffer, int p) { * declaration in the owner type CU. Other information is already written to the * corresponding type units. */ - if (classEntry.getMethods().stream().anyMatch(m -> m.isInRange() || m.isInlined()) || + if (classEntry.getMethods().stream().anyMatch(m -> m.isInRange() || m.isInlined() || m.isCompiledInPriorLayer()) || classEntry.getFields().stream().anyMatch(DwarfInfoSectionImpl::isManifestedStaticField)) { setCUIndex(classEntry, pos); pos = writeInstanceClassInfo(context, classEntry, buffer, pos); @@ -1279,7 +1279,7 @@ private int writeField(DebugContext context, StructureTypeEntry entry, FieldEntr private int writeSkeletonMethodDeclarations(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { int pos = p; for (MethodEntry method : classEntry.getMethods()) { - if (method.isInRange() || method.isInlined()) { + if (method.isInRange() || method.isInlined() || method.isCompiledInPriorLayer()) { /* * Declare all methods whether or not they have been compiled or inlined. */ @@ -1357,7 +1357,7 @@ private int writeSkeletonMethodParameterDeclaration(DebugContext context, LocalE private int writeMethodDeclarations(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { int pos = p; for (MethodEntry method : classEntry.getMethods()) { - if (method.isInRange() || method.isInlined()) { + if (method.isInRange() || method.isInlined() || method.isCompiledInPriorLayer()) { /* * Declare all methods whether they have been compiled or inlined. */ diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLineSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLineSectionImpl.java index 73491c6a890d..55b0eeeb964a 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLineSectionImpl.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLineSectionImpl.java @@ -508,6 +508,11 @@ private int writeCompiledMethodLineInfo(DebugContext context, ClassEntry classEn pos = writeAdvancePCOp(context, addressDelta, buffer, pos); } } + /* + * Add a row to the line number table and reset some flags. This makes sure a + * debugger can stop here and properly process line and address. Special opcodes + * have a similar effect as the copy operation, hence it is only used here. + */ pos = writeCopyOp(context, buffer, pos); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SharedDebugInfoProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SharedDebugInfoProvider.java index 9d62fadd7c7f..8f89b8149cb0 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SharedDebugInfoProvider.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SharedDebugInfoProvider.java @@ -733,10 +733,11 @@ private MethodEntry createMethodEntry(SharedMethod method) { boolean isOverride = isOverride(method); boolean isDeopt = method.isDeoptTarget(); boolean isConstructor = method.isConstructor(); + boolean isCompiledInPriorLayer = isCompiledInPriorLayer(method); return new MethodEntry(fileEntry, line, methodName, ownerType, valueType, modifiers, params, symbolName, isDeopt, isOverride, isConstructor, - vTableOffset, lastParamSlot); + isCompiledInPriorLayer, vTableOffset, lastParamSlot); } /** @@ -1010,6 +1011,10 @@ public boolean isOverride(@SuppressWarnings("unused") SharedMethod method) { return false; } + public boolean isCompiledInPriorLayer(@SuppressWarnings("unused") SharedMethod method) { + return false; + } + public boolean isVirtual(@SuppressWarnings("unused") SharedMethod method) { return false; } @@ -1217,7 +1222,7 @@ protected LocalEntry lookupLocalEntry(String name, TypeEntry typeEntry, int slot /** * Lookup a {@code LocalValueEntry}. This can either be a register, stack, constant value. This * allows to reuse the same entries for multiple ranges. - * + * * @param localValueEntry the {@code LocalValueEntry} to lookup * @return the {@code LocalValueEntry} from the lookup map */ @@ -1403,7 +1408,7 @@ private Map initSyntheticInfoList(ParamLocationProd debug.log(DebugContext.DETAILED_LEVEL, "local[%d] %s type %s slot %d", paramIdx + 1, param.name(), param.type().getTypeName(), param.slot()); debug.log(DebugContext.DETAILED_LEVEL, " => %s", value); } - LocalValueEntry localValueEntry = createLocalValueEntry(value, PRE_EXTEND_FRAME_SIZE); + LocalValueEntry localValueEntry = createLocalValueEntry(value, PRE_EXTEND_FRAME_SIZE, true); if (localValueEntry != null) { localValueInfos.put(param, localValueEntry); } @@ -1565,7 +1570,13 @@ public Range process(CompilationResultFrameTree.FrameNode node, CallRange caller LineNumberTable lineNumberTable = method.getLineNumberTable(); int line = lineNumberTable == null ? -1 : lineNumberTable.getLineNumber(pos.getBCI()); - Map localValueInfos = initLocalInfoList(pos, methodEntry, frameSize); + /* + * Locals are stored in both leaf ranges and call ranges. However, for call ranges we do + * not want to store register values as registers may be overwritten in callee ranges. + * Thus, a debugger is still able to process stack values and constants for stack + * traces. + */ + Map localValueInfos = initLocalInfoList(pos, methodEntry, frameSize, isLeaf); Range locationInfo = Range.createSubrange(primary, methodEntry, localValueInfos, node.getStartPos(), node.getEndPos() + 1, line, callerInfo, isLeaf); if (debug.isLogEnabled()) { @@ -1585,9 +1596,10 @@ public Range process(CompilationResultFrameTree.FrameNode node, CallRange caller * @param pos the bytecode position of the location info * @param methodEntry the {@code MethodEntry} corresponding to the bytecode position * @param frameSize the current frame size + * @param isLeaf whether the range to create this value for is a leaf range * @return a mapping from {@code LocalEntry} to {@code LocalValueEntry} */ - protected Map initLocalInfoList(BytecodePosition pos, MethodEntry methodEntry, int frameSize) { + protected Map initLocalInfoList(BytecodePosition pos, MethodEntry methodEntry, int frameSize, boolean isLeaf) { Map localInfos = new HashMap<>(); if (pos instanceof BytecodeFrame frame && frame.numLocals > 0) { @@ -1688,7 +1700,7 @@ protected Map initLocalInfoList(BytecodePosition po * upfront from the local variable table, the LocalEntry already exists. */ LocalEntry localEntry = methodEntry.getOrAddLocalEntry(lookupLocalEntry(name, typeEntry, slot), line); - LocalValueEntry localValueEntry = createLocalValueEntry(value, frameSize); + LocalValueEntry localValueEntry = createLocalValueEntry(value, frameSize, isLeaf); if (localEntry != null && localValueEntry != null) { localInfos.put(localEntry, localValueEntry); } @@ -1703,16 +1715,24 @@ protected Map initLocalInfoList(BytecodePosition po /** * Creates a {@code LocalValueEntry} for a given {@code JavaValue}. This processes register - * values, stack values, primitive constants and constant in the heap. + * values, stack values, primitive constants and constant in the heap. Register values are only + * added for leaf frames, as register may be overwritten within callee frames of a call range. + * But, stack values and constants also work for call ranges. * * @param value the given {@code JavaValue} * @param frameSize the frame size for stack values + * @param isLeaf whether the range to create this value for is a leaf range * @return the {@code LocalValueEntry} or {@code null} if the value can't be processed */ - private LocalValueEntry createLocalValueEntry(JavaValue value, int frameSize) { + private LocalValueEntry createLocalValueEntry(JavaValue value, int frameSize, boolean isLeaf) { switch (value) { case RegisterValue registerValue -> { - return lookupLocalValueEntry(new RegisterValueEntry(registerValue.getRegister().number)); + /* + * We only want to create register entries for leaf nodes. Thus, they are only valid + * within a leaf range but not over a whole call range where the register value is + * potentially overwritten. + */ + return isLeaf ? lookupLocalValueEntry(new RegisterValueEntry(registerValue.getRegister().number)) : null; } case StackSlot stackValue -> { int stackSlot = frameSize == 0 ? stackValue.getRawOffset() : stackValue.getOffset(frameSize); @@ -1827,7 +1847,7 @@ private Range createEmbeddedParentLocationInfo(PrimaryRange primary, Compilation LineNumberTable lineNumberTable = method.getLineNumberTable(); int line = lineNumberTable == null ? -1 : lineNumberTable.getLineNumber(pos.getBCI()); - Map localValueInfos = initLocalInfoList(pos, methodEntry, frameSize); + Map localValueInfos = initLocalInfoList(pos, methodEntry, frameSize, true); Range locationInfo = Range.createSubrange(primary, methodEntry, localValueInfos, startPos, endPos, line, callerLocation, true); if (debug.isLogEnabled()) { @@ -1881,7 +1901,7 @@ private static boolean skipPos(BytecodePosition pos) { * at native image build-time while still having comparable performance (for most of the index * maps). The most performance critical maps that see more traffic will use the less memory * efficient ConcurrentHashMaps instead. - * + * * @param map the {@code EconomicMap} to perform {@code computeIfAbsent} on * @param key the key to look for * @param mappingFunction the function producing the value for a given key diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java index c44c86b128d0..12546999ca43 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java @@ -71,6 +71,8 @@ import com.oracle.svm.core.debug.SubstrateDebugTypeEntrySupport; import com.oracle.svm.core.graal.meta.RuntimeConfiguration; import com.oracle.svm.core.image.ImageHeapPartition; +import com.oracle.svm.core.imagelayer.DynamicImageLayerInfo; +import com.oracle.svm.core.imagelayer.ImageLayerBuildingSupport; import com.oracle.svm.core.meta.SharedMethod; import com.oracle.svm.core.meta.SharedType; import com.oracle.svm.core.util.VMError; @@ -496,6 +498,11 @@ public boolean isOverride(SharedMethod method) { return method instanceof HostedMethod && allOverrides.contains(method); } + @Override + public boolean isCompiledInPriorLayer(SharedMethod method) { + return method instanceof HostedMethod hostedMethod && hostedMethod.isCompiledInPriorLayer(); + } + @Override public boolean isVirtual(SharedMethod method) { return method instanceof HostedMethod hostedMethod && hostedMethod.hasVTableIndex(); @@ -583,7 +590,14 @@ private void processFieldEntries(HostedType type, StructureTypeEntry structureTy for (ResolvedJavaField field : type.getStaticFields()) { assert field instanceof HostedField; - structureTypeEntry.addField(createFieldEntry((HostedField) field, structureTypeEntry)); + /* + * If we are in a layered build only add debug info for a static field if it is + * installed in the current layer. + */ + if (!ImageLayerBuildingSupport.buildingImageLayer() || + (((HostedField) field).hasInstalledLayerNum() && DynamicImageLayerInfo.getCurrentLayerNumber() == StaticFieldsSupport.getInstalledLayerNum(field))) { + structureTypeEntry.addField(createFieldEntry((HostedField) field, structureTypeEntry)); + } } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/CStructTests.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/CStructTests.java index 84e0a7074b94..c5eadccdf86b 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/CStructTests.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/CStructTests.java @@ -36,11 +36,10 @@ import org.graalvm.nativeimage.c.type.CTypeConversion.CCharPointerHolder; import org.graalvm.word.PointerBase; import org.graalvm.word.SignedWord; +import org.graalvm.word.WordBase; import com.oracle.svm.core.NeverInline; -import jdk.graal.compiler.api.directives.GraalDirectives; - @CContext(CInterfaceDebugTestDirectives.class) public class CStructTests { @CPointerTo(nameOfCType = "int") @@ -238,10 +237,20 @@ private static void testMixedArguments(String m1, short s, SimpleStruct ss1, lon System.out.println("You find " + m1); System.out.println("You find " + m2); System.out.println("You find " + m3); - GraalDirectives.blackhole(s); - GraalDirectives.blackhole(ss1); - GraalDirectives.blackhole(l); - GraalDirectives.blackhole(ss2); + blackhole(s); + blackhole(ss1); + blackhole(l); + blackhole(ss2); + } + + @NeverInline("For testing.") + @SuppressWarnings("unused") + static void blackhole(Object value) { + } + + @NeverInline("For testing.") + @SuppressWarnings("unused") + static void blackhole(WordBase value) { } public static void main(String[] args) { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/PrettyPrinterTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/PrettyPrinterTest.java index 890a9f4e0e59..c2a44d03561f 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/PrettyPrinterTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/PrettyPrinterTest.java @@ -115,51 +115,54 @@ public static class Holder { @NeverInline("For testing purposes") static void testPrimitive(byte b, Byte bObj, short s, Short sObj, char c, Character cObj, int i, Integer iObj, long l, Long lObj, float f, Float fObj, double d, Double dObj, boolean x, Boolean xObj) { - System.out.print(""); + blackhole(b, bObj, s, sObj, c, cObj, i, iObj, l, lObj, f, fObj, d, dObj, x, xObj); } @SuppressWarnings("unused") @NeverInline("For testing purposes") static void testString(String nullStr, String emptyStr, String str, String uStr1, String uStr2, String uStr3, String uStr4, String uStr5, String str0) { - System.out.print(""); + blackhole(nullStr, emptyStr, str, uStr1, uStr2, uStr3, uStr4, uStr5, str0); } @SuppressWarnings("unused") @NeverInline("For testing purposes") static void testArray(int[] ia, Object[] oa, String[] sa) { - System.out.print(""); + blackhole(ia, oa, sa); } @SuppressWarnings("unused") @NeverInline("For testing purposes") static void testObject(ExampleClass object, ExampleClass recObject) { - System.out.print(""); + blackhole(object, recObject); } @SuppressWarnings("unused") @NeverInline("For testing purposes") static void testArrayList(ArrayList strList, List mixedList, ArrayList nullList) { - System.out.print(""); + blackhole(strList, mixedList, nullList); } @SuppressWarnings("unused") @NeverInline("For testing purposes") static void testHashMap(HashMap strMap, Map mixedMap) { - System.out.print(""); + blackhole(strMap, mixedMap); } @SuppressWarnings("unused") @NeverInline("For testing purposes") static void testLambda(Function lambda) { - System.out.print(""); + blackhole(lambda); } @SuppressWarnings("unused") @NeverInline("For testing purposes") static void testClassType(Class clazz, Holder dyn) { - System.out.print(""); - System.out.print(staticHolder.c); - System.out.print(staticHolder.o); + blackhole(clazz, dyn, staticHolder.c, staticHolder.o); + } + + @NeverInline("For testing.") + @SuppressWarnings("unused") + static void blackhole(Object... value) { } static ExampleClass setupExampleObject(boolean recursive) { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/RuntimeCompileDebugInfoTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/RuntimeCompileDebugInfoTest.java index 2709bc47b8ec..bcd661a9a0b2 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/RuntimeCompileDebugInfoTest.java +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/RuntimeCompileDebugInfoTest.java @@ -219,9 +219,6 @@ public static class RegisterMethodsFeature implements Feature { ModuleSupport.accessPackagesToClass(ModuleSupport.Access.EXPORT, RegisterMethodsFeature.class, false, "jdk.internal.vm.ci", "jdk.vm.ci.code"); - ModuleSupport.accessPackagesToClass(ModuleSupport.Access.EXPORT, RegisterMethodsFeature.class, false, - "jdk.graal.compiler", - "jdk.graal.compiler.api.directives", "jdk.graal.compiler.word"); } @Override From f9fff9fde19a3360e0fabc7149a31fe2759ea25e Mon Sep 17 00:00:00 2001 From: Dominik Mascherbauer Date: Wed, 6 Aug 2025 15:11:09 +0200 Subject: [PATCH 2/5] Add debug info tests for layered images and adapt testing. Adapt testhello for the layered image test. Add layered debuginfotests to gate. Use default optimization level for all debuginfo tests --- substratevm/mx.substratevm/mx_substratevm.py | 214 +++++++++++++------ substratevm/mx.substratevm/testhello.py | 22 +- 2 files changed, 162 insertions(+), 74 deletions(-) diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index 1d732d4e20b3..196747168098 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -423,6 +423,14 @@ def svm_gate_body(args, tasks): with native_image_context(IMAGE_ASSERTION_FLAGS) as native_image: debuginfotest(['--output-path', svmbuild_dir()] + args.extra_image_builder_arguments) + with Task('image layereddebuginfotest', tasks, tags=[GraalTags.debuginfotest]) as t: + if t: + if mx.is_windows(): + mx.warn('layereddebuginfotest does not work on Windows') + else: + with native_image_context(IMAGE_ASSERTION_FLAGS) as native_image: + layereddebuginfotest(['--output-path', svmbuild_dir()] + args.extra_image_builder_arguments) + with Task('image debughelpertest', tasks, tags=[GraalTags.debuginfotest]) as t: if t: if mx.is_windows(): @@ -678,14 +686,7 @@ def batched(iterable, n): def _native_junit(native_image, unittest_args, build_args=None, run_args=None, blacklist=None, whitelist=None, preserve_image=False, test_classes_per_run=None): build_args = build_args or [] - javaProperties = {} - for dist in suite.dists: - if isinstance(dist, mx.ClasspathDependency): - for cpEntry in mx.classpath_entries(dist): - if hasattr(cpEntry, "getJavaProperties"): - for key, value in cpEntry.getJavaProperties().items(): - javaProperties[key] = value - for key, value in javaProperties.items(): + for key, value in get_java_properties().items(): build_args.append("-D" + key + "=" + value) build_args.append('--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED') @@ -913,6 +914,17 @@ class HelloWorld { } +def get_java_properties(): + javaProperties = {} + for dist in suite.dists: + if isinstance(dist, mx.ClasspathDependency): + for cpEntry in mx.classpath_entries(dist): + if hasattr(cpEntry, "getJavaProperties"): + for key, value in cpEntry.getJavaProperties().items(): + javaProperties[key] = value + return javaProperties + + def _helloworld(native_image, javac_command, path, build_only, args, variant=list(_helloworld_variants.keys())[0]): mx_util.ensure_dir_exists(path) hello_file = os.path.join(path, 'HelloWorld.java') @@ -923,14 +935,7 @@ def _helloworld(native_image, javac_command, path, build_only, args, variant=lis fp.flush() mx.run(javac_command + [hello_file]) - javaProperties = {} - for dist in suite.dists: - if isinstance(dist, mx.ClasspathDependency): - for cpEntry in mx.classpath_entries(dist): - if hasattr(cpEntry, "getJavaProperties"): - for key, value in cpEntry.getJavaProperties().items(): - javaProperties[key] = value - for key, value in javaProperties.items(): + for key, value in get_java_properties().items(): args.append("-D" + key + "=" + value) binary_path = join(path, "helloworld") @@ -977,39 +982,18 @@ def _collector(x): raise Exception('Unexpected output: ' + str(actual_output) + " != " + str(expected_output)) def _debuginfotest(native_image, path, build_only, with_isolates_only, args): - mx.log(f"path={path}") sourcepath = mx.project('com.oracle.svm.test').source_dirs()[0] - mx.log(f"sourcepath={sourcepath}") - sourcecache = join(path, 'sources') - mx.log(f"sourcecache={sourcecache}") # the header file for foreign types resides at the root of the # com.oracle.svm.test source tree cincludepath = sourcepath - javaProperties = {} - for dist in suite.dists: - if isinstance(dist, mx.ClasspathDependency): - for cpEntry in mx.classpath_entries(dist): - if hasattr(cpEntry, "getJavaProperties"): - for key, value in cpEntry.getJavaProperties().items(): - javaProperties[key] = value - for key, value in javaProperties.items(): + for key, value in get_java_properties().items(): args.append("-D" + key + "=" + value) - # set property controlling inclusion of foreign struct header - args.append("-DbuildDebugInfoTestExample=true") - - native_image_args = [ - '--native-compiler-options=-I' + cincludepath, - '-H:CLibraryPath=' + sourcepath, - '--native-image-info', - '-cp', classpath('com.oracle.svm.test'), - '-Djdk.graal.LogFile=graal.log', - '-g', - ] + svm_experimental_options([ - '-H:+VerifyNamingConventions', - '-H:+SourceLevelDebug', - '-H:DebugInfoSourceSearchPath=' + sourcepath, - ]) + args + native_image_args = ( + testhello_ni_args(cincludepath, sourcepath) + + svm_experimental_options(['-H:+VerifyNamingConventions']) + + args + ) def build_debug_test(variant_name, image_name, extra_args): per_build_path = join(path, variant_name) @@ -1020,9 +1004,13 @@ def build_debug_test(variant_name, image_name, extra_args): mx.log(f'native_image {build_args}') return native_image(build_args) + env = os.environ.copy() # build with and without Isolates and check both work if '--libc=musl' in args: - os.environ.update({'debuginfotest_musl': 'yes'}) + env['debuginfotest_musl'] = 'yes' + + # this is a non-layered build + env['debuginfotest_layered'] = 'no' testhello_py = join(suite.dir, 'mx.substratevm', 'testhello.py') testhello_args = [ @@ -1031,26 +1019,75 @@ def build_debug_test(variant_name, image_name, extra_args): 'hello.Hello' ] if mx.get_os() == 'linux' and not build_only: - os.environ.update({'debuginfotest_arch': mx.get_arch()}) + env['debuginfotest_arch'] = mx.get_arch() if not with_isolates_only: hello_binary = build_debug_test('isolates_off', 'hello_image', testhello_args + svm_experimental_options(['-H:-SpawnIsolates'])) if mx.get_os() == 'linux' and not build_only: - os.environ.update({'debuginfotest_isolates': 'no'}) - mx.run([os.environ.get('GDB_BIN', 'gdb'), '--nx', '-q', '-iex', 'set pagination off', '-ex', 'python "ISOLATES=False"', '-x', testhello_py, hello_binary]) + env['debuginfotest_isolates'] = 'no' + mx.run(gdb_base_command() + ['-x', testhello_py, hello_binary], env=env) hello_binary = build_debug_test('isolates_on', 'hello_image', testhello_args + svm_experimental_options(['-H:+SpawnIsolates'])) if mx.get_os() == 'linux' and not build_only: - os.environ.update({'debuginfotest_isolates': 'yes'}) - mx.run([os.environ.get('GDB_BIN', 'gdb'), '--nx', '-q', '-iex', 'set pagination off', '-ex', 'python "ISOLATES=True"', '-x', testhello_py, hello_binary]) + env['debuginfotest_isolates'] = 'yes' + mx.run(gdb_base_command() + ['-x', testhello_py, hello_binary], env=env) -def gdb_base_command(logfile, autoload_path): - return [ - os.environ.get('GDB_BIN', 'gdb'), - '--nx', - '-q', # do not print the introductory and copyright messages - '-iex', 'set pagination off', # messages from enabling logging could already cause pagination, so this must be done first +def _layereddebuginfotest(native_image, output_path, skip_base_layer, with_isolates_only, args): + sourcepath = mx.project('com.oracle.svm.test').source_dirs()[0] + cincludepath = sourcepath + + for key, value in get_java_properties().items(): + args.append("-D" + key + "=" + value) + + # fetch arguments used in all layers + testhello_args = testhello_ni_args(cincludepath, sourcepath) + args + + def build_layer(layer_path, layer_args): + # clean / create layer output directory + if exists(layer_path): + mx.rmtree(layer_path) + mx_util.ensure_dir_exists(layer_path) + # build layer + return native_image(testhello_args + layer_args) + + base_layer_path = join(output_path, 'base-layer') + base_layer_name = 'libbase' + # Build base layer if missing or not skipped. + if not (skip_base_layer and exists(join(base_layer_path, base_layer_name + '.nil'))): + build_layer(base_layer_path, [ + '-o', join(base_layer_path, base_layer_name), + ] + svm_experimental_options([ + f'-H:LayerCreate={base_layer_name}.nil,module=java.base,package=com.oracle.svm.test' + ])) + + app_layer_path = join(output_path, 'app-layer') + app_layer_name = 'hello_image' + # build app layer + app_layer = build_layer(app_layer_path, [ + '-o', join(app_layer_path, app_layer_name), + # We do not want to step into class initializer, so initialize everything at build time. + '--initialize-at-build-time=hello', + 'hello.Hello' + ] + svm_experimental_options([ + f'-H:LayerUse={join(base_layer_path, base_layer_name)}.nil', + ])) + + # prepare environment + env = os.environ.copy() + env['debuginfotest_isolates'] = 'yes' + env['debuginfotest_layered'] = 'yes' + env['LD_LIBRARY_PATH'] = base_layer_path + + # fetch python test file + testhello_py = join(suite.dir, 'mx.substratevm', 'testhello.py') + + # run gdb + mx.run(gdb_base_command() + ['-x', testhello_py, app_layer], cwd=app_layer_path, env=env) + + +def gdb_logging_command(logfile, autoload_path): + return gdb_base_command() + [ '-iex', 'set logging redirect on', '-iex', 'set logging overwrite off', '-iex', f"set logging file {logfile}", @@ -1058,6 +1095,28 @@ def gdb_base_command(logfile, autoload_path): '-iex', f"set auto-load safe-path {autoload_path}", ] +def gdb_base_command(): + return [ + os.environ.get('GDB_BIN', 'gdb'), + '--nx', + '-q', # do not print the introductory and copyright messages + '-iex', 'set pagination off', # messages from enabling logging could already cause pagination, so this must be done first + ] + +def testhello_ni_args(cincludepath, sourcepath): + return [ + '-J-ea', '-J-esa', + '-g', + '--native-compiler-options=-I' + cincludepath, + '-H:CLibraryPath=' + sourcepath, + '--native-image-info', + '-cp', classpath('com.oracle.svm.test'), + '-Djdk.graal.LogFile=graal.log', + '-DbuildDebugInfoTestExample=true', # set property controlling inclusion of foreign struct header + ] + svm_experimental_options([ + '-H:+SourceLevelDebug', + '-H:DebugInfoSourceSearchPath=' + sourcepath, + ]) def _gdbdebughelperstest(native_image, path, with_isolates_only, args): @@ -1124,7 +1183,7 @@ def run_debug_test(image_name: str, testfile: str, source_path: str, with_isolat '-H:CLibraryPath=' + source_path, '--native-image-info', '-Djdk.graal.LogFile=graal.log', - '-g', '-O0', + '-g', ] + svm_experimental_options([ '-H:+VerifyNamingConventions', '-H:+SourceLevelDebug', @@ -1160,7 +1219,7 @@ def run_debug_test(image_name: str, testfile: str, source_path: str, with_isolat if mx.get_os() == 'linux': logfile = join(path, pathlib.Path(testfile).stem + ('' if with_isolates else '_no_isolates') + '.log') os.environ.update({'gdb_logfile': logfile}) - gdb_command = gdb_base_command(logfile, join(build_dir, 'gdb-debughelpers.py')) + [ + gdb_command = gdb_logging_command(logfile, join(build_dir, 'gdb-debughelpers.py')) + [ '-x', testfile, join(build_dir, image_name) ] # unittest may result in different exit code, nonZeroIsFatal ensures that we can go on with other test @@ -1209,7 +1268,7 @@ def _runtimedebuginfotest(native_image, output_path, with_isolates_only, args=No # Build the native image from Java code build_args = [ - '-g', '-O0', + '-g', # set property controlling inclusion of foreign struct header '-DbuildDebugInfoTestExample=true', '--native-compiler-options=-I' + test_source_path, @@ -1230,7 +1289,7 @@ def _runtimedebuginfotest(native_image, output_path, with_isolates_only, args=No logfile = join(output_path, 'test_runtime_compilation.log') os.environ.update({'gdb_logfile': logfile}) - gdb_command = gdb_base_command(logfile, join(output_path, 'gdb-debughelpers.py')) + [ + gdb_command = gdb_logging_command(logfile, join(output_path, 'gdb-debughelpers.py')) + [ '-x', test_runtime_compilation_py, runtime_compile_binary ] # unittest may result in different exit code, nonZeroIsFatal ensures that we can go on with other test @@ -1244,12 +1303,12 @@ def run_js_test(eager: bool = False): '-H:+RuntimeDebugInfo', '-H:-LazyDeoptimization' if eager else '-H:+LazyDeoptimization', ]) + - ['-g', '-O0', '--macro:jsvm-library'] + ['-g', '--macro:jsvm-library'] )) js_launcher = get_js_launcher(jslib) logfile = join(output_path, 'test_runtime_deopt_' + ('eager' if eager else 'lazy') + '.log') os.environ.update({'gdb_logfile': logfile}) - gdb_command = gdb_base_command(logfile, join(output_path, 'gdb-debughelpers.py')) + [ + gdb_command = gdb_logging_command(logfile, join(output_path, 'gdb-debughelpers.py')) + [ '-x', test_runtime_deopt_py, '--args', js_launcher, testdeopt_js ] # unittest may result in different exit code, nonZeroIsFatal ensures that we can go on with other test @@ -1813,6 +1872,30 @@ def debuginfotest(args, config=None): config=config ) +@mx.command(suite_name=suite.name, command_name='layereddebuginfotest', usage_msg='[options]') +def layereddebuginfotest(args, config=None): + """ + Builds a layered native image and tests it with gdb. + Base layer: java.base, com.oracle.svm.test + App Layer: hello.Hello + """ + parser = ArgumentParser(prog='mx layereddebuginfotest') + all_args = ['--output-path', '--skip-base-layer', '--with-isolates-only'] + masked_args = [_mask(arg, all_args) for arg in args] + parser.add_argument(all_args[0], metavar='', nargs=1, help='Path of the generated image', default=[join(svmbuild_dir(), "layereddebuginfotest")]) + parser.add_argument(all_args[1], action='store_true', help='Skip building the base layer if it already exists') + parser.add_argument(all_args[2], action='store_true', help='Only build and test the native image with isolates') + parser.add_argument('image_args', nargs='*', default=[]) + parsed = parser.parse_args(masked_args) + output_path = unmask(parsed.output_path)[0] + skip_base_layer = parsed.skip_base_layer + with_isolates_only = parsed.with_isolates_only + native_image_context_run( + lambda native_image, a: + _layereddebuginfotest(native_image, output_path, skip_base_layer, with_isolates_only, a), unmask(parsed.image_args), + config=config + ) + @mx.command(suite_name=suite.name, command_name='debuginfotestshared', usage_msg='[options]') def debuginfotestshared(args, config=None): """ @@ -2424,15 +2507,8 @@ def native_image_on_jvm(args, **kwargs): if not exists(executable): mx.abort("Can not find " + executable + "\nDid you forget to build? Try `mx build`") - javaProperties = {} - for dist in suite.dists: - if isinstance(dist, mx.ClasspathDependency): - for cpEntry in mx.classpath_entries(dist): - if hasattr(cpEntry, "getJavaProperties"): - for key, value in cpEntry.getJavaProperties().items(): - javaProperties[key] = value if not any(arg.startswith('--help') or arg == '--version' for arg in args): - for key, value in javaProperties.items(): + for key, value in get_java_properties().items(): args.append("-D" + key + "=" + value) jacoco_args = mx_gate.get_jacoco_agent_args(agent_option_prefix='-J') diff --git a/substratevm/mx.substratevm/testhello.py b/substratevm/mx.substratevm/testhello.py index 3d04c62ab2c9..b23351f67fc7 100644 --- a/substratevm/mx.substratevm/testhello.py +++ b/substratevm/mx.substratevm/testhello.py @@ -74,6 +74,8 @@ def test(): isolates = os.environ.get('debuginfotest_isolates', 'no') == 'yes' + layered = os.environ.get('debuginfotest_layered', 'no') == 'yes' + arch = os.environ.get('debuginfotest_arch', 'amd64') print(f"Testing with isolates {'enabled' if isolates else 'disabled'}!") @@ -595,13 +597,18 @@ def test(): execute("delete breakpoints") # Set breakpoint at method with inline and not-inlined invocation in same line exec_string = execute("break hello.Hello::inlineFrom") - rexp = fr"Breakpoint {digits_pattern} at {address_pattern}: file hello/Hello\.java, line {digits_pattern}." + if layered: + rexp = fr"Breakpoint {digits_pattern} at {address_pattern}: hello\.Hello::inlineFrom\. \({digits_pattern} locations\)" + else: + rexp = fr"Breakpoint {digits_pattern} at {address_pattern}: file hello/Hello\.java, line {digits_pattern}." checker = Checker('break inlineFrom', rexp) checker.check(exec_string, skip_fails=False) exec_string = execute("info break") - rexp = [ - fr"{digits_pattern}{spaces_pattern}breakpoint{spaces_pattern}keep{spaces_pattern}y{spaces_pattern}{address_pattern} in hello\.Hello::inlineFrom\(\) at hello/Hello\.java:131"] + if layered: + rexp = [fr"{wildcard_pattern}y{spaces_pattern}{address_pattern} in hello\.Hello::inlineFrom\(\) at hello/Hello\.java:131"] + else: + rexp = [fr"{digits_pattern}{spaces_pattern}breakpoint{spaces_pattern}keep{spaces_pattern}y{spaces_pattern}{address_pattern} in hello\.Hello::inlineFrom\(\) at hello/Hello\.java:131"] checker = Checker('info break inlineFrom', rexp) checker.check(exec_string) @@ -661,6 +668,11 @@ def test(): fr"#12{spaces_pattern}hello\.Hello::inlineFrom{no_param_types_pattern} {no_arg_values_pattern} at hello/Hello\.java:133", fr"#13{spaces_pattern}hello\.Hello::main{param_types_pattern} {arg_values_pattern} at hello/Hello\.java:{main_inlinefrom:d}"] checker = Checker('backtrace in recursive inlineTo', rexp) + # This test does not work with -O0 in graal-enterprise + # This is because the compilation for inlineTo contains the same source line (160) twice in what GDB figures to be the same lexical context. + # GDB has an "optimization" that if it finds the same line twice in the same lexical context, it just takes the first as breakpoint and drops all other occasions of this line. + # To make this work, we would need to create an artificial lexical context that splits occasions of the same source line into different lexical contexts in GDB while still having the same symbols. + # checker.check(exec_string, skip_fails=False) execute("delete breakpoints") @@ -872,8 +884,8 @@ def test(): exec_string = execute("backtrace 3") rexp = [ fr"#0{spaces_pattern}hello\.Hello::lambda\$(static\$)?0{no_param_types_pattern} {no_arg_values_pattern} at hello/Hello\.java:222", - fr"#1{spaces_pattern}{address_pattern} in hello\.Hello\$\$Lambda((\${digits_pattern}/0x)|(\$)|(\.0x|/0x))?{hex_digits_pattern}::get{wildcard_pattern} at hello/Hello\.java:259", - fr"#2{spaces_pattern}hello\.Hello::main{param_types_pattern} {arg_values_pattern} at hello/Hello\.java:259"] + fr"#1{spaces_pattern}{address_pattern} in hello\.Hello\$\$Lambda((\${digits_pattern}/0x)|(\$)|(\.0x|/0x))?{hex_digits_pattern}::get{wildcard_pattern}", + fr"#2{spaces_pattern} hello\.Hello::main{param_types_pattern} {arg_values_pattern} at hello/Hello\.java:259"] checker = Checker('backtrace in lambda', rexp) checker.check(exec_string, skip_fails=False) From 2e89e8cdb9dc654fd8202561a65937d0a4c12a68 Mon Sep 17 00:00:00 2001 From: Dominik Mascherbauer Date: Mon, 11 Aug 2025 12:04:09 +0200 Subject: [PATCH 3/5] Move debuginfotests into its own module. This makes native image builds for debuginfo tests independent of svm tests. Make debuginfotests work with -O2. --- substratevm/mx.substratevm/mx_substratevm.py | 48 +++++++++--------- substratevm/mx.substratevm/suite.py | 49 ++++++++++++------- .../debug}/missing/classes/TestClass.java | 2 +- .../debug/CInterfaceDebugTestDirectives.java | 0 .../oracle/svm/test/debug/CStructTests.java | 0 .../test/debug/helper/ClassLoaderTest.java | 6 +-- .../test/debug/helper/PrettyPrinterTest.java | 3 +- .../helper/RuntimeCompileDebugInfoTest.java | 5 +- .../src/gdb-tests}/gdb_helper.py | 0 .../src/gdb-tests}/gdb_utils.py | 0 .../src/gdb-tests}/test_cinterface.py | 0 .../src/gdb-tests}/test_class_loader.py | 12 ++--- .../src/gdb-tests}/test_pretty_printer.py | 0 .../gdb-tests}/test_runtime_compilation.py | 0 .../src/gdb-tests}/test_runtime_deopt.py | 0 .../src/gdb-tests}/test_settings.py | 18 +++---- .../src/gdb-tests}/test_svm_util.py | 0 .../src/gdb-tests}/testdeopt.js | 0 .../src/gdb-tests}/testhello.py | 7 ++- .../src/hello/Hello.java | 0 .../Target_hello_Hello_DefaultGreeter.java | 0 .../src/systemjava_debugtest.h | 0 22 files changed, 81 insertions(+), 69 deletions(-) rename substratevm/src/{com.oracle.svm.test.missing.classes/src/com/oracle/svm/test => com.oracle.svm.test.debug.missing.classes/src/com/oracle/svm/test/debug}/missing/classes/TestClass.java (97%) rename substratevm/src/{com.oracle.svm.test => com.oracle.svm.test.debug}/src/com/oracle/svm/test/debug/CInterfaceDebugTestDirectives.java (100%) rename substratevm/src/{com.oracle.svm.test => com.oracle.svm.test.debug}/src/com/oracle/svm/test/debug/CStructTests.java (100%) rename substratevm/src/{com.oracle.svm.test => com.oracle.svm.test.debug}/src/com/oracle/svm/test/debug/helper/ClassLoaderTest.java (95%) rename substratevm/src/{com.oracle.svm.test => com.oracle.svm.test.debug}/src/com/oracle/svm/test/debug/helper/PrettyPrinterTest.java (98%) rename substratevm/src/{com.oracle.svm.test => com.oracle.svm.test.debug}/src/com/oracle/svm/test/debug/helper/RuntimeCompileDebugInfoTest.java (99%) rename substratevm/src/{com.oracle.svm.test/src/com/oracle/svm/test/debug/helper => com.oracle.svm.test.debug/src/gdb-tests}/gdb_helper.py (100%) rename substratevm/{mx.substratevm => src/com.oracle.svm.test.debug/src/gdb-tests}/gdb_utils.py (100%) rename substratevm/src/{com.oracle.svm.test/src/com/oracle/svm/test/debug/helper => com.oracle.svm.test.debug/src/gdb-tests}/test_cinterface.py (100%) rename substratevm/src/{com.oracle.svm.test/src/com/oracle/svm/test/debug/helper => com.oracle.svm.test.debug/src/gdb-tests}/test_class_loader.py (85%) rename substratevm/src/{com.oracle.svm.test/src/com/oracle/svm/test/debug/helper => com.oracle.svm.test.debug/src/gdb-tests}/test_pretty_printer.py (100%) rename substratevm/src/{com.oracle.svm.test/src/com/oracle/svm/test/debug/helper => com.oracle.svm.test.debug/src/gdb-tests}/test_runtime_compilation.py (100%) rename substratevm/src/{com.oracle.svm.test/src/com/oracle/svm/test/debug/helper => com.oracle.svm.test.debug/src/gdb-tests}/test_runtime_deopt.py (100%) rename substratevm/src/{com.oracle.svm.test/src/com/oracle/svm/test/debug/helper => com.oracle.svm.test.debug/src/gdb-tests}/test_settings.py (96%) rename substratevm/src/{com.oracle.svm.test/src/com/oracle/svm/test/debug/helper => com.oracle.svm.test.debug/src/gdb-tests}/test_svm_util.py (100%) rename substratevm/{mx.substratevm => src/com.oracle.svm.test.debug/src/gdb-tests}/testdeopt.js (100%) rename substratevm/{mx.substratevm => src/com.oracle.svm.test.debug/src/gdb-tests}/testhello.py (99%) rename substratevm/src/{com.oracle.svm.test => com.oracle.svm.test.debug}/src/hello/Hello.java (100%) rename substratevm/src/{com.oracle.svm.test => com.oracle.svm.test.debug}/src/hello/Target_hello_Hello_DefaultGreeter.java (100%) rename substratevm/src/{com.oracle.svm.test => com.oracle.svm.test.debug}/src/systemjava_debugtest.h (100%) diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index 196747168098..a0e7132da63b 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -982,9 +982,9 @@ def _collector(x): raise Exception('Unexpected output: ' + str(actual_output) + " != " + str(expected_output)) def _debuginfotest(native_image, path, build_only, with_isolates_only, args): - sourcepath = mx.project('com.oracle.svm.test').source_dirs()[0] + sourcepath = mx.project('com.oracle.svm.test.debug').source_dirs()[0] # the header file for foreign types resides at the root of the - # com.oracle.svm.test source tree + # com.oracle.svm.test.debug source tree cincludepath = sourcepath for key, value in get_java_properties().items(): args.append("-D" + key + "=" + value) @@ -1012,7 +1012,7 @@ def build_debug_test(variant_name, image_name, extra_args): # this is a non-layered build env['debuginfotest_layered'] = 'no' - testhello_py = join(suite.dir, 'mx.substratevm', 'testhello.py') + testhello_py = join(sourcepath, 'gdb-tests', 'testhello.py') testhello_args = [ # We do not want to step into class initializer, so initialize everything at build time. '--initialize-at-build-time=hello', @@ -1034,7 +1034,7 @@ def build_debug_test(variant_name, image_name, extra_args): def _layereddebuginfotest(native_image, output_path, skip_base_layer, with_isolates_only, args): - sourcepath = mx.project('com.oracle.svm.test').source_dirs()[0] + sourcepath = mx.project('com.oracle.svm.test.debug').source_dirs()[0] cincludepath = sourcepath for key, value in get_java_properties().items(): @@ -1058,7 +1058,7 @@ def build_layer(layer_path, layer_args): build_layer(base_layer_path, [ '-o', join(base_layer_path, base_layer_name), ] + svm_experimental_options([ - f'-H:LayerCreate={base_layer_name}.nil,module=java.base,package=com.oracle.svm.test' + f'-H:LayerCreate={base_layer_name}.nil,module=java.base' ])) app_layer_path = join(output_path, 'app-layer') @@ -1080,7 +1080,7 @@ def build_layer(layer_path, layer_args): env['LD_LIBRARY_PATH'] = base_layer_path # fetch python test file - testhello_py = join(suite.dir, 'mx.substratevm', 'testhello.py') + testhello_py = join(sourcepath, 'gdb-tests', 'testhello.py') # run gdb mx.run(gdb_base_command() + ['-x', testhello_py, app_layer], cwd=app_layer_path, env=env) @@ -1110,13 +1110,13 @@ def testhello_ni_args(cincludepath, sourcepath): '--native-compiler-options=-I' + cincludepath, '-H:CLibraryPath=' + sourcepath, '--native-image-info', - '-cp', classpath('com.oracle.svm.test'), + '-cp', classpath('com.oracle.svm.test.debug'), '-Djdk.graal.LogFile=graal.log', - '-DbuildDebugInfoTestExample=true', # set property controlling inclusion of foreign struct header - ] + svm_experimental_options([ - '-H:+SourceLevelDebug', - '-H:DebugInfoSourceSearchPath=' + sourcepath, - ]) + '-DbuildDebugInfoTestExample=true', # set property controlling inclusion of foreign struct header + ] + svm_experimental_options([ + '-H:+SourceLevelDebug', + '-H:DebugInfoSourceSearchPath=' + sourcepath, + ]) def _gdbdebughelperstest(native_image, path, with_isolates_only, args): @@ -1134,13 +1134,13 @@ def _gdbdebughelperstest(native_image, path, with_isolates_only, args): ('GDB Python API is not available.' if gdb_version == 0 else f'found GDB version {gdb_version}.')) # =============================== - test_proj = mx.dependency('com.oracle.svm.test') + test_proj = mx.dependency('com.oracle.svm.test.debug') test_source_path = test_proj.source_dirs()[0] tutorial_proj = mx.dependency('com.oracle.svm.tutorial') tutorial_c_source_dir = join(tutorial_proj.dir, 'native') tutorial_source_path = tutorial_proj.source_dirs()[0] - test_python_source_dir = join(test_source_path, 'com', 'oracle', 'svm', 'test', 'debug', 'helper') + test_python_source_dir = join(test_source_path, 'gdb-tests') test_pretty_printer_py = join(test_python_source_dir, 'test_pretty_printer.py') test_cinterface_py = join(test_python_source_dir, 'test_cinterface.py') test_class_loader_py = join(test_python_source_dir, 'test_class_loader.py') @@ -1148,7 +1148,7 @@ def _gdbdebughelperstest(native_image, path, with_isolates_only, args): test_svm_util_py = join(test_python_source_dir, 'test_svm_util.py') test_pretty_printer_args = [ - '-cp', classpath('com.oracle.svm.test'), + '-cp', classpath('com.oracle.svm.test.debug'), # We do not want to step into class initializer, so initialize everything at build time. '--initialize-at-build-time=com.oracle.svm.test.debug.helper', 'com.oracle.svm.test.debug.helper.PrettyPrinterTest' @@ -1159,11 +1159,11 @@ def _gdbdebughelperstest(native_image, path, with_isolates_only, args): '-cp', tutorial_proj.output_dir() ] test_class_loader_args = [ - '-cp', classpath('com.oracle.svm.test'), - '-Dsvm.test.missing.classes=' + classpath('com.oracle.svm.test.missing.classes'), + '-cp', classpath('com.oracle.svm.test.debug'), + '-Dcom.oracle.svm.test.debug.missing.classes=' + classpath('com.oracle.svm.test.debug.missing.classes'), '--initialize-at-build-time=com.oracle.svm.test.debug.helper', # We need the static initializer of the ClassLoaderTest to run at image build time - '--initialize-at-build-time=com.oracle.svm.test.missing.classes', + '--initialize-at-build-time=com.oracle.svm.test.debug.missing.classes', 'com.oracle.svm.test.debug.helper.ClassLoaderTest' ] @@ -1253,13 +1253,13 @@ def _runtimedebuginfotest(native_image, output_path, with_isolates_only, args=No args = [] if args is None else args - test_proj = mx.dependency('com.oracle.svm.test') + test_proj = mx.dependency('com.oracle.svm.test.debug') test_source_path = test_proj.source_dirs()[0] - test_python_source_dir = join(test_source_path, 'com', 'oracle', 'svm', 'test', 'debug', 'helper') + test_python_source_dir = join(test_source_path, 'gdb-tests') test_runtime_compilation_py = join(test_python_source_dir, 'test_runtime_compilation.py') test_runtime_deopt_py = join(test_python_source_dir, 'test_runtime_deopt.py') - testdeopt_js = join(suite.dir, 'mx.substratevm', 'testdeopt.js') + testdeopt_js = join(test_python_source_dir, 'testdeopt.js') # clean / create output directory if exists(output_path): @@ -1273,9 +1273,11 @@ def _runtimedebuginfotest(native_image, output_path, with_isolates_only, args=No '-DbuildDebugInfoTestExample=true', '--native-compiler-options=-I' + test_source_path, '-o', join(output_path, 'runtimedebuginfotest'), - '-cp', classpath('com.oracle.svm.test'), + '-cp', classpath('com.oracle.svm.test.debug'), # We do not want to step into class initializer, so initialize everything at build time. '--initialize-at-build-time=com.oracle.svm.test.debug.helper', + # We need access to ModuleSupport + '--add-exports=org.graalvm.nativeimage.base/com.oracle.svm.util=ALL-UNNAMED', '--features=com.oracle.svm.test.debug.helper.RuntimeCompileDebugInfoTest$RegisterMethodsFeature', 'com.oracle.svm.test.debug.helper.RuntimeCompileDebugInfoTest', ] + svm_experimental_options([ @@ -1876,7 +1878,7 @@ def debuginfotest(args, config=None): def layereddebuginfotest(args, config=None): """ Builds a layered native image and tests it with gdb. - Base layer: java.base, com.oracle.svm.test + Base layer: java.base App Layer: hello.Hello """ parser = ArgumentParser(prog='mx layereddebuginfotest') diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index 2a265097b27f..043af6b0719e 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -1172,10 +1172,6 @@ "jdk.internal.misc", "sun.security.jca", ], - "jdk.internal.vm.ci" : [ - "jdk.vm.ci.code", - "jdk.vm.ci.meta", - ], }, "checkstyle": "com.oracle.svm.test", "checkstyleVersion" : "10.21.0", @@ -1189,19 +1185,46 @@ "jacoco" : "exclude", }, - "com.oracle.svm.test.missing.classes": { + "com.oracle.svm.test.debug": { "subDir": "src", "sourceDirs": ["src"], "dependencies": [ + "sdk:NATIVEIMAGE", + "SVM", ], + "requiresConcealed": { + "jdk.internal.vm.ci": [ + "jdk.vm.ci.code", + "jdk.vm.ci.meta", + ], + }, + "checkstyle": "com.oracle.svm.test", + "workingSets": "SVM", "annotationProcessors": [ + "compiler:GRAAL_PROCESSOR", + "SVM_PROCESSOR", + ], + "javaCompliance": "21+", + "spotbugs": "false", + "jacoco": "exclude", + "testProject": True, + }, + + "com.oracle.svm.test.debug.missing.classes": { + "subDir": "src", + "sourceDirs": ["src"], + "dependencies": [ ], "checkstyle": "com.oracle.svm.test", - "javaCompliance" : "21+", "workingSets": "SVM", + "annotationProcessors": [ + "compiler:GRAAL_PROCESSOR", + "SVM_PROCESSOR", + ], + "javaCompliance": "21+", "spotbugs": "false", + "jacoco": "exclude", "testProject": True, - "jacoco" : "exclude", }, "com.oracle.svm.with.space.test": { @@ -2489,6 +2512,8 @@ "com.oracle.svm.test", "com.oracle.svm.configure.test", "com.oracle.svm.graal.test", + "com.oracle.svm.test.debug", + "com.oracle.svm.test.debug.missing.classes", "SVM_TEST_RESOURCE_WITH_SPACE", ], "distDependencies": [ @@ -2496,20 +2521,10 @@ "sdk:NATIVEIMAGE", "SVM", "SVM_CONFIGURE", - "SVM_TEST_MISSING_CLASSES", ], "testDistribution" : True, }, - "SVM_TEST_MISSING_CLASSES" : { - "subDir": "src", - "relpath" : True, - "dependencies": [ - "com.oracle.svm.test.missing.classes" - ], - "testDistribution": True, - }, - # Special test distribution used for testing inclusion of resources from jar files with a space in their name. # The space in the distribution name is intentional. "SVM_TESTS WITH SPACE" : { diff --git a/substratevm/src/com.oracle.svm.test.missing.classes/src/com/oracle/svm/test/missing/classes/TestClass.java b/substratevm/src/com.oracle.svm.test.debug.missing.classes/src/com/oracle/svm/test/debug/missing/classes/TestClass.java similarity index 97% rename from substratevm/src/com.oracle.svm.test.missing.classes/src/com/oracle/svm/test/missing/classes/TestClass.java rename to substratevm/src/com.oracle.svm.test.debug.missing.classes/src/com/oracle/svm/test/debug/missing/classes/TestClass.java index e4a5070e5c47..a0de643f0a13 100644 --- a/substratevm/src/com.oracle.svm.test.missing.classes/src/com/oracle/svm/test/missing/classes/TestClass.java +++ b/substratevm/src/com.oracle.svm.test.debug.missing.classes/src/com/oracle/svm/test/debug/missing/classes/TestClass.java @@ -22,7 +22,7 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.test.missing.classes; +package com.oracle.svm.test.debug.missing.classes; public class TestClass { public static final Object CONSTANT_OBJECT_FIELD = 42; diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/CInterfaceDebugTestDirectives.java b/substratevm/src/com.oracle.svm.test.debug/src/com/oracle/svm/test/debug/CInterfaceDebugTestDirectives.java similarity index 100% rename from substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/CInterfaceDebugTestDirectives.java rename to substratevm/src/com.oracle.svm.test.debug/src/com/oracle/svm/test/debug/CInterfaceDebugTestDirectives.java diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/CStructTests.java b/substratevm/src/com.oracle.svm.test.debug/src/com/oracle/svm/test/debug/CStructTests.java similarity index 100% rename from substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/CStructTests.java rename to substratevm/src/com.oracle.svm.test.debug/src/com/oracle/svm/test/debug/CStructTests.java diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/ClassLoaderTest.java b/substratevm/src/com.oracle.svm.test.debug/src/com/oracle/svm/test/debug/helper/ClassLoaderTest.java similarity index 95% rename from substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/ClassLoaderTest.java rename to substratevm/src/com.oracle.svm.test.debug/src/com/oracle/svm/test/debug/helper/ClassLoaderTest.java index 2a4b01602a4a..4db7d9b3e572 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/ClassLoaderTest.java +++ b/substratevm/src/com.oracle.svm.test.debug/src/com/oracle/svm/test/debug/helper/ClassLoaderTest.java @@ -44,10 +44,10 @@ public class ClassLoaderTest { static { try { - Path path = Paths.get(System.getProperty("svm.test.missing.classes")); + Path path = Paths.get(System.getProperty("com.oracle.svm.test.debug.missing.classes")); URL[] urls = new URL[]{path.toUri().toURL()}; try (URLClassLoader classLoader = new URLClassLoader("testClassLoader", urls, ClassLoaderTest.class.getClassLoader())) { - testClass = classLoader.loadClass("com.oracle.svm.test.missing.classes.TestClass"); + testClass = classLoader.loadClass("com.oracle.svm.test.debug.missing.classes.TestClass"); testObj = testClass.getConstructor().newInstance(); instanceMethod = testClass.getDeclaredMethod("instanceMethod"); } catch (IOException | ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { @@ -55,7 +55,7 @@ public class ClassLoaderTest { } // load the same class from two different classLoaders try (URLClassLoader classLoader = new URLClassLoader(urls, ClassLoaderTest.class.getClassLoader())) { - testClass2 = classLoader.loadClass("com.oracle.svm.test.missing.classes.TestClass"); + testClass2 = classLoader.loadClass("com.oracle.svm.test.debug.missing.classes.TestClass"); testObj2 = testClass2.getConstructor().newInstance(); instanceMethod2 = testClass2.getDeclaredMethod("instanceMethod"); } catch (IOException | ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/PrettyPrinterTest.java b/substratevm/src/com.oracle.svm.test.debug/src/com/oracle/svm/test/debug/helper/PrettyPrinterTest.java similarity index 98% rename from substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/PrettyPrinterTest.java rename to substratevm/src/com.oracle.svm.test.debug/src/com/oracle/svm/test/debug/helper/PrettyPrinterTest.java index c2a44d03561f..df25a2d746eb 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/PrettyPrinterTest.java +++ b/substratevm/src/com.oracle.svm.test.debug/src/com/oracle/svm/test/debug/helper/PrettyPrinterTest.java @@ -151,7 +151,7 @@ static void testHashMap(HashMap strMap, Map mixe @SuppressWarnings("unused") @NeverInline("For testing purposes") static void testLambda(Function lambda) { - blackhole(lambda); + lambda.apply("test"); } @SuppressWarnings("unused") @@ -166,6 +166,7 @@ static void blackhole(Object... value) { } static ExampleClass setupExampleObject(boolean recursive) { + ExampleClass.s1 += "!"; // Ensure the static field ends up in the debug info. ExampleClass example = new ExampleClass(); example.f10 = new ExampleClass(10, 20, (short) 30, '\40', (byte) 50, true, "60", Day.Sunday, new Object(), null); example.f9 = new ArrayList<>(List.of(example.f10, new ExampleClass())); diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/RuntimeCompileDebugInfoTest.java b/substratevm/src/com.oracle.svm.test.debug/src/com/oracle/svm/test/debug/helper/RuntimeCompileDebugInfoTest.java similarity index 99% rename from substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/RuntimeCompileDebugInfoTest.java rename to substratevm/src/com.oracle.svm.test.debug/src/com/oracle/svm/test/debug/helper/RuntimeCompileDebugInfoTest.java index bcd661a9a0b2..9cacd64020be 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/RuntimeCompileDebugInfoTest.java +++ b/substratevm/src/com.oracle.svm.test.debug/src/com/oracle/svm/test/debug/helper/RuntimeCompileDebugInfoTest.java @@ -25,8 +25,6 @@ package com.oracle.svm.test.debug.helper; -import static com.oracle.svm.core.util.VMError.shouldNotReachHere; - import java.util.ArrayList; import java.util.List; @@ -46,6 +44,7 @@ import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.NeverInline; import com.oracle.svm.core.c.InvokeJavaFunctionPointer; +import com.oracle.svm.core.util.VMError; import com.oracle.svm.graal.SubstrateGraalUtils; import com.oracle.svm.graal.hosted.runtimecompilation.RuntimeCompilationFeature; import com.oracle.svm.graal.meta.SubstrateMethod; @@ -256,7 +255,7 @@ private static SubstrateMethod prepareMethodForRuntimeCompilation(BeforeAnalysis try { return runtimeCompilationFeature.prepareMethodForRuntimeCompilation(holder.getMethod(methodName, signature), config); } catch (NoSuchMethodException ex) { - throw shouldNotReachHere(ex); + throw VMError.shouldNotReachHere(ex); } } } diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/gdb_helper.py b/substratevm/src/com.oracle.svm.test.debug/src/gdb-tests/gdb_helper.py similarity index 100% rename from substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/gdb_helper.py rename to substratevm/src/com.oracle.svm.test.debug/src/gdb-tests/gdb_helper.py diff --git a/substratevm/mx.substratevm/gdb_utils.py b/substratevm/src/com.oracle.svm.test.debug/src/gdb-tests/gdb_utils.py similarity index 100% rename from substratevm/mx.substratevm/gdb_utils.py rename to substratevm/src/com.oracle.svm.test.debug/src/gdb-tests/gdb_utils.py diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_cinterface.py b/substratevm/src/com.oracle.svm.test.debug/src/gdb-tests/test_cinterface.py similarity index 100% rename from substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_cinterface.py rename to substratevm/src/com.oracle.svm.test.debug/src/gdb-tests/test_cinterface.py diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_class_loader.py b/substratevm/src/com.oracle.svm.test.debug/src/gdb-tests/test_class_loader.py similarity index 85% rename from substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_class_loader.py rename to substratevm/src/com.oracle.svm.test.debug/src/gdb-tests/test_class_loader.py index 577763f9bf8e..529c8d34a074 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_class_loader.py +++ b/substratevm/src/com.oracle.svm.test.debug/src/gdb-tests/test_class_loader.py @@ -45,22 +45,22 @@ def tearDown(self): gdb_kill() def test_instanceMethod_named_classloader(self): - gdb_set_breakpoint("com.oracle.svm.test.missing.classes.TestClass::instanceMethod") + gdb_set_breakpoint("com.oracle.svm.test.debug.missing.classes.TestClass::instanceMethod") gdb_continue() # named classloader is called first in test code exec_string = gdb_output("this") self.assertTrue(exec_string.startswith("testClassLoader_"), f"GDB output: '{exec_string}'") # check for correct class loader - self.assertIn("::com.oracle.svm.test.missing.classes.TestClass = {", exec_string) # check if TestClass has a namespace + self.assertIn("::com.oracle.svm.test.debug.missing.classes.TestClass = {", exec_string) # check if TestClass has a namespace self.assertIn("instanceField = null", exec_string) gdb_output("$other=(('java.lang.Object' *)this)") self.assertIn("null", gdb_advanced_print("$other.instanceField")) # force a typecast def test_instanceMethod_unnamed_classloader(self): - gdb_set_breakpoint("com.oracle.svm.test.missing.classes.TestClass::instanceMethod") + gdb_set_breakpoint("com.oracle.svm.test.debug.missing.classes.TestClass::instanceMethod") gdb_continue() # skip named classloader gdb_continue() # unnamed classloader is called second in test code exec_string = gdb_output("this") self.assertTrue(exec_string.startswith("URLClassLoader_"), f"GDB output: '{exec_string}'") # check for correct class loader - self.assertIn("::com.oracle.svm.test.missing.classes.TestClass = {", exec_string) # check if TestClass has a namespace + self.assertIn("::com.oracle.svm.test.debug.missing.classes.TestClass = {", exec_string) # check if TestClass has a namespace self.assertIn("instanceField = null", exec_string) gdb_output("$other=(('java.lang.Object' *)this)") self.assertIn("null", gdb_advanced_print("$other.instanceField")) # force a typecast @@ -79,9 +79,9 @@ def tearDown(self): gdb_kill() def test_get_classloader_namespace(self): - gdb_set_breakpoint("com.oracle.svm.test.missing.classes.TestClass::instanceMethod") + gdb_set_breakpoint("com.oracle.svm.test.debug.missing.classes.TestClass::instanceMethod") gdb_run() - this = gdb.parse_and_eval('this') # type = com.oracle.svm.test.missing.classes.TestClass -> testClassLoader + this = gdb.parse_and_eval('this') # type = com.oracle.svm.test.debug.missing.classes.TestClass -> testClassLoader field = gdb.parse_and_eval('this.instanceField') # instanceField is null self.assertRegex(self.svm_util.get_classloader_namespace(this), f'testClassLoader_{hex_rexp.pattern}') self.assertEqual(self.svm_util.get_classloader_namespace(field), "") diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_pretty_printer.py b/substratevm/src/com.oracle.svm.test.debug/src/gdb-tests/test_pretty_printer.py similarity index 100% rename from substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_pretty_printer.py rename to substratevm/src/com.oracle.svm.test.debug/src/gdb-tests/test_pretty_printer.py diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_runtime_compilation.py b/substratevm/src/com.oracle.svm.test.debug/src/gdb-tests/test_runtime_compilation.py similarity index 100% rename from substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_runtime_compilation.py rename to substratevm/src/com.oracle.svm.test.debug/src/gdb-tests/test_runtime_compilation.py diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_runtime_deopt.py b/substratevm/src/com.oracle.svm.test.debug/src/gdb-tests/test_runtime_deopt.py similarity index 100% rename from substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_runtime_deopt.py rename to substratevm/src/com.oracle.svm.test.debug/src/gdb-tests/test_runtime_deopt.py diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_settings.py b/substratevm/src/com.oracle.svm.test.debug/src/gdb-tests/test_settings.py similarity index 96% rename from substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_settings.py rename to substratevm/src/com.oracle.svm.test.debug/src/gdb-tests/test_settings.py index 4557bcff38eb..cef241d5558e 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_settings.py +++ b/substratevm/src/com.oracle.svm.test.debug/src/gdb-tests/test_settings.py @@ -129,15 +129,13 @@ def test_auto_complete_field_access(self): result = self.svm_command_print.complete("oa[3].add", "") self.assertIn('add', result) self.assertIn('addAll', result) - self.assertIn('addFirst', result) - self.assertIn('addLast', result) def test_auto_complete_invalid(self): gdb_set_breakpoint("com.oracle.svm.test.debug.helper.PrettyPrinterTest::testArray") gdb_run() self.assertEqual(self.svm_command_print.complete("oa[3].addNone", ""), []) - def test_auto_complete_None(self): + def test_auto_complete_none(self): gdb_set_breakpoint("com.oracle.svm.test.debug.helper.PrettyPrinterTest::testArray") gdb_run() self.assertEqual(self.svm_command_print.complete("oa[3]", ""), gdb.COMPLETE_NONE) @@ -265,23 +263,21 @@ def test_svm_selfref_check(self): self.assertGreater(len(exec_string1), len(exec_string2)) def test_svm_print_static_fields(self): - gdb_set_breakpoint("com.oracle.svm.test.debug.helper.PrettyPrinterTest::testArrayList") + gdb_set_breakpoint("com.oracle.svm.test.debug.helper.PrettyPrinterTest::testObject") gdb_run() gdb_set_param('svm-print-static-fields', 'on') - gdb_set_param('svm-use-hlrep', 'off') - self.assertIn('DEFAULT_CAPACITY', gdb_output("strList")) + self.assertIn('s1 =', gdb_output("object")) gdb_set_param('svm-print-static-fields', 'off') - self.assertNotIn('DEFAULT_CAPACITY', gdb_output("strList")) + self.assertNotIn('s1 =', gdb_output("object")) def test_svm_complete_static_variables(self): svm_command_print = SVMCommandPrint() - gdb_set_breakpoint("com.oracle.svm.test.debug.helper.PrettyPrinterTest::testArrayList") + gdb_set_breakpoint("com.oracle.svm.test.debug.helper.PrettyPrinterTest::testObject") gdb_run() gdb_set_param('svm-complete-static-variables', 'on') - gdb_set_param('svm-use-hlrep', 'off') - self.assertIn('DEFAULT_CAPACITY', svm_command_print.complete("strList.", "")) + self.assertIn('s1', svm_command_print.complete("object.", "")) gdb_set_param('svm-complete-static-variables', 'off') - self.assertNotIn('DEFAULT_CAPACITY', svm_command_print.complete("strList.", "")) + self.assertNotIn('s1', svm_command_print.complete("object.", "")) # redirect unittest output to terminal diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_svm_util.py b/substratevm/src/com.oracle.svm.test.debug/src/gdb-tests/test_svm_util.py similarity index 100% rename from substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_svm_util.py rename to substratevm/src/com.oracle.svm.test.debug/src/gdb-tests/test_svm_util.py diff --git a/substratevm/mx.substratevm/testdeopt.js b/substratevm/src/com.oracle.svm.test.debug/src/gdb-tests/testdeopt.js similarity index 100% rename from substratevm/mx.substratevm/testdeopt.js rename to substratevm/src/com.oracle.svm.test.debug/src/gdb-tests/testdeopt.js diff --git a/substratevm/mx.substratevm/testhello.py b/substratevm/src/com.oracle.svm.test.debug/src/gdb-tests/testhello.py similarity index 99% rename from substratevm/mx.substratevm/testhello.py rename to substratevm/src/com.oracle.svm.test.debug/src/gdb-tests/testhello.py index b23351f67fc7..2fa511053e76 100644 --- a/substratevm/mx.substratevm/testhello.py +++ b/substratevm/src/com.oracle.svm.test.debug/src/gdb-tests/testhello.py @@ -46,7 +46,6 @@ # PYTHON_PATH which gdb needs to use to locate any imported code. # -import re import sys import os @@ -614,7 +613,7 @@ def test(): execute("delete breakpoints") exec_string = execute("break Hello.java:147") - rexp = fr"Breakpoint {digits_pattern} at {address_pattern}: (Hello\.java:147\. \(2 locations\)|file hello/Hello\.java, line {digits_pattern}\.)" + rexp = fr"Breakpoint {digits_pattern} at {address_pattern}: (Hello\.java:147\. \({digits_pattern} locations\)|file hello/Hello\.java, line {digits_pattern}\.)" checker = Checker('break Hello.java:147', rexp) checker.check(exec_string) @@ -859,7 +858,7 @@ def test(): execute("delete breakpoints") exec_string = execute("break " + lambda_name) - rexp = fr"Breakpoint {digits_pattern} at {address_pattern}: (file hello/Hello.java, line 221|hello.Hello::lambda($static)?${digits_pattern}. \({digits_pattern} locations\))" + rexp = fr"Breakpoint {digits_pattern} at {address_pattern}: (file hello/Hello\.java, line 221|hello\.Hello::lambda(\$static)?\${digits_pattern}. \({digits_pattern} locations\))" checker = Checker('break ' + lambda_name, rexp) checker.check(exec_string) @@ -872,7 +871,7 @@ def test(): execute("delete breakpoints") exec_string = execute("break Hello.java:222") - rexp = fr"Breakpoint {digits_pattern} at {address_pattern}: file hello/Hello.java, line 222" + rexp = fr"Breakpoint {digits_pattern} at {address_pattern}: (file hello/Hello\.java, line 222|Hello\.java:222\. \({digits_pattern} locations\))" checker = Checker('break Hello.java:222', rexp) checker.check(exec_string) diff --git a/substratevm/src/com.oracle.svm.test/src/hello/Hello.java b/substratevm/src/com.oracle.svm.test.debug/src/hello/Hello.java similarity index 100% rename from substratevm/src/com.oracle.svm.test/src/hello/Hello.java rename to substratevm/src/com.oracle.svm.test.debug/src/hello/Hello.java diff --git a/substratevm/src/com.oracle.svm.test/src/hello/Target_hello_Hello_DefaultGreeter.java b/substratevm/src/com.oracle.svm.test.debug/src/hello/Target_hello_Hello_DefaultGreeter.java similarity index 100% rename from substratevm/src/com.oracle.svm.test/src/hello/Target_hello_Hello_DefaultGreeter.java rename to substratevm/src/com.oracle.svm.test.debug/src/hello/Target_hello_Hello_DefaultGreeter.java diff --git a/substratevm/src/com.oracle.svm.test/src/systemjava_debugtest.h b/substratevm/src/com.oracle.svm.test.debug/src/systemjava_debugtest.h similarity index 100% rename from substratevm/src/com.oracle.svm.test/src/systemjava_debugtest.h rename to substratevm/src/com.oracle.svm.test.debug/src/systemjava_debugtest.h From e37702e32af29e29105f777360eccdfb07494cdb Mon Sep 17 00:00:00 2001 From: Dominik Mascherbauer Date: Mon, 11 Aug 2025 13:31:03 +0200 Subject: [PATCH 4/5] Do not run layereddebuginfotests for static builds. --- substratevm/mx.substratevm/mx_substratevm.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index a0e7132da63b..d466f2e1aebe 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -427,7 +427,8 @@ def svm_gate_body(args, tasks): if t: if mx.is_windows(): mx.warn('layereddebuginfotest does not work on Windows') - else: + # Running debuginfotest with layers does not work for static builds + elif '--static' not in args.extra_image_builder_arguments: with native_image_context(IMAGE_ASSERTION_FLAGS) as native_image: layereddebuginfotest(['--output-path', svmbuild_dir()] + args.extra_image_builder_arguments) From bf169b7f989a2c948f2da23c2dc947d649f4ca75 Mon Sep 17 00:00:00 2001 From: Dominik Mascherbauer Date: Wed, 3 Sep 2025 17:01:50 +0200 Subject: [PATCH 5/5] Incorporate feedback --- substratevm/mx.substratevm/mx_substratevm.py | 4 ++-- .../image/NativeImageDebugInfoProvider.java | 6 +++--- .../test/debug/helper/PrettyPrinterTest.java | 9 +++++++- .../src/gdb-tests/test_cinterface.py | 21 +++++++++++++++++++ 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index d466f2e1aebe..4d940e41be50 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -1069,16 +1069,16 @@ def build_layer(layer_path, layer_args): '-o', join(app_layer_path, app_layer_name), # We do not want to step into class initializer, so initialize everything at build time. '--initialize-at-build-time=hello', - 'hello.Hello' + 'hello.Hello', ] + svm_experimental_options([ f'-H:LayerUse={join(base_layer_path, base_layer_name)}.nil', + f'-H:LinkerRPath={base_layer_path}' ])) # prepare environment env = os.environ.copy() env['debuginfotest_isolates'] = 'yes' env['debuginfotest_layered'] = 'yes' - env['LD_LIBRARY_PATH'] = base_layer_path # fetch python test file testhello_py = join(sourcepath, 'gdb-tests', 'testhello.py') diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java index 12546999ca43..9560fc8c0d12 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java @@ -589,14 +589,14 @@ private void processFieldEntries(HostedType type, StructureTypeEntry structureTy } for (ResolvedJavaField field : type.getStaticFields()) { - assert field instanceof HostedField; + HostedField hField = (HostedField) field; /* * If we are in a layered build only add debug info for a static field if it is * installed in the current layer. */ if (!ImageLayerBuildingSupport.buildingImageLayer() || - (((HostedField) field).hasInstalledLayerNum() && DynamicImageLayerInfo.getCurrentLayerNumber() == StaticFieldsSupport.getInstalledLayerNum(field))) { - structureTypeEntry.addField(createFieldEntry((HostedField) field, structureTypeEntry)); + (hField.hasInstalledLayerNum() && DynamicImageLayerInfo.getCurrentLayerNumber() == hField.getInstalledLayerNum())) { + structureTypeEntry.addField(createFieldEntry(hField, structureTypeEntry)); } } } diff --git a/substratevm/src/com.oracle.svm.test.debug/src/com/oracle/svm/test/debug/helper/PrettyPrinterTest.java b/substratevm/src/com.oracle.svm.test.debug/src/com/oracle/svm/test/debug/helper/PrettyPrinterTest.java index df25a2d746eb..3b245b77745e 100644 --- a/substratevm/src/com.oracle.svm.test.debug/src/com/oracle/svm/test/debug/helper/PrettyPrinterTest.java +++ b/substratevm/src/com.oracle.svm.test.debug/src/com/oracle/svm/test/debug/helper/PrettyPrinterTest.java @@ -154,9 +154,17 @@ static void testLambda(Function lambda) { lambda.apply("test"); } + @NeverInline("Prevent constant folding") + private static boolean alwaysFalse() { + return false; + } + @SuppressWarnings("unused") @NeverInline("For testing purposes") static void testClassType(Class clazz, Holder dyn) { + if (alwaysFalse()) { + staticHolder = null; // Prevent constant folding of staticHolder. + } blackhole(clazz, dyn, staticHolder.c, staticHolder.o); } @@ -197,6 +205,5 @@ public static void main(String[] args) { testLambda(str -> str); testClassType(String.class, new Holder(String.class, String.class)); - staticHolder = null; } } diff --git a/substratevm/src/com.oracle.svm.test.debug/src/gdb-tests/test_cinterface.py b/substratevm/src/com.oracle.svm.test.debug/src/gdb-tests/test_cinterface.py index 0d6282593a82..5abe70d8d209 100644 --- a/substratevm/src/com.oracle.svm.test.debug/src/gdb-tests/test_cinterface.py +++ b/substratevm/src/com.oracle.svm.test.debug/src/gdb-tests/test_cinterface.py @@ -100,6 +100,27 @@ def test_auto_reload(self): self.assertIn('SubstrateVM FrameFilter', exec_string) +class TestSVMFrameDecorator(unittest.TestCase): + @classmethod + def setUp(cls): + cls.maxDiff = None + set_up_test() + gdb_start() + set_up_gdb_debughelpers() + + @classmethod + def tearDown(cls): + gdb_delete_breakpoints() + gdb_kill() + + def test_object_file_names_in_backtrace(self): + gdb_set_breakpoint("com.oracle.svm.tutorial.CInterfaceTutorial::releaseData") + gdb_continue() + backtrace = gdb_execute("backtrace") + self.assertIn('in cinterfacetutorial', backtrace) + self.assertIn('in libcinterfacetutorial.so', backtrace) + + class TestCInterface(unittest.TestCase): @classmethod def setUp(cls):