From c76cbc5e4eb3c88aa5e05e7b1f820b3c400810a9 Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Wed, 26 Jun 2024 11:04:33 +0200
Subject: [PATCH 01/34] Implementation for GDB JIT compilation interface

---
 .../svm/core/debug/GDBJITInterface.java       | 68 +++++++++++++++++++
 .../include/gdbJITCompilationInterface.h      | 42 ++++++++++++
 .../src/gdbJITCompilationInterface.c          | 66 ++++++++++++++++++
 3 files changed, 176 insertions(+)
 create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterface.java
 create mode 100644 substratevm/src/com.oracle.svm.native.debug/include/gdbJITCompilationInterface.h
 create mode 100644 substratevm/src/com.oracle.svm.native.debug/src/gdbJITCompilationInterface.c

diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterface.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterface.java
new file mode 100644
index 000000000000..bbc6c174152b
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterface.java
@@ -0,0 +1,68 @@
+package com.oracle.svm.core.debug;
+
+import com.oracle.svm.core.SubstrateOptions;
+import com.oracle.svm.core.c.ProjectHeaderFile;
+import org.graalvm.nativeimage.c.CContext;
+import org.graalvm.nativeimage.c.function.CFunction;
+import org.graalvm.nativeimage.c.struct.CField;
+import org.graalvm.nativeimage.c.struct.CStruct;
+import org.graalvm.nativeimage.c.type.CCharPointer;
+import org.graalvm.nativeimage.c.type.CUnsigned;
+import org.graalvm.word.PointerBase;
+
+import java.util.Collections;
+import java.util.List;
+
+@CContext(GDBJITInterface.GDBJITInterfaceDirectives.class)
+public class GDBJITInterface {
+
+    public static class GDBJITInterfaceDirectives implements CContext.Directives {
+        @Override
+        public boolean isInConfiguration() {
+            return SubstrateOptions.RuntimeDebugInfo.getValue();
+        }
+
+        @Override
+        public List<String> getHeaderFiles() {
+            return Collections.singletonList(ProjectHeaderFile.resolve("com.oracle.svm.native.debug", "include/gdbJITCompilationInterface.h"));
+        }
+
+        @Override
+        public List<String> getLibraries() {
+            return Collections.singletonList("debug");
+        }
+    }
+
+    @CStruct(value = "jit_code_entry", addStructKeyword = true)
+    public interface JITCodeEntry extends PointerBase {
+        // struct jit_code_entry *next_entry;
+        @CField("next_entry")
+        JITCodeEntry getNextEntry();
+        @CField("next_entry")
+        void setNextEntry(JITCodeEntry jitCodeEntry);
+
+        // struct jit_code_entry *prev_entry;
+        @CField("prev_entry")
+        JITCodeEntry getPrevEntry();
+        @CField("prev_entry")
+        void setPrevEntry(JITCodeEntry jitCodeEntry);
+
+        // const char *symfile_addr;
+        @CField("symfile_addr")
+        CCharPointer getSymfileAddr();
+        @CField("symfile_addr")
+        void setSymfileAddr(CCharPointer symfileAddr);
+
+        // uint64_t symfile_size;
+        @CField("symfile_size")
+        @CUnsigned long getSymfileSize();
+        @CField("symfile_size")
+        void setSymfileSize(@CUnsigned long symfileSize);
+    }
+
+    @CFunction(value = "register_jit_code", transition = CFunction.Transition.NO_TRANSITION)
+    public static native JITCodeEntry registerJITCode(CCharPointer addr, @CUnsigned long size);
+
+    @CFunction(value = "unregister_jit_code", transition = CFunction.Transition.NO_TRANSITION)
+    public static native void unregisterJITCode(JITCodeEntry entry);
+}
diff --git a/substratevm/src/com.oracle.svm.native.debug/include/gdbJITCompilationInterface.h b/substratevm/src/com.oracle.svm.native.debug/include/gdbJITCompilationInterface.h
new file mode 100644
index 000000000000..b61d31757dc8
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.native.debug/include/gdbJITCompilationInterface.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
+ */
+
+#ifndef SVM_NATIVE_GDBJITCOMPILATIONINTERFACE_H
+#define SVM_NATIVE_GDBJITCOMPILATIONINTERFACE_H
+
+#include <stdint.h>
+
+typedef enum
+{
+  JIT_NOACTION = 0,
+  JIT_REGISTER_FN,
+  JIT_UNREGISTER_FN
+} jit_actions_t;
+
+struct jit_code_entry
+{
+  struct jit_code_entry *next_entry;
+  struct jit_code_entry *prev_entry;
+  const char *symfile_addr;
+  uint64_t symfile_size;
+};
+
+struct jit_descriptor
+{
+  uint32_t version;
+  /* This type should be jit_actions_t, but we use uint32_t
+     to be explicit about the bitwidth.  */
+  uint32_t action_flag;
+  struct jit_code_entry *relevant_entry;
+  struct jit_code_entry *first_entry;
+};
+
+/* GDB puts a breakpoint in this function.  */
+void __attribute__((noinline)) __jit_debug_register_code() { };
+
+struct jit_code_entry *register_jit_code(const char *addr, uint64_t size);
+void unregister_jit_code(struct jit_code_entry *const entry);
+
+#endif
diff --git a/substratevm/src/com.oracle.svm.native.debug/src/gdbJITCompilationInterface.c b/substratevm/src/com.oracle.svm.native.debug/src/gdbJITCompilationInterface.c
new file mode 100644
index 000000000000..27f8a78ded60
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.native.debug/src/gdbJITCompilationInterface.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
+ */
+
+#include <gdbJITCompilationInterface.h>
+
+#include <stdlib.h>
+#include <assert.h>
+
+
+/* Make sure to specify the version statically, because the
+   debugger may check the version before we can set it.  */
+struct jit_descriptor __jit_debug_descriptor = { 1, 0, 0, 0 };
+
+
+struct jit_code_entry *register_jit_code(const char *addr, uint64_t size)
+{
+    /* Create new jit_code_entry */
+    struct jit_code_entry *const entry = malloc(sizeof(*entry));
+    entry->symfile_addr = addr;
+    entry->symfile_size = size;
+
+	/* Insert entry at head of the list. */
+	struct jit_code_entry *const next_entry = __jit_debug_descriptor.first_entry;
+	entry->prev_entry = NULL;
+	entry->next_entry = next_entry;
+
+	if (next_entry != NULL) {
+	    next_entry->prev_entry = entry;
+        }
+
+	/* Notify GDB.  */
+	__jit_debug_descriptor.action_flag = JIT_REGISTER_FN;
+	__jit_debug_descriptor.first_entry = entry;
+	__jit_debug_descriptor.relevant_entry = entry;
+	__jit_debug_register_code();
+
+    return entry;
+}
+
+
+void unregister_jit_code(struct jit_code_entry *const entry)
+{
+    struct jit_code_entry *const prev_entry = entry->prev_entry;
+    struct jit_code_entry *const next_entry = entry->next_entry;
+
+    /* Fix prev and next in list */
+    if (next_entry != NULL) {
+	next_entry->prev_entry = prev_entry;
+    }
+
+    if (prev_entry != NULL) {
+	prev_entry->next_entry = next_entry;
+    } else {
+	assert(__jit_debug_descriptor.first_entry == entry);
+	__jit_debug_descriptor.first_entry = next_entry;
+    }
+
+    /* Notify GDB.  */
+    __jit_debug_descriptor.action_flag = JIT_UNREGISTER_FN;
+    __jit_debug_descriptor.relevant_entry = entry;
+    __jit_debug_register_code();
+
+    free(entry);
+}

From 413c159b2b471d0805bae7da8f27cdf077af1d17 Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Wed, 26 Jun 2024 11:04:33 +0200
Subject: [PATCH 02/34] Add a LocalVariableTable to SubstrateMethod

---
 .../GraalGraphObjectReplacer.java             | 23 ++++++++++++++++++-
 .../svm/graal/meta/SubstrateMethod.java       |  7 ++++--
 2 files changed, 27 insertions(+), 3 deletions(-)

diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/GraalGraphObjectReplacer.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/GraalGraphObjectReplacer.java
index af1702a2dbb3..c40a6bcbec15 100644
--- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/GraalGraphObjectReplacer.java
+++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/GraalGraphObjectReplacer.java
@@ -29,6 +29,8 @@
 import java.util.concurrent.ConcurrentMap;
 import java.util.function.Function;
 
+import jdk.vm.ci.meta.Local;
+import jdk.vm.ci.meta.LocalVariableTable;
 import org.graalvm.nativeimage.c.function.RelocatedPointer;
 import org.graalvm.nativeimage.hosted.Feature.BeforeHeapLayoutAccess;
 
@@ -279,7 +281,7 @@ public synchronized SubstrateMethod createMethod(ResolvedJavaMethod original) {
                  * The links to other meta objects must be set after adding to the methods to avoid
                  * infinite recursion.
                  */
-                sMethod.setLinks(createSignature(aMethod.getSignature()), createType(aMethod.getDeclaringClass()));
+                sMethod.setLinks(createSignature(aMethod.getSignature()), createType(aMethod.getDeclaringClass()), createLocalVariableTable(aMethod.getLocalVariableTable()));
             }
         }
         return sMethod;
@@ -408,6 +410,25 @@ private synchronized SubstrateSignature createSignature(ResolvedSignature<Analys
         return sSignature;
     }
 
+    private synchronized LocalVariableTable createLocalVariableTable(LocalVariableTable original) {
+        if (original == null) {
+            return null;
+        }
+        try {
+            Local[] origLocals = original.getLocals();
+            Local[] newLocals = new Local[origLocals.length];
+            for (int i = 0; i < newLocals.length; ++i) {
+                Local origLocal = origLocals[i];
+                JavaType origType = origLocal.getType();
+                SubstrateType newType = createType(origType);
+                newLocals[i] = new Local(origLocal.getName(), newType, origLocal.getStartBCI(), origLocal.getEndBCI(), origLocal.getSlot());
+            }
+            return new LocalVariableTable(newLocals);
+        } catch (UnsupportedFeatureException e) {
+            return null;
+        }
+    }
+
     /**
      * Collect {@link SubstrateMethod} implementations.
      */
diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateMethod.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateMethod.java
index faff3535fd0d..995d2fb571ac 100644
--- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateMethod.java
+++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateMethod.java
@@ -87,6 +87,8 @@ public class SubstrateMethod implements SharedRuntimeMethod {
     private final String name;
     private final int hashCode;
     private SubstrateType declaringClass;
+    private int encodedGraphStartOffset;
+    private LocalVariableTable localVariableTable;
     @UnknownPrimitiveField(availability = ReadyForCompilation.class) private int encodedGraphStartOffset;
     @UnknownPrimitiveField(availability = AfterHeapLayout.class) private int vTableIndex;
 
@@ -175,9 +177,10 @@ public int hashCode() {
         return hashCode;
     }
 
-    public void setLinks(SubstrateSignature signature, SubstrateType declaringClass) {
+    public void setLinks(SubstrateSignature signature, SubstrateType declaringClass, LocalVariableTable localVariableTable) {
         this.signature = signature;
         this.declaringClass = declaringClass;
+        this.localVariableTable = localVariableTable;
     }
 
     public void setImplementations(SubstrateMethod[] rawImplementations) {
@@ -445,7 +448,7 @@ public LineNumberTable getLineNumberTable() {
 
     @Override
     public LocalVariableTable getLocalVariableTable() {
-        return null;
+        return localVariableTable;
     }
 
     @Override

From 8ebd7607f2a7d7049d21a224c9e65bc63b0c2646 Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Wed, 26 Jun 2024 11:04:33 +0200
Subject: [PATCH 03/34] Create a test to manually trigger runtime compilation

---
 substratevm/mx.substratevm/mx_substratevm.py  |  35 ++
 substratevm/mx.substratevm/suite.py           |   4 +
 .../debug/RuntimeCompileDebugInfoTest.java    | 302 ++++++++++++++++++
 3 files changed, 341 insertions(+)
 create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/RuntimeCompileDebugInfoTest.java

diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py
index 551ceff88ae3..6f214450a97b 100644
--- a/substratevm/mx.substratevm/mx_substratevm.py
+++ b/substratevm/mx.substratevm/mx_substratevm.py
@@ -856,6 +856,32 @@ def _cinterfacetutorial(native_image, args=None):
     mx.run([join(build_dir, 'cinterfacetutorial')])
 
 
+def _runtimedebuginfotest(native_image, args=None):
+    """Build and run the runtimedebuginfotest"""
+
+    args = [] if args is None else args
+    build_dir = join(svmbuild_dir(), 'runtimedebuginfotest')
+
+    # clean / create output directory
+    if exists(build_dir):
+        mx.rmtree(build_dir)
+    mx_util.ensure_dir_exists(build_dir)
+
+    # Build the native image from Java code
+    native_image([
+                     '-g', '-O0', '--no-fallback',
+                     '-o', join(build_dir, 'runtimedebuginfotest'),
+                     '-cp', classpath('com.oracle.svm.test'),
+                     '--features=com.oracle.svm.test.debug.RuntimeCompileDebugInfoTest$RegisterMethodsFeature',
+                     'com.oracle.svm.test.debug.RuntimeCompileDebugInfoTest',
+                 ] + svm_experimental_options([
+        '-H:DebugInfoSourceSearchPath=' + mx.project('com.oracle.svm.test').source_dirs()[0],
+        ]) + args)
+
+    # Start the native image
+    mx.run([join(build_dir, 'runtimedebuginfotest')])
+
+
 _helloworld_variants = {
     'traditional': '''
 public class HelloWorld {
@@ -1817,6 +1843,14 @@ def cinterfacetutorial(args):
     native_image_context_run(_cinterfacetutorial, args)
 
 
+@mx.command(suite.name, 'runtimedebuginfotest', 'Runs the runtime debuginfo generation test')
+def runtimedebuginfotest(args):
+    """
+    runs a native image that compiles code and creates debug info at runtime.
+    """
+    native_image_context_run(_runtimedebuginfotest, args)
+
+
 @mx.command(suite.name, 'javaagenttest', 'Runs tests for java agent with native image')
 def java_agent_test(args):
     def build_and_run(args, binary_path, native_image, agents, agents_arg):
@@ -1855,6 +1889,7 @@ def build_and_test_java_agent_image(native_image, args):
 
     native_image_context_run(build_and_test_java_agent_image, args)
 
+
 @mx.command(suite.name, 'clinittest', 'Runs the ')
 def clinittest(args):
     def build_and_test_clinittest_image(native_image, args):
diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py
index b035719dcd8b..81ab6bc892dc 100644
--- a/substratevm/mx.substratevm/suite.py
+++ b/substratevm/mx.substratevm/suite.py
@@ -1077,6 +1077,10 @@
                     "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",
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/RuntimeCompileDebugInfoTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/RuntimeCompileDebugInfoTest.java
new file mode 100644
index 000000000000..8031d1d22228
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/RuntimeCompileDebugInfoTest.java
@@ -0,0 +1,302 @@
+package com.oracle.svm.test.debug;
+
+import com.oracle.svm.core.NeverInline;
+import com.oracle.svm.core.c.InvokeJavaFunctionPointer;
+import com.oracle.svm.graal.SubstrateGraalUtils;
+import com.oracle.svm.graal.hosted.runtimecompilation.RuntimeCompilationFeature;
+import com.oracle.svm.graal.meta.SubstrateMethod;
+import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl;
+import com.oracle.svm.util.ModuleSupport;
+import jdk.vm.ci.code.InstalledCode;
+import org.graalvm.nativeimage.ImageSingletons;
+import org.graalvm.nativeimage.VMRuntime;
+import org.graalvm.nativeimage.c.function.CFunctionPointer;
+import org.graalvm.nativeimage.hosted.Feature;
+import org.graalvm.nativeimage.hosted.RuntimeClassInitialization;
+import org.graalvm.word.WordFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.oracle.svm.core.util.VMError.shouldNotReachHere;
+
+class RuntimeCompilations {
+
+    Integer a;
+    int b;
+    String c;
+    Object d;
+
+    static Integer sa = 42;
+    static String sb;
+    static Object sc;
+
+    RuntimeCompilations(int a) {
+        this.a = a;
+    }
+
+    @NeverInline("For testing")
+    private void breakHere() {}
+
+    @NeverInline("For testing")
+    public Integer paramMethod(int param1, String param2, Object param3) {
+        breakHere();
+        b = param1;
+        breakHere();
+        c = param2;
+        breakHere();
+        d = param3;
+        breakHere();
+        return a;
+    }
+
+    @NeverInline("For testing")
+    public Integer noParamMethod() {
+        return a;
+    }
+
+    @NeverInline("For testing")
+    public void voidMethod() {
+        a *= 2;
+    }
+
+    @NeverInline("For testing")
+    public int primitiveReturnMethod(int param1) {
+        a = param1;
+        return param1;
+    }
+
+    @NeverInline("For testing")
+    public Integer[] arrayMethod(int[] param1, String[] param2) {
+        a = param1[0];
+        return new Integer[] { a };
+    }
+
+    @NeverInline("For testing")
+    public float localsMethod(String param1) {
+        float f = 1.5f;
+        String x = param1;
+        byte[] bytes = x.getBytes();
+        return f;
+    }
+
+    @NeverInline("For testing")
+    public static Integer staticMethod(int param1, String param2, Object param3) {
+        sa = param1;
+        sb = param2;
+        sc = param3;
+        return sa;
+    }
+}
+
+public class RuntimeCompileDebugInfoTest {
+    static class RuntimeHolder {
+        SubstrateMethod testMethod1;
+        SubstrateMethod testMethod2;
+        SubstrateMethod testMethod3;
+        SubstrateMethod testMethod4;
+        SubstrateMethod testMethod5;
+        SubstrateMethod testMethod6;
+        SubstrateMethod testMethod7;
+    }
+
+    public static class RegisterMethodsFeature implements Feature {
+        RegisterMethodsFeature() {
+            ModuleSupport.accessPackagesToClass(ModuleSupport.Access.EXPORT, RegisterMethodsFeature.class, false,
+                    "org.graalvm.nativeimage.builder",
+                    "com.oracle.svm.graal", "com.oracle.svm.graal.hosted.runtimecompilation", "com.oracle.svm.hosted");
+            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");
+        }
+
+        @Override
+        public List<Class<? extends Feature>> getRequiredFeatures() {
+            return List.of(RuntimeCompilationFeature.class);
+        }
+
+        @Override
+        public void beforeAnalysis(BeforeAnalysisAccess a) {
+            BeforeAnalysisAccessImpl config = (BeforeAnalysisAccessImpl) a;
+
+            RuntimeHolder holder = new RuntimeHolder();
+            RuntimeClassInitialization.initializeAtBuildTime(RuntimeHolder.class);
+            ImageSingletons.add(RuntimeHolder.class, holder);
+
+            RuntimeCompilationFeature runtimeCompilationFeature = RuntimeCompilationFeature.singleton();
+            runtimeCompilationFeature.initializeRuntimeCompilationForTesting(config);
+
+            holder.testMethod1 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "paramMethod", int.class, String.class, Object.class);
+            holder.testMethod2 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "noParamMethod");
+            holder.testMethod3 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "voidMethod");
+            holder.testMethod4 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "primitiveReturnMethod", int.class);
+            holder.testMethod5 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "arrayMethod", int[].class, String[].class);
+            holder.testMethod6 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "localsMethod", String.class);
+            holder.testMethod7 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "staticMethod", int.class, String.class, Object.class);
+        }
+
+        private SubstrateMethod prepareMethodForRuntimeCompilation(BeforeAnalysisAccessImpl config, RuntimeCompilationFeature runtimeCompilationFeature, Class<?> holder, String methodName, Class<?>... signature) {
+            RuntimeClassInitialization.initializeAtBuildTime(holder);
+            try {
+                return runtimeCompilationFeature.prepareMethodForRuntimeCompilation(holder.getMethod(methodName, signature), config);
+            } catch (NoSuchMethodException ex) {
+                throw shouldNotReachHere(ex);
+            }
+        }
+    }
+
+
+    interface TestFunctionPointer extends CFunctionPointer {
+        @InvokeJavaFunctionPointer
+        Integer invoke(Object receiver, int arg1, String arg2, Object arg3);
+        @InvokeJavaFunctionPointer
+        Object invoke(Object receiver);
+        @InvokeJavaFunctionPointer
+        int invoke(Object receiver, int arg1);
+        @InvokeJavaFunctionPointer
+        Integer[] invoke(Object receiver, int[] arg1, String[] arg2);
+        @InvokeJavaFunctionPointer
+        float invoke(Object receiver, String arg1);
+        @InvokeJavaFunctionPointer
+        Integer invoke(int arg1, String arg2, Object arg3);
+    }
+
+    private static RuntimeHolder getHolder() {
+        return ImageSingletons.lookup(RuntimeHolder.class);
+    }
+
+    private static Integer invoke(TestFunctionPointer functionPointer, Object receiver, int arg1, String arg2, Object arg3) {
+        return functionPointer.invoke(receiver, arg1, arg2, arg3);
+    }
+
+    private static Object invoke(TestFunctionPointer functionPointer, Object receiver) {
+        return functionPointer.invoke(receiver);
+    }
+
+    private static int invoke(TestFunctionPointer functionPointer, Object receiver, int arg1) {
+        return functionPointer.invoke(receiver, arg1);
+    }
+
+    private static Integer[] invoke(TestFunctionPointer functionPointer, Object receiver, int[] arg1, String[] arg2) {
+        return functionPointer.invoke(receiver, arg1, arg2);
+    }
+
+    private static float invoke(TestFunctionPointer functionPointer, Object receiver, String arg1) {
+        return functionPointer.invoke(receiver, arg1);
+    }
+
+    private static Integer invoke(TestFunctionPointer functionPointer, Integer arg1, String arg2, Object arg3) {
+        return functionPointer.invoke(arg1, arg2, arg3);
+    }
+
+    private static TestFunctionPointer getFunctionPointer(InstalledCode installedCode) {
+        return WordFactory.pointer(installedCode.getEntryPoint());
+    }
+
+    public static void testParams() {
+
+        InstalledCode installedCode = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod1);
+        InstalledCode installedCodeStatic = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod7);
+
+        RuntimeCompilations x = new RuntimeCompilations(11);
+        int param1 = 27;
+        String param2 = "test";
+        Object param3 = new ArrayList<>();
+
+        Integer result = invoke(getFunctionPointer(installedCode), x, param1, param2, param3);
+        Integer resultStatic = invoke(getFunctionPointer(installedCodeStatic), param1, param2, param3);
+
+        installedCode.invalidate();
+        installedCodeStatic.invalidate();
+    }
+
+    public static void testNoParam() {
+
+        InstalledCode installedCode = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod2);
+
+        RuntimeCompilations x = new RuntimeCompilations(11);
+
+        Integer result = (Integer) invoke(getFunctionPointer(installedCode), x);
+
+        installedCode.invalidate();
+    }
+
+    public static void testVoid() {
+
+        InstalledCode installedCode = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod3);
+
+        RuntimeCompilations x = new RuntimeCompilations(11);
+
+        invoke(getFunctionPointer(installedCode), x);
+
+        installedCode.invalidate();
+    }
+
+    public static void testPrimitiveReturn() {
+
+        InstalledCode installedCode = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod4);
+
+        RuntimeCompilations x = new RuntimeCompilations(11);
+        int param1 = 42;
+
+        int result = invoke(getFunctionPointer(installedCode), x, param1);
+
+        installedCode.invalidate();
+    }
+
+    public static void testArray() {
+
+        InstalledCode installedCode = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod5);
+
+        RuntimeCompilations x = new RuntimeCompilations(11);
+        int[] param1 = new int[] {1,2,3,4};
+        String[] param2 = new String[] {"this", "is", "an", "array"};
+
+        Integer[] result = invoke(getFunctionPointer(installedCode), x, param1, param2);
+
+        installedCode.invalidate();
+    }
+
+    public static void testLocals() {
+
+        InstalledCode installedCode = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod6);
+
+        RuntimeCompilations x = new RuntimeCompilations(11);
+        String param1 = "test";
+
+        float result = invoke(getFunctionPointer(installedCode), x, param1);
+
+        installedCode.invalidate();
+    }
+
+    public static void testAOTCompiled() {
+
+        RuntimeCompilations x = new RuntimeCompilations(11);
+        int param1 = 27;
+        String param2 = "test";
+        Object param3 = new ArrayList<>();
+
+        Integer result = x.paramMethod(param1, param2, param3);
+        Integer result2 = RuntimeCompilations.staticMethod(param1, param2, param3);
+        Integer result3 = x.noParamMethod();
+    }
+
+    public static void main(String[] args) {
+        /* Run startup hooks to, e.g., install the segfault handler so that we get crash reports. */
+        VMRuntime.initialize();
+
+        // aot compiled code for comparing generated debug infos
+        testAOTCompiled();
+
+        // use runtime compiled code
+        testParams();
+        testNoParam();
+        testVoid();
+        testPrimitiveReturn();
+        testArray();
+        testLocals();
+    }
+}

From fc3bfe67cdf6c5c0daa470356e183ba94cb8e954 Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Mon, 29 Jul 2024 16:19:34 +0200
Subject: [PATCH 04/34] Create Initial Runtime Debug Info Implementation

---
 substratevm/mx.substratevm/suite.py           |   27 +
 .../src/com/oracle/objectfile/ObjectFile.java |    8 +-
 .../objectfile/debugentry/ClassEntry.java     |   10 +-
 .../objectfile/debugentry/DebugInfoBase.java  |    8 +-
 .../debugentry/ForeignTypeEntry.java          |    5 +
 .../objectfile/debugentry/MethodEntry.java    |   12 +-
 .../debugentry/StructureTypeEntry.java        |    2 +-
 .../oracle/objectfile/elf/ELFObjectFile.java  |   59 +
 .../elf/dwarf/DwarfAbbrevSectionImpl.java     |   21 +
 .../objectfile/elf/dwarf/DwarfDebugInfo.java  |    1 +
 .../runtime/RuntimeDebugInfoBase.java         |  595 +++++++
 .../runtime/RuntimeDebugInfoProvider.java     |  353 ++++
 .../debugentry/CompiledMethodEntry.java       |  164 ++
 .../runtime/debugentry/DirEntry.java          |   52 +
 .../runtime/debugentry/FileEntry.java         |   82 +
 .../runtime/debugentry/MemberEntry.java       |  112 ++
 .../runtime/debugentry/MethodEntry.java       |  380 +++++
 .../debugentry/PrimitiveTypeEntry.java        |  102 ++
 .../runtime/debugentry/StringEntry.java       |   89 +
 .../runtime/debugentry/StringTable.java       |  103 ++
 .../debugentry/StructureTypeEntry.java        |   72 +
 .../runtime/debugentry/TypeEntry.java         |  105 ++
 .../runtime/debugentry/TypedefEntry.java      |   18 +
 .../runtime/debugentry/range/CallRange.java   |   73 +
 .../runtime/debugentry/range/LeafRange.java   |   50 +
 .../debugentry/range/PrimaryRange.java        |   77 +
 .../runtime/debugentry/range/Range.java       |  301 ++++
 .../runtime/debugentry/range/SubRange.java    |  131 ++
 .../dwarf/RuntimeDwarfAbbrevSectionImpl.java  |  344 ++++
 .../runtime/dwarf/RuntimeDwarfDebugInfo.java  |  525 ++++++
 .../dwarf/RuntimeDwarfFrameSectionImpl.java   |  303 ++++
 .../RuntimeDwarfFrameSectionImplAArch64.java  |  113 ++
 .../RuntimeDwarfFrameSectionImplX86_64.java   |  112 ++
 .../dwarf/RuntimeDwarfInfoSectionImpl.java    |  601 +++++++
 .../dwarf/RuntimeDwarfLineSectionImpl.java    |  791 +++++++++
 .../dwarf/RuntimeDwarfLocSectionImpl.java     |  608 +++++++
 .../dwarf/RuntimeDwarfSectionImpl.java        |  795 +++++++++
 .../dwarf/RuntimeDwarfStrSectionImpl.java     |   79 +
 .../runtime/dwarf/constants/DwarfAccess.java  |   46 +
 .../dwarf/constants/DwarfAttribute.java       |   81 +
 .../dwarf/constants/DwarfEncoding.java        |   52 +
 .../constants/DwarfExpressionOpcode.java      |   62 +
 .../runtime/dwarf/constants/DwarfFlag.java    |   46 +
 .../runtime/dwarf/constants/DwarfForm.java    |   66 +
 .../dwarf/constants/DwarfFrameValue.java      |   68 +
 .../dwarf/constants/DwarfHasChildren.java     |   45 +
 .../runtime/dwarf/constants/DwarfInline.java  |   50 +
 .../dwarf/constants/DwarfLanguage.java        |   45 +
 .../dwarf/constants/DwarfLineOpcode.java      |  113 ++
 .../dwarf/constants/DwarfSectionName.java     |   53 +
 .../runtime/dwarf/constants/DwarfTag.java     |   62 +
 .../runtime/dwarf/constants/DwarfVersion.java |   51 +
 .../com/oracle/svm/core/SubstrateOptions.java |   16 +
 .../core/debug/SubstrateDebugInfoFeature.java |   33 +
 .../debug/SubstrateDebugInfoInstaller.java    |  164 ++
 .../debug/SubstrateDebugInfoProvider.java     | 1444 +++++++++++++++++
 .../oracle/svm/graal/SubstrateGraalUtils.java |   22 +
 57 files changed, 9785 insertions(+), 17 deletions(-)
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/RuntimeDebugInfoBase.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/RuntimeDebugInfoProvider.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/CompiledMethodEntry.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/DirEntry.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/FileEntry.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/MemberEntry.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/MethodEntry.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/PrimitiveTypeEntry.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/StringEntry.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/StringTable.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/StructureTypeEntry.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/TypeEntry.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/TypedefEntry.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/CallRange.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/LeafRange.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/PrimaryRange.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/Range.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/SubRange.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfAbbrevSectionImpl.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfDebugInfo.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImpl.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImplAArch64.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImplX86_64.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfInfoSectionImpl.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfLineSectionImpl.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfLocSectionImpl.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfSectionImpl.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfStrSectionImpl.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfAccess.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfAttribute.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfEncoding.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfExpressionOpcode.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfFlag.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfForm.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfFrameValue.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfHasChildren.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfInline.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfLanguage.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfLineOpcode.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfSectionName.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfTag.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfVersion.java
 create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoFeature.java
 create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
 create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java

diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py
index 81ab6bc892dc..ab1c8e0e55c0 100644
--- a/substratevm/mx.substratevm/suite.py
+++ b/substratevm/mx.substratevm/suite.py
@@ -309,6 +309,7 @@
             "dependencies": [
                 "com.oracle.svm.common",
                 "com.oracle.svm.shaded.org.objectweb.asm",
+                "com.oracle.objectfile",
             ],
             "requires" : [
                 "java.compiler",
@@ -982,6 +983,28 @@
             "jacoco" : "exclude",
         },
 
+        "com.oracle.svm.native.debug": {
+            "native": "static_lib",
+            "subDir": "src",
+            "os_arch": {
+                "solaris": {
+                    "<others>": {
+                        "ignore": "solaris is not supported",
+                    },
+                },
+                "windows": {
+                    "<others>": {
+                        "cflags": ["-W4", "-O2", "-Zi"],
+                    },
+                },
+                "<others>": {
+                    "<others>": {
+                        "cflags": ["-Wall", "-fPIC", "-O2", "-g", "-gdwarf-4"],
+                    },
+                },
+            },
+        },
+
         "svm-jvmfuncs-fallback-builder": {
             "class" : "SubstrateJvmFuncsFallbacksBuilder",
         },
@@ -1869,6 +1892,7 @@
                 "com.oracle.objectfile.io",
                 "com.oracle.objectfile.debuginfo",
                 "com.oracle.objectfile.macho",
+                "com.oracle.objectfile.runtime",
               ],
 
               "requiresConcealed" : {
@@ -1998,6 +2022,7 @@
                             "dependency:com.oracle.svm.native.libchelper/*",
                             "dependency:com.oracle.svm.native.jvm.posix/*",
                             "dependency:com.oracle.svm.native.libcontainer/*",
+                            "dependency:com.oracle.svm.native.debug/*",
                         ],
                     },
                 },
@@ -2006,9 +2031,11 @@
                         # on all other os's we don't want libc specific subdirectories
                         "include/": [
                             "dependency:com.oracle.svm.native.libchelper/include/*",
+                            "dependency:com.oracle.svm.native.debug/include/*",
                         ],
                         "<os>-<arch>/": [
                             "dependency:com.oracle.svm.native.libchelper/<os>-<arch>/default/*",
+                            "dependency:com.oracle.svm.native.debug/<os>-<arch>/default/*",
                             "dependency:com.oracle.svm.native.jvm.posix/<os>-<arch>/default/*",
                             "dependency:com.oracle.svm.native.darwin/*",
                             "dependency:com.oracle.svm.native.jvm.windows/*",
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/ObjectFile.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/ObjectFile.java
index 734c93a025e1..d9709bf4bb04 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/ObjectFile.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/ObjectFile.java
@@ -46,6 +46,7 @@
 import java.util.function.Consumer;
 import java.util.stream.StreamSupport;
 
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider;
 import com.oracle.objectfile.debuginfo.DebugInfoProvider;
 import com.oracle.objectfile.elf.ELFObjectFile;
 import com.oracle.objectfile.macho.MachOObjectFile;
@@ -1180,6 +1181,11 @@ public void installDebugInfo(@SuppressWarnings("unused") DebugInfoProvider debug
         // do nothing by default
     }
 
+
+    public void installRuntimeDebugInfo(@SuppressWarnings("unused") RuntimeDebugInfoProvider runtimeDebugInfoProvider) {
+        // do nothing by default
+    }
+
     protected static Iterable<LayoutDecision> allDecisions(final Map<Element, LayoutDecisionMap> decisions) {
         return () -> StreamSupport.stream(decisions.values().spliterator(), false)
                         .flatMap(layoutDecisionMap -> StreamSupport.stream(layoutDecisionMap.spliterator(), false)).iterator();
@@ -1828,7 +1834,7 @@ public final SymbolTable getOrCreateSymbolTable() {
      * Temporary storage for a debug context installed in a nested scope under a call. to
      * {@link #withDebugContext}
      */
-    private DebugContext debugContext = null;
+    private DebugContext debugContext = DebugContext.disabled(null);
 
     /**
      * Allows a task to be executed with a debug context in a named subscope bound to the object
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
index f9747acaf638..b2d5f83d809a 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
@@ -268,7 +268,7 @@ protected MethodEntry processMethod(DebugMethodInfo debugMethodInfo, DebugInfoBa
         if (debugContext.isLogEnabled()) {
             String resultTypeName = resultType.toJavaName();
             debugContext.log("typename %s adding %s method %s %s(%s)%n",
-                            typeName, memberModifiers(modifiers), resultTypeName, methodName, formatParams(paramInfos));
+                    typeName, memberModifiers(modifiers), resultTypeName, methodName, formatParams(paramInfos));
         }
         TypeEntry resultTypeEntry = debugInfoBase.lookupTypeEntry(resultType);
         TypeEntry[] typeEntries = new TypeEntry[paramCount];
@@ -281,7 +281,7 @@ protected MethodEntry processMethod(DebugMethodInfo debugMethodInfo, DebugInfoBa
          */
         FileEntry methodFileEntry = debugInfoBase.ensureFileEntry(debugMethodInfo);
         MethodEntry methodEntry = new MethodEntry(debugInfoBase, debugMethodInfo, methodFileEntry, line, methodName,
-                        this, resultTypeEntry, typeEntries, paramInfos, thisParam);
+                this, resultTypeEntry, typeEntries, paramInfos, thisParam);
         indexMethodEntry(methodEntry, debugMethodInfo.idMethod());
 
         return methodEntry;
@@ -372,7 +372,7 @@ public int hipc() {
 
     /**
      * Add a file to the list of files referenced from info associated with this class.
-     * 
+     *
      * @param file The file to be added.
      */
     public void includeFile(FileEntry file) {
@@ -383,7 +383,7 @@ public void includeFile(FileEntry file) {
 
     /**
      * Add a directory to the list of firectories referenced from info associated with this class.
-     * 
+     *
      * @param dirEntry The directory to be added.
      */
     public void includeDir(DirEntry dirEntry) {
@@ -421,7 +421,7 @@ public void buildFileAndDirIndexes() {
     /**
      * Retrieve a stream of all files referenced from debug info for this class in line info file
      * table order, starting with the file at index 1.
-     * 
+     *
      * @return a stream of all referenced files
      */
     public Stream<FileEntry> fileStream() {
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java
index 47a68ad22445..d22dedee11f7 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java
@@ -506,7 +506,7 @@ private Range addSubrange(DebugLocationInfo locationInfo, PrimaryRange primaryRa
         DebugLocationInfo callerLocationInfo = locationInfo.getCaller();
         boolean isTopLevel = callerLocationInfo == null;
         assert (!isTopLevel || (locationInfo.name().equals(primaryRange.getMethodName()) &&
-                        locationInfo.ownerType().toJavaName().equals(primaryRange.getClassName())));
+                locationInfo.ownerType().toJavaName().equals(primaryRange.getClassName())));
         Range caller = (isTopLevel ? primaryRange : subRangeIndex.get(callerLocationInfo));
         // the frame tree is walked topdown so inline ranges should always have a caller range
         assert caller != null;
@@ -528,7 +528,7 @@ private Range addSubrange(DebugLocationInfo locationInfo, PrimaryRange primaryRa
         subRangeIndex.put(locationInfo, subRange);
         if (debugContext.isLogEnabled(DebugContext.DETAILED_LEVEL)) {
             debugContext.log(DebugContext.DETAILED_LEVEL, "SubRange %s.%s %d %s:%d [0x%x, 0x%x] (%d, %d)",
-                            ownerType.toJavaName(), methodName, subRange.getDepth(), fullPath, line, lo, hi, loOff, hiOff);
+                    ownerType.toJavaName(), methodName, subRange.getDepth(), fullPath, line, lo, hi, loOff, hiOff);
         }
         assert (callerLocationInfo == null || (callerLocationInfo.addressLo() <= loOff && callerLocationInfo.addressHi() >= hiOff)) : "parent range should enclose subrange!";
         DebugLocalValueInfo[] localValueInfos = locationInfo.getLocalValueInfo();
@@ -560,7 +560,7 @@ private boolean verifyMethodOrder(CompiledMethodEntry next) {
             PrimaryRange nextRange = next.getPrimary();
             if (lastRange.getHi() > nextRange.getLo()) {
                 assert false : "methods %s [0x%x, 0x%x] and %s [0x%x, 0x%x] presented out of order".formatted(lastRange.getFullMethodName(), lastRange.getLo(), lastRange.getHi(),
-                                nextRange.getFullMethodName(), nextRange.getLo(), nextRange.getHi());
+                        nextRange.getFullMethodName(), nextRange.getLo(), nextRange.getHi());
                 return false;
             }
         }
@@ -783,7 +783,7 @@ private static void collectFilesAndDirs(ClassEntry classEntry) {
     /**
      * Ensure the supplied file entry and associated directory entry are included, but only once, in
      * a class entry's file and dir list.
-     * 
+     *
      * @param classEntry the class entry whose file and dir list may need to be updated
      * @param fileEntry a file entry which may need to be added to the class entry's file list or
      *            whose dir may need adding to the class entry's dir list
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignTypeEntry.java
index dc3dae992f16..15612764d54d 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignTypeEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignTypeEntry.java
@@ -71,6 +71,11 @@ public void setLayoutTypeSignature(long typeSignature) {
     public void addDebugInfo(DebugInfoBase debugInfoBase, DebugTypeInfo debugTypeInfo, DebugContext debugContext) {
         assert debugTypeInfo instanceof DebugForeignTypeInfo;
         super.addDebugInfo(debugInfoBase, debugTypeInfo, debugContext);
+
+        // foreign pointers are never stored compressed so don't need a separate indirect type
+        this.indirectTypeSignature = typeSignature;
+        this.indirectLayoutTypeSignature = layoutTypeSignature;
+
         DebugForeignTypeInfo debugForeignTypeInfo = (DebugForeignTypeInfo) debugTypeInfo;
         this.typedefName = debugForeignTypeInfo.typedefName();
         if (debugForeignTypeInfo.isWord()) {
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 d583784898ad..b9dba7245613 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
@@ -57,8 +57,8 @@ public class MethodEntry extends MemberEntry {
 
     @SuppressWarnings("this-escape")
     public MethodEntry(DebugInfoBase debugInfoBase, DebugMethodInfo debugMethodInfo,
-                    FileEntry fileEntry, int line, String methodName, ClassEntry ownerType,
-                    TypeEntry valueType, TypeEntry[] paramTypes, DebugLocalInfo[] paramInfos, DebugLocalInfo thisParam) {
+                       FileEntry fileEntry, int line, String methodName, ClassEntry ownerType,
+                       TypeEntry valueType, TypeEntry[] paramTypes, DebugLocalInfo[] paramInfos, DebugLocalInfo thisParam) {
         super(fileEntry, line, methodName, ownerType, valueType, debugMethodInfo.modifiers());
         this.paramTypes = paramTypes;
         this.paramInfos = paramInfos;
@@ -240,7 +240,7 @@ public String getSymbolName() {
      * type equal those of the parameter. Values whose slot is in the local range will always
      * succeed,. either by matchign the slot and name of an existing local or by being recorded as a
      * new local variable.
-     * 
+     *
      * @param localValueInfo
      * @return the unique local variable with which this local value can be legitimately associated
      *         otherwise null.
@@ -335,7 +335,7 @@ private DebugLocalInfo matchLocal(DebugLocalValueInfo localValueInfo) {
                 int currentLine = next.line();
                 int newLine = localValueInfo.line();
                 if ((currentLine < 0 && newLine >= 0) ||
-                                (newLine >= 0 && newLine < currentLine)) {
+                        (newLine >= 0 && newLine < currentLine)) {
                     next.setLine(newLine);
                 }
                 return next;
@@ -354,8 +354,8 @@ private DebugLocalInfo matchLocal(DebugLocalValueInfo localValueInfo) {
 
     boolean checkMatch(DebugLocalInfo local, DebugLocalValueInfo value) {
         boolean isMatch = (local.slot() == value.slot() &&
-                        local.name().equals(value.name()) &&
-                        local.typeName().equals(value.typeName()));
+                local.name().equals(value.name()) &&
+                local.typeName().equals(value.typeName()));
         assert !isMatch || verifyMatch(local, value) : "failed to verify matched var and value";
         return isMatch;
     }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java
index 43473ca1cfb2..961692ee4c00 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java
@@ -88,7 +88,7 @@ protected FieldEntry addField(DebugFieldInfo debugFieldInfo, DebugInfoBase debug
         int fieldModifiers = debugFieldInfo.modifiers();
         if (debugContext.isLogEnabled()) {
             debugContext.log("typename %s adding %s field %s type %s%s size %s at offset 0x%x%n",
-                            typeName, memberModifiers(fieldModifiers), fieldName, valueTypeName, (fieldIsEmbedded ? "(embedded)" : ""), fieldSize, fieldoffset);
+                    typeName, memberModifiers(fieldModifiers), fieldName, valueTypeName, (fieldIsEmbedded ? "(embedded)" : ""), fieldSize, fieldoffset);
         }
         TypeEntry valueTypeEntry = debugInfoBase.lookupTypeEntry(valueType);
         /*
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/ELFObjectFile.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/ELFObjectFile.java
index e0af88f3fd07..bfe8cc4ebac5 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/ELFObjectFile.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/ELFObjectFile.java
@@ -36,6 +36,14 @@
 import java.util.Set;
 
 import com.oracle.objectfile.elf.dwarf.DwarfLocSectionImpl;
+import com.oracle.objectfile.runtime.dwarf.RuntimeDwarfAbbrevSectionImpl;
+import com.oracle.objectfile.runtime.dwarf.RuntimeDwarfDebugInfo;
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider;
+import com.oracle.objectfile.runtime.dwarf.RuntimeDwarfFrameSectionImpl;
+import com.oracle.objectfile.runtime.dwarf.RuntimeDwarfInfoSectionImpl;
+import com.oracle.objectfile.runtime.dwarf.RuntimeDwarfLineSectionImpl;
+import com.oracle.objectfile.runtime.dwarf.RuntimeDwarfLocSectionImpl;
+import com.oracle.objectfile.runtime.dwarf.RuntimeDwarfStrSectionImpl;
 import org.graalvm.nativeimage.ImageSingletons;
 import org.graalvm.nativeimage.Platform;
 
@@ -1180,6 +1188,7 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) {
         DwarfFrameSectionImpl frameSectionImpl = dwarfSections.getFrameSectionImpl();
         DwarfLocSectionImpl elfLocSectionImpl = dwarfSections.getLocSectionImpl();
         DwarfInfoSectionImpl elfInfoSectionImpl = dwarfSections.getInfoSectionImpl();
+        // DwarfTypesSectionImpl elfTypesSectionImpl = dwarfSections.getTypesSectionImpl();
         DwarfARangesSectionImpl elfARangesSectionImpl = dwarfSections.getARangesSectionImpl();
         DwarfRangesSectionImpl elfRangesSectionImpl = dwarfSections.getRangesSectionImpl();
         DwarfLineSectionImpl elfLineSectionImpl = dwarfSections.getLineSectionImpl();
@@ -1189,6 +1198,7 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) {
         newDebugSection(frameSectionImpl.getSectionName(), frameSectionImpl);
         newDebugSection(elfLocSectionImpl.getSectionName(), elfLocSectionImpl);
         newDebugSection(elfInfoSectionImpl.getSectionName(), elfInfoSectionImpl);
+        // newDebugSection(elfTypesSectionImpl.getSectionName(), elfTypesSectionImpl);
         newDebugSection(elfARangesSectionImpl.getSectionName(), elfARangesSectionImpl);
         newDebugSection(elfRangesSectionImpl.getSectionName(), elfRangesSectionImpl);
         newDebugSection(elfLineSectionImpl.getSectionName(), elfLineSectionImpl);
@@ -1200,6 +1210,7 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) {
          */
         createDefinedSymbol(elfAbbrevSectionImpl.getSectionName(), elfAbbrevSectionImpl.getElement(), 0, 0, false, false);
         createDefinedSymbol(elfInfoSectionImpl.getSectionName(), elfInfoSectionImpl.getElement(), 0, 0, false, false);
+        // createDefinedSymbol(elfTypesSectionImpl.getSectionName(), elfTypesSectionImpl.getElement(), 0, 0, false, false);
         createDefinedSymbol(elfLineSectionImpl.getSectionName(), elfLineSectionImpl.getElement(), 0, 0, false, false);
         createDefinedSymbol(elfStrSectionImpl.getSectionName(), elfStrSectionImpl.getElement(), 0, 0, false, false);
         createDefinedSymbol(elfRangesSectionImpl.getSectionName(), elfRangesSectionImpl.getElement(), 0, 0, false, false);
@@ -1217,6 +1228,7 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) {
         elfAbbrevSectionImpl.getOrCreateRelocationElement(0);
         frameSectionImpl.getOrCreateRelocationElement(0);
         elfInfoSectionImpl.getOrCreateRelocationElement(0);
+        // elfTypesSectionImpl.getOrCreateRelocationElement(0);
         elfLocSectionImpl.getOrCreateRelocationElement(0);
         elfARangesSectionImpl.getOrCreateRelocationElement(0);
         elfRangesSectionImpl.getOrCreateRelocationElement(0);
@@ -1225,6 +1237,53 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) {
         dwarfSections.installDebugInfo(debugInfoProvider);
     }
 
+    @Override
+    public void installRuntimeDebugInfo(RuntimeDebugInfoProvider runtimeDebugInfoProvider) {
+        RuntimeDwarfDebugInfo dwarfSections = new RuntimeDwarfDebugInfo(getMachine(), getByteOrder());
+        /* We need an implementation for each generated DWARF section. */
+        RuntimeDwarfStrSectionImpl elfStrSectionImpl = dwarfSections.getStrSectionImpl();
+        RuntimeDwarfAbbrevSectionImpl elfAbbrevSectionImpl = dwarfSections.getAbbrevSectionImpl();
+        RuntimeDwarfFrameSectionImpl frameSectionImpl = dwarfSections.getFrameSectionImpl();
+        RuntimeDwarfLocSectionImpl elfLocSectionImpl = dwarfSections.getLocSectionImpl();
+        RuntimeDwarfInfoSectionImpl elfInfoSectionImpl = dwarfSections.getInfoSectionImpl();
+        RuntimeDwarfLineSectionImpl elfLineSectionImpl = dwarfSections.getLineSectionImpl();
+        /* Now we can create the section elements with empty content. */
+        newDebugSection(elfStrSectionImpl.getSectionName(), elfStrSectionImpl);
+        newDebugSection(elfAbbrevSectionImpl.getSectionName(), elfAbbrevSectionImpl);
+        newDebugSection(frameSectionImpl.getSectionName(), frameSectionImpl);
+        newDebugSection(elfLocSectionImpl.getSectionName(), elfLocSectionImpl);
+        newDebugSection(elfInfoSectionImpl.getSectionName(), elfInfoSectionImpl);
+        newDebugSection(elfLineSectionImpl.getSectionName(), elfLineSectionImpl);
+        /*
+         * Add symbols for the base of all DWARF sections whose content may need to be referenced
+         * using a section global offset. These need to be written using a base relative reloc so
+         * that they get updated if the section is merged with DWARF content from other ELF objects
+         * during image linking.
+         */
+        createDefinedSymbol(elfAbbrevSectionImpl.getSectionName(), elfAbbrevSectionImpl.getElement(), 0, 0, false, false);
+        createDefinedSymbol(elfInfoSectionImpl.getSectionName(), elfInfoSectionImpl.getElement(), 0, 0, false, false);
+        createDefinedSymbol(elfLineSectionImpl.getSectionName(), elfLineSectionImpl.getElement(), 0, 0, false, false);
+        createDefinedSymbol(elfStrSectionImpl.getSectionName(), elfStrSectionImpl.getElement(), 0, 0, false, false);
+        createDefinedSymbol(elfLocSectionImpl.getSectionName(), elfLocSectionImpl.getElement(), 0, 0, false, false);
+        /*
+         * The byte[] for each implementation's content are created and written under
+         * getOrDecideContent. Doing that ensures that all dependent sections are filled in and then
+         * sized according to the declared dependencies. However, if we leave it at that then
+         * associated reloc sections only get created when the first reloc is inserted during
+         * content write that's too late for them to have layout constraints included in the layout
+         * decision set and causes an NPE during reloc section write. So we need to create the
+         * relevant reloc sections here in advance.
+         */
+        elfStrSectionImpl.getOrCreateRelocationElement(0);
+        elfAbbrevSectionImpl.getOrCreateRelocationElement(0);
+        frameSectionImpl.getOrCreateRelocationElement(0);
+        elfInfoSectionImpl.getOrCreateRelocationElement(0);
+        elfLocSectionImpl.getOrCreateRelocationElement(0);
+        elfLineSectionImpl.getOrCreateRelocationElement(0);
+        /* Ok now we can populate the debug info model. */
+        dwarfSections.installDebugInfo(runtimeDebugInfoProvider);
+    }
+
     @SuppressWarnings("unused")
     static boolean useExplicitAddend(long addend) {
         // For now, we are always using explicit addends
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java
index ead4598414a3..3ceb9a36988f 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java
@@ -943,6 +943,7 @@ public int writeAbbrevs(DebugContext context, byte[] buffer, int p) {
         pos = writeMethodLocationAbbrev(context, buffer, pos);
         pos = writeAbstractInlineMethodAbbrev(context, buffer, pos);
         pos = writeStaticFieldLocationAbbrev(context, buffer, pos);
+        pos = writeStaticFieldLocationTypeUnitRefAbbrev(context, buffer, pos);
         pos = writeSuperReferenceAbbrev(context, buffer, pos);
         pos = writeInterfaceImplementorAbbrev(context, buffer, pos);
 
@@ -1558,6 +1559,26 @@ private int writeStaticFieldLocationAbbrev(@SuppressWarnings("unused") DebugCont
         return pos;
     }
 
+    private int writeStaticFieldLocationTypeUnitRefAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
+        int pos = p;
+
+        pos = writeAbbrevCode(AbbrevCode.STATIC_FIELD_LOCATION_TYPE_UNIT_REF, buffer, pos);
+        pos = writeTag(DwarfTag.DW_TAG_variable, buffer, pos);
+        pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_no, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_specification, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_ref_addr, buffer, pos);
+        // pos = writeAttrType(DwarfDebugInfo.DW_AT_linkage_name, buffer, pos);
+        // pos = writeAttrForm(DwarfDebugInfo.DW_FORM_strp, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_location, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_expr_loc, buffer, pos);
+        /*
+         * Now terminate.
+         */
+        pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos);
+        return pos;
+    }
+
     private int writeSuperReferenceAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
         int pos = p;
         pos = writeAbbrevCode(AbbrevCode.SUPER_REFERENCE, buffer, pos);
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java
index e3634115c5b3..4166c81def1c 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java
@@ -76,6 +76,7 @@ enum AbbrevCode {
         FOREIGN_STRUCT,
         METHOD_LOCATION,
         STATIC_FIELD_LOCATION,
+        STATIC_FIELD_LOCATION_TYPE_UNIT_REF,
         ARRAY_LAYOUT,
         INTERFACE_LAYOUT,
         COMPRESSED_LAYOUT,
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/RuntimeDebugInfoBase.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/RuntimeDebugInfoBase.java
new file mode 100644
index 000000000000..6dc86f836589
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/RuntimeDebugInfoBase.java
@@ -0,0 +1,595 @@
+package com.oracle.objectfile.runtime;
+
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugCodeInfo;
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugFileInfo;
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocalInfo;
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocalValueInfo;
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocationInfo;
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugMethodInfo;
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugTypeInfo.DebugTypeKind;
+import com.oracle.objectfile.runtime.debugentry.CompiledMethodEntry;
+import com.oracle.objectfile.runtime.debugentry.DirEntry;
+import com.oracle.objectfile.runtime.debugentry.FileEntry;
+import com.oracle.objectfile.runtime.debugentry.MethodEntry;
+import com.oracle.objectfile.runtime.debugentry.PrimitiveTypeEntry;
+import com.oracle.objectfile.runtime.debugentry.StringTable;
+import com.oracle.objectfile.runtime.debugentry.TypeEntry;
+import com.oracle.objectfile.runtime.debugentry.TypedefEntry;
+import com.oracle.objectfile.runtime.debugentry.range.PrimaryRange;
+import com.oracle.objectfile.runtime.debugentry.range.Range;
+import com.oracle.objectfile.runtime.debugentry.range.SubRange;
+import jdk.vm.ci.meta.JavaKind;
+import jdk.vm.ci.meta.ResolvedJavaType;
+
+import java.nio.ByteOrder;
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class RuntimeDebugInfoBase {
+
+    protected ByteOrder byteOrder;
+    /**
+     * A table listing all known strings, some of which may be marked for insertion into the
+     * debug_str section.
+     */
+    private final StringTable stringTable = new StringTable();
+    /**
+     * List of dirs in which files are found to reside.
+     */
+    private final List<DirEntry> dirs = new ArrayList<>();
+    /**
+     * Index of all dirs in which files are found to reside either as part of substrate/compiler or
+     * user code.
+     */
+    private final HashMap<Path, DirEntry> dirsIndex = new HashMap<>();
+
+    /**
+     * List of all types present in the native image including instance classes, array classes,
+     * primitive types and the one-off Java header struct.
+     */
+    private final List<TypeEntry> types = new ArrayList<>();
+    /**
+     * Index of already seen types keyed by the unique, associated, identifying ResolvedJavaType or,
+     * in the single special case of the TypeEntry for the Java header structure, by key null.
+     */
+    private final HashMap<ResolvedJavaType, TypeEntry> typesIndex = new HashMap<>();
+    /**
+     * List of all types present in the native image including instance classes, array classes,
+     * primitive types and the one-off Java header struct.
+     */
+    private final List<TypedefEntry> typedefs = new ArrayList<>();
+    /**
+     * Index of already seen types keyed by the unique, associated, identifying ResolvedJavaType or,
+     * in the single special case of the TypeEntry for the Java header structure, by key null.
+     */
+    private final HashMap<ResolvedJavaType, TypedefEntry> typedefsIndex = new HashMap<>();
+    /**
+     * Handle on runtime compiled method found in debug info.
+     */
+    private CompiledMethodEntry compiledMethod;
+    /**
+     * List of files which contain primary or secondary ranges.
+     */
+    private final List<FileEntry> files = new ArrayList<>();
+    /**
+     * Index of files which contain primary or secondary ranges keyed by path.
+     */
+    private final HashMap<Path, FileEntry> filesIndex = new HashMap<>();
+
+    /**
+     * Flag set to true if heap references are stored as addresses relative to a heap base register
+     * otherwise false.
+     */
+    private boolean useHeapBase;
+
+    private String cuName;
+    /**
+     * Number of bits oops are left shifted by when using compressed oops.
+     */
+    private int oopCompressShift;
+    /**
+     * Number of low order bits used for tagging oops.
+     */
+    private int oopTagsCount;
+    /**
+     * Number of bytes used to store an oop reference.
+     */
+    private int oopReferenceSize;
+    /**
+     * Number of bytes used to store a raw pointer.
+     */
+    private int pointerSize;
+    /**
+     * Alignment of object memory area (and, therefore, of any oop) in bytes.
+     */
+    private int oopAlignment;
+    /**
+     * Number of bits in oop which are guaranteed 0 by virtue of alignment.
+     */
+    private int oopAlignShift;
+    /**
+     * The compilation directory in which to look for source files as a {@link String}.
+     */
+    private String cachePath;
+
+    /**
+     * The offset of the first byte beyond the end of the Java compiled code address range.
+     */
+    private int compiledCodeMax;
+
+    @SuppressWarnings("this-escape")
+    public RuntimeDebugInfoBase(ByteOrder byteOrder) {
+        this.byteOrder = byteOrder;
+        this.useHeapBase = true;
+        this.oopTagsCount = 0;
+        this.oopCompressShift = 0;
+        this.oopReferenceSize = 0;
+        this.pointerSize = 0;
+        this.oopAlignment = 0;
+        this.oopAlignShift = 0;
+        this.compiledCodeMax = 0;
+        // create and index an empty dir with index 0.
+        ensureDirEntry(EMPTY_PATH);
+    }
+
+    public int compiledCodeMax() {
+        return compiledCodeMax;
+    }
+
+    /**
+     * Entry point allowing ELFObjectFile to pass on information about types, code and heap data.
+     *
+     * @param debugInfoProvider provider instance passed by ObjectFile client.
+     */
+    @SuppressWarnings("try")
+    public void installDebugInfo(RuntimeDebugInfoProvider debugInfoProvider) {
+        /*
+         * Track whether we need to use a heap base register.
+         */
+        useHeapBase = debugInfoProvider.useHeapBase();
+
+        cuName = debugInfoProvider.getCompilationUnitName();
+
+        /*
+         * Save count of low order tag bits that may appear in references.
+         */
+        int oopTagsMask = debugInfoProvider.oopTagsMask();
+
+        /* Tag bits must be between 0 and 32 for us to emit as DW_OP_lit<n>. */
+        assert oopTagsMask >= 0 && oopTagsMask < 32;
+        /* Mask must be contiguous from bit 0. */
+        assert ((oopTagsMask + 1) & oopTagsMask) == 0;
+
+        oopTagsCount = Integer.bitCount(oopTagsMask);
+
+        /* Save amount we need to shift references by when loading from an object field. */
+        oopCompressShift = debugInfoProvider.oopCompressShift();
+
+        /* shift bit count must be either 0 or 3 */
+        assert (oopCompressShift == 0 || oopCompressShift == 3);
+
+        /* Save number of bytes in a reference field. */
+        oopReferenceSize = debugInfoProvider.oopReferenceSize();
+
+        /* Save pointer size of current target. */
+        pointerSize = debugInfoProvider.pointerSize();
+
+        /* Save alignment of a reference. */
+        oopAlignment = debugInfoProvider.oopAlignment();
+
+        /* Save alignment of a reference. */
+        oopAlignShift = Integer.bitCount(oopAlignment - 1);
+
+        /* Reference alignment must be 8 bytes. */
+        assert oopAlignment == 8;
+
+        /* retrieve limit for Java code address range */
+        compiledCodeMax = debugInfoProvider.compiledCodeMax();
+
+        /* Ensure we have a null string and cachePath in the string section. */
+        String uniqueNullString = stringTable.uniqueDebugString("");
+        if (debugInfoProvider.getCachePath() != null) {
+            cachePath = stringTable.uniqueDebugString(debugInfoProvider.getCachePath().toString());
+        } else {
+            cachePath = uniqueNullString; // fall back to null string
+        }
+
+        // TODO: handle method and required typedefs / primitives
+        DebugCodeInfo debugCodeInfo = debugInfoProvider.codeInfoProvider();
+        String fileName = debugCodeInfo.fileName();
+        Path filePath = debugCodeInfo.filePath();
+        ResolvedJavaType ownerType = debugCodeInfo.ownerType();
+        String methodName = debugCodeInfo.name();
+        long lo = debugCodeInfo.addressLo();
+        long hi = debugCodeInfo.addressHi();
+        int primaryLine = debugCodeInfo.line();
+
+        TypedefEntry typedefEntry = lookupTypedefEntry(ownerType);
+        MethodEntry methodEntry = processMethod(debugCodeInfo, typedefEntry);
+        PrimaryRange primaryRange = Range.createPrimary(methodEntry, lo, hi, primaryLine);
+        compiledMethod = new CompiledMethodEntry(primaryRange, debugCodeInfo.getFrameSizeChanges(), debugCodeInfo.getFrameSize(), typedefEntry);
+        HashMap<DebugLocationInfo, SubRange> subRangeIndex = new HashMap<>();
+        for(DebugLocationInfo debugLocationInfo : debugCodeInfo.locationInfoProvider()) {
+            addSubrange(debugLocationInfo, primaryRange, subRangeIndex);
+        }
+    }
+
+    private MethodEntry processMethod(DebugMethodInfo debugMethodInfo, TypedefEntry ownerType) {
+        String methodName = debugMethodInfo.name();
+        int line = debugMethodInfo.line();
+        ResolvedJavaType resultType = debugMethodInfo.valueType();
+        List<DebugLocalInfo> paramInfos = debugMethodInfo.getParamInfo();
+        DebugLocalInfo thisParam = debugMethodInfo.getThisParamInfo();
+        int paramCount = paramInfos.size();
+        TypeEntry resultTypeEntry = lookupTypeEntry(resultType);
+        TypeEntry[] typeEntries = new TypeEntry[paramCount];
+        for (int i = 0; i < paramCount; i++) {
+            typeEntries[i] = lookupTypeEntry(paramInfos.get(i).valueType());
+        }
+        FileEntry methodFileEntry = ensureFileEntry(debugMethodInfo);
+        MethodEntry methodEntry = new MethodEntry(this, debugMethodInfo, methodFileEntry, line, methodName, ownerType, resultTypeEntry, typeEntries, paramInfos, thisParam);
+        // indexMethodEntry(methodEntry, debugMethodInfo.idMethod());
+        return methodEntry;
+    }
+
+    private TypeEntry createTypeEntry(String typeName, String fileName, Path filePath, int size, DebugTypeKind typeKind) {
+        TypeEntry typeEntry = null;
+        switch (typeKind) {
+            case TYPEDEF:
+                typeEntry = new TypedefEntry(typeName, size);
+                break;
+            case PRIMITIVE:
+                assert fileName.length() == 0;
+                assert filePath == null;
+                typeEntry = new PrimitiveTypeEntry(typeName, size);
+                break;
+        }
+        return typeEntry;
+    }
+
+    private TypeEntry addTypeEntry(ResolvedJavaType idType, String typeName, String fileName, Path filePath, int size, DebugTypeKind typeKind) {
+        TypeEntry typeEntry = (idType != null ? typesIndex.get(idType) : null);
+        if (typeEntry == null) {
+            typeEntry = createTypeEntry(typeName, fileName, filePath, size, typeKind);
+            types.add(typeEntry);
+            if (idType != null) {
+                typesIndex.put(idType, typeEntry);
+            }
+            if (typeEntry instanceof TypedefEntry) {
+                indexTypedef(idType, (TypedefEntry) typeEntry);
+            }
+        }
+        return typeEntry;
+    }
+
+    public TypeEntry lookupTypeEntry(ResolvedJavaType type) {
+        TypeEntry typeEntry = typesIndex.get(type);
+        if (typeEntry == null) {
+            if (type.isPrimitive()) {
+                typeEntry = addTypeEntry(type, type.toJavaName(), "", null, (type.getJavaKind() == JavaKind.Void ? 0 : type.getJavaKind().getBitCount()), DebugTypeKind.PRIMITIVE);
+            } else {
+                String fileName = type.getSourceFileName();
+                Path filePath = fullFilePathFromClassName(type);
+                typeEntry = addTypeEntry(type, type.toJavaName(),fileName, filePath, pointerSize, DebugTypeKind.TYPEDEF);
+            }
+        }
+        return typeEntry;
+    }
+
+    TypedefEntry lookupTypedefEntry(ResolvedJavaType type) {
+        TypedefEntry typedefEntry = typedefsIndex.get(type);
+        if (typedefEntry == null) {
+            String fileName = type.getSourceFileName();
+            Path filePath = fullFilePathFromClassName(type);
+            typedefEntry = (TypedefEntry) addTypeEntry(type, type.toJavaName(), fileName, filePath, pointerSize, DebugTypeKind.TYPEDEF);
+        }
+        return typedefEntry;
+    }
+
+    protected static Path fullFilePathFromClassName(ResolvedJavaType type) {
+        String[] elements = type.toJavaName().split("\\.");
+        int count = elements.length;
+        String name = elements[count - 1];
+        while (name.startsWith("$")) {
+            name = name.substring(1);
+        }
+        if (name.contains("$")) {
+            name = name.substring(0, name.indexOf('$'));
+        }
+        if (name.equals("")) {
+            name = "_nofile_";
+        }
+        elements[count - 1] = name + ".java";
+        return FileSystems.getDefault().getPath("", elements);
+    }
+
+    /**
+     * Recursively creates subranges based on DebugLocationInfo including, and appropriately
+     * linking, nested inline subranges.
+     *
+     * @param locationInfo
+     * @param primaryRange
+     * @param subRangeIndex
+     * @return the subrange for {@code locationInfo} linked with all its caller subranges up to the
+     *         primaryRange
+     */
+    @SuppressWarnings("try")
+    private Range addSubrange(DebugLocationInfo locationInfo, PrimaryRange primaryRange, HashMap<DebugLocationInfo, SubRange> subRangeIndex) {
+        /*
+         * We still insert subranges for the primary method but they don't actually count as inline.
+         * we only need a range so that subranges for inline code can refer to the top level line
+         * number.
+         */
+        DebugLocationInfo callerLocationInfo = locationInfo.getCaller();
+        boolean isTopLevel = callerLocationInfo == null;
+        assert (!isTopLevel || (locationInfo.name().equals(primaryRange.getMethodName()) &&
+                locationInfo.ownerType().toJavaName().equals(primaryRange.getClassName())));
+        Range caller = (isTopLevel ? primaryRange : subRangeIndex.get(callerLocationInfo));
+        // the frame tree is walked topdown so inline ranges should always have a caller range
+        assert caller != null;
+
+        final String fileName = locationInfo.fileName();
+        final Path filePath = locationInfo.filePath();
+        final String fullPath = (filePath == null ? "" : filePath.toString() + "/") + fileName;
+        final ResolvedJavaType ownerType = locationInfo.ownerType();
+        final String methodName = locationInfo.name();
+        final long loOff = locationInfo.addressLo();
+        final long hiOff = locationInfo.addressHi() - 1;
+        final long lo = primaryRange.getLo() + locationInfo.addressLo();
+        final long hi = primaryRange.getLo() + locationInfo.addressHi();
+        final int line = locationInfo.line();
+        TypedefEntry subRangeTypedefEntry = lookupTypedefEntry(ownerType);
+        MethodEntry subRangeMethodEntry = processMethod(locationInfo, subRangeTypedefEntry);
+        SubRange subRange = Range.createSubrange(subRangeMethodEntry, lo, hi, line, primaryRange, caller, locationInfo.isLeaf());
+        //classEntry.indexSubRange(subRange);
+        subRangeIndex.put(locationInfo, subRange);
+        /*if (debugContext.isLogEnabled(DebugContext.DETAILED_LEVEL)) {
+            debugContext.log(DebugContext.DETAILED_LEVEL, "SubRange %s.%s %d %s:%d [0x%x, 0x%x] (%d, %d)",
+                    ownerType.toJavaName(), methodName, subRange.getDepth(), fullPath, line, lo, hi, loOff, hiOff);
+        }*/
+        assert (callerLocationInfo == null || (callerLocationInfo.addressLo() <= loOff && callerLocationInfo.addressHi() >= hiOff)) : "parent range should enclose subrange!";
+        List<DebugLocalValueInfo> localValueInfos = locationInfo.getLocalValueInfo();
+        /*for (int i = 0; i < localValueInfos.length; i++) {
+            DebugLocalValueInfo localValueInfo = localValueInfos[i];
+            if (debugContext.isLogEnabled(DebugContext.DETAILED_LEVEL)) {
+                debugContext.log(DebugContext.DETAILED_LEVEL, "  locals[%d] %s:%s = %s", localValueInfo.slot(), localValueInfo.name(), localValueInfo.typeName(), localValueInfo);
+            }
+        }*/
+        subRange.setLocalValueInfo(localValueInfos);
+        return subRange;
+    }
+
+    private void indexTypedef(ResolvedJavaType idType, TypedefEntry typedefEntry) {
+        typedefs.add(typedefEntry);
+        typedefsIndex.put(idType, typedefEntry);
+    }
+
+    static final Path EMPTY_PATH = Paths.get("");
+
+    private FileEntry addFileEntry(String fileName, Path filePath) {
+        assert fileName != null;
+        Path dirPath = filePath;
+        Path fileAsPath;
+        if (filePath != null) {
+            fileAsPath = dirPath.resolve(fileName);
+        } else {
+            fileAsPath = Paths.get(fileName);
+            dirPath = EMPTY_PATH;
+        }
+        FileEntry fileEntry = filesIndex.get(fileAsPath);
+        if (fileEntry == null) {
+            DirEntry dirEntry = ensureDirEntry(dirPath);
+            /* Ensure file and cachepath are added to the debug_str section. */
+            uniqueDebugString(fileName);
+            uniqueDebugString(cachePath);
+            fileEntry = new FileEntry(fileName, dirEntry);
+            files.add(fileEntry);
+            /* Index the file entry by file path. */
+            filesIndex.put(fileAsPath, fileEntry);
+        } else {
+            assert fileEntry.getDirEntry().getPath().equals(dirPath);
+        }
+        return fileEntry;
+    }
+
+    public FileEntry ensureFileEntry(DebugFileInfo debugFileInfo) {
+        String fileName = debugFileInfo.fileName();
+        if (fileName == null || fileName.length() == 0) {
+            return null;
+        }
+        Path filePath = debugFileInfo.filePath();
+        Path fileAsPath;
+        if (filePath == null) {
+            fileAsPath = Paths.get(fileName);
+        } else {
+            fileAsPath = filePath.resolve(fileName);
+        }
+        /* Reuse any existing entry. */
+        FileEntry fileEntry = findFile(fileAsPath);
+        if (fileEntry == null) {
+            fileEntry = addFileEntry(fileName, filePath);
+        }
+        return fileEntry;
+    }
+
+    private DirEntry ensureDirEntry(Path filePath) {
+        if (filePath == null) {
+            return null;
+        }
+        DirEntry dirEntry = dirsIndex.get(filePath);
+        if (dirEntry == null) {
+            /* Ensure dir path is entered into the debug_str section. */
+            uniqueDebugString(filePath.toString());
+            dirEntry = new DirEntry(filePath);
+            dirsIndex.put(filePath, dirEntry);
+            dirs.add(dirEntry);
+        }
+        return dirEntry;
+    }
+
+    /* Accessors to query the debug info model. */
+    public ByteOrder getByteOrder() {
+        return byteOrder;
+    }
+
+    public List<TypeEntry> getTypes() {
+        return types;
+    }
+    public List<TypedefEntry> getTypedefs() {
+        return typedefs;
+    }
+
+    public List<FileEntry> getFiles() {
+        return files;
+    }
+
+    public List<DirEntry> getDirs() {
+        return dirs;
+    }
+
+    @SuppressWarnings("unused")
+    public FileEntry findFile(Path fullFileName) {
+        return filesIndex.get(fullFileName);
+    }
+
+    public StringTable getStringTable() {
+        return stringTable;
+    }
+
+    /**
+     * Indirects this call to the string table.
+     *
+     * @param string the string whose index is required.
+     */
+    public String uniqueDebugString(String string) {
+        return stringTable.uniqueDebugString(string);
+    }
+
+    /**
+     * Indirects this call to the string table.
+     *
+     * @param string the string whose index is required.
+     * @return the offset of the string in the .debug_str section.
+     */
+    public int debugStringIndex(String string) {
+        return stringTable.debugStringIndex(string);
+    }
+
+    public boolean useHeapBase() {
+        return useHeapBase;
+    }
+
+    public String cuName() {
+        return cuName;
+    }
+
+    public byte oopTagsMask() {
+        return (byte) ((1 << oopTagsCount) - 1);
+    }
+
+    public byte oopTagsShift() {
+        return (byte) oopTagsCount;
+    }
+
+    public int oopCompressShift() {
+        return oopCompressShift;
+    }
+
+    public int oopReferenceSize() {
+        return oopReferenceSize;
+    }
+
+    public int pointerSize() {
+        return pointerSize;
+    }
+
+    public int oopAlignment() {
+        return oopAlignment;
+    }
+
+    public int oopAlignShift() {
+        return oopAlignShift;
+    }
+
+    public String getCachePath() {
+        return cachePath;
+    }
+
+    public CompiledMethodEntry getCompiledMethod() {
+        return compiledMethod;
+    }
+
+    public Iterable<PrimitiveTypeEntry> getPrimitiveTypes() {
+        List<PrimitiveTypeEntry> primitiveTypes = new ArrayList<>();
+        for (TypeEntry typeEntry : types) {
+            if (typeEntry instanceof PrimitiveTypeEntry primitiveTypeEntry) {
+                primitiveTypes.add(primitiveTypeEntry);
+            }
+        }
+        return primitiveTypes;
+    }
+
+
+    /*
+    private static void collectFilesAndDirs(TypedefEntry classEntry) {
+        // track files and dirs we have already seen so that we only add them once
+        EconomicSet<FileEntry> visitedFiles = EconomicSet.create();
+        EconomicSet<DirEntry> visitedDirs = EconomicSet.create();
+        // add the class's file and dir
+        includeOnce(classEntry, classEntry.getFileEntry(), visitedFiles, visitedDirs);
+        // add files for fields (may differ from class file if we have a substitution)
+        for (FieldEntry fieldEntry : classEntry.fields) {
+            includeOnce(classEntry, fieldEntry.getFileEntry(), visitedFiles, visitedDirs);
+        }
+        // add files for declared methods (may differ from class file if we have a substitution)
+        for (MethodEntry methodEntry : classEntry.getMethods()) {
+            includeOnce(classEntry, methodEntry.getFileEntry(), visitedFiles, visitedDirs);
+        }
+        // add files for top level compiled and inline methods
+        classEntry.compiledEntries().forEachOrdered(compiledMethodEntry -> {
+            includeOnce(classEntry, compiledMethodEntry.getPrimary().getFileEntry(), visitedFiles, visitedDirs);
+            // we need files for leaf ranges and for inline caller ranges
+            //
+            // add leaf range files first because they get searched for linearly
+            // during line info processing
+            compiledMethodEntry.leafRangeIterator().forEachRemaining(subRange -> {
+                includeOnce(classEntry, subRange.getFileEntry(), visitedFiles, visitedDirs);
+            });
+            // now the non-leaf range files
+            compiledMethodEntry.topDownRangeIterator().forEachRemaining(subRange -> {
+                if (!subRange.isLeaf()) {
+                    includeOnce(classEntry, subRange.getFileEntry(), visitedFiles, visitedDirs);
+                }
+            });
+        });
+        // now all files and dirs are known build an index for them
+        classEntry.buildFileAndDirIndexes();
+    }*/
+
+    /**
+     * Ensure the supplied file entry and associated directory entry are included, but only once, in
+     * a class entry's file and dir list.
+     *
+     * @param classEntry the class entry whose file and dir list may need to be updated
+     * @param fileEntry a file entry which may need to be added to the class entry's file list or
+     *            whose dir may need adding to the class entry's dir list
+     * @param visitedFiles a set tracking current file list entries, updated if a file is added
+     * @param visitedDirs a set tracking current dir list entries, updated if a dir is added
+     */
+    /*
+    private static void includeOnce(ClassEntry classEntry, FileEntry fileEntry, EconomicSet<FileEntry> visitedFiles, EconomicSet<DirEntry> visitedDirs) {
+        if (fileEntry != null && !visitedFiles.contains(fileEntry)) {
+            visitedFiles.add(fileEntry);
+            classEntry.includeFile(fileEntry);
+            DirEntry dirEntry = fileEntry.getDirEntry();
+            if (dirEntry != null && !dirEntry.getPathString().isEmpty() && !visitedDirs.contains(dirEntry)) {
+                visitedDirs.add(dirEntry);
+                classEntry.includeDir(dirEntry);
+            }
+        }
+    }*/
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/RuntimeDebugInfoProvider.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/RuntimeDebugInfoProvider.java
new file mode 100644
index 000000000000..73b670a89b3c
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/RuntimeDebugInfoProvider.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime;
+
+import jdk.graal.compiler.debug.DebugContext;
+import jdk.vm.ci.meta.JavaConstant;
+import jdk.vm.ci.meta.JavaKind;
+import jdk.vm.ci.meta.ResolvedJavaMethod;
+import jdk.vm.ci.meta.ResolvedJavaType;
+
+import java.nio.file.Path;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * Interfaces used to allow a native image to communicate details of types, code and data to the
+ * underlying object file so that the latter can insert appropriate debug info.
+ */
+public interface RuntimeDebugInfoProvider {
+
+    boolean useHeapBase();
+
+    String getCompilationUnitName();
+
+    /**
+     * Number of bits oops are left shifted by when using compressed oops.
+     */
+    int oopCompressShift();
+
+    /**
+     * Mask selecting low order bits used for tagging oops.
+     */
+    int oopTagsMask();
+
+    /**
+     * Number of bytes used to store an oop reference.
+     */
+    int oopReferenceSize();
+
+    /**
+     * Number of bytes used to store a raw pointer.
+     */
+    int pointerSize();
+
+    /**
+     * Alignment of object memory area (and, therefore, of any oop) in bytes.
+     */
+    int oopAlignment();
+
+    int compiledCodeMax();
+
+    /**
+     * An interface implemented by items that can be located in a file.
+     */
+    interface DebugFileInfo {
+        /**
+         * @return the name of the file containing a file element excluding any path.
+         */
+        String fileName();
+
+        /**
+         * @return a relative path to the file containing a file element derived from its package
+         * name or {@code null} if the element is in the empty package.
+         */
+        Path filePath();
+    }
+
+    interface DebugTypeInfo extends DebugFileInfo {
+        ResolvedJavaType idType();
+
+        enum DebugTypeKind {
+            PRIMITIVE,
+            TYPEDEF;
+
+            @Override
+            public String toString() {
+                return switch (this) {
+                    case PRIMITIVE -> "primitive";
+                    case TYPEDEF -> "typedef";
+                    default -> "???";
+                };
+            }
+        }
+
+        void debugContext(Consumer<DebugContext> action);
+
+        /**
+         * @return the fully qualified name of the debug type.
+         */
+        String typeName();
+
+        DebugTypeKind typeKind();
+
+        /**
+         * returns the offset in the heap at which the java.lang.Class instance which models this
+         * class is located or -1 if no such instance exists for this class.
+         *
+         * @return the offset of the java.lang.Class instance which models this class or -1.
+         */
+        long classOffset();
+
+        int size();
+    }
+
+    interface DebugTypedefInfo extends DebugTypeInfo {
+    }
+
+    interface DebugPrimitiveTypeInfo extends DebugTypeInfo {
+        /*
+         * NUMERIC excludes LOGICAL types boolean and void
+         */
+        int FLAG_NUMERIC = 1 << 0;
+        /*
+         * INTEGRAL excludes FLOATING types float and double
+         */
+        int FLAG_INTEGRAL = 1 << 1;
+        /*
+         * SIGNED excludes UNSIGNED type char
+         */
+        int FLAG_SIGNED = 1 << 2;
+
+        int bitCount();
+
+        char typeChar();
+
+        int flags();
+    }
+
+    interface DebugMemberInfo extends DebugFileInfo {
+
+        String name();
+
+        ResolvedJavaType valueType();
+
+        int modifiers();
+    }
+
+    interface DebugMethodInfo extends DebugMemberInfo {
+        /**
+         * @return the line number for the outer or inlined segment.
+         */
+        int line();
+
+        /**
+         * @return an array of DebugLocalInfo objects holding details of this method's parameters
+         */
+        List<DebugLocalInfo> getParamInfo();
+
+        /**
+         * @return a DebugLocalInfo objects holding details of the target instance parameter this if
+         * the method is an instance method or null if it is a static method.
+         */
+        DebugLocalInfo getThisParamInfo();
+
+        /**
+         * @return the symbolNameForMethod string
+         */
+        String symbolNameForMethod();
+
+        /**
+         * @return true if this method has been compiled in as a deoptimization target
+         */
+        boolean isDeoptTarget();
+
+        /**
+         * @return true if this method is a constructor.
+         */
+        boolean isConstructor();
+
+        /**
+         * @return true if this is a virtual method. In Graal a virtual method can become
+         * non-virtual if all other implementations are non-reachable.
+         */
+        boolean isVirtual();
+
+        /**
+         * @return the offset into the virtual function table for this method if virtual
+         */
+        int vtableOffset();
+
+        /**
+         * @return true if this method is an override of another method.
+         */
+        boolean isOverride();
+
+        /*
+         * Return the unique type that owns this method. <p/>
+         *
+         * @return the unique type that owns this method
+         */
+        ResolvedJavaType ownerType();
+
+        /*
+         * Return the unique identifier for this method. The result can be used to unify details of
+         * methods presented via interface DebugTypeInfo with related details of compiled methods
+         * presented via interface DebugRangeInfo and of call frame methods presented via interface
+         * DebugLocationInfo. <p/>
+         *
+         * @return the unique identifier for this method
+         */
+        ResolvedJavaMethod idMethod();
+    }
+
+    /**
+     * Access details of a compiled top level or inline method producing the code in a specific
+     * {@link com.oracle.objectfile.debugentry.range.Range}.
+     */
+    interface DebugRangeInfo extends DebugMethodInfo {
+
+        /**
+         * @return the lowest address containing code generated for an outer or inlined code segment
+         * reported at this line represented as an offset into the code segment.
+         */
+        long addressLo();
+
+        /**
+         * @return the first address above the code generated for an outer or inlined code segment
+         * reported at this line represented as an offset into the code segment.
+         */
+        long addressHi();
+    }
+
+    /**
+     * Access details of a specific compiled method.
+     */
+    interface DebugCodeInfo extends DebugRangeInfo {
+        void debugContext(Consumer<DebugContext> action);
+
+        /**
+         * @return a stream of records detailing source local var and line locations within the
+         * compiled method.
+         */
+        Iterable<DebugLocationInfo> locationInfoProvider();
+
+        /**
+         * @return the size of the method frame between prologue and epilogue.
+         */
+        int getFrameSize();
+
+        /**
+         * @return a list of positions at which the stack is extended to a full frame or torn down
+         * to an empty frame
+         */
+        List<DebugFrameSizeChange> getFrameSizeChanges();
+    }
+
+    /**
+     * Access details of code generated for a specific outer or inlined method at a given line
+     * number.
+     */
+    interface DebugLocationInfo extends DebugRangeInfo {
+        /**
+         * @return the {@link DebugLocationInfo} of the nested inline caller-line
+         */
+        DebugLocationInfo getCaller();
+
+        /**
+         * @return a stream of {@link DebugLocalValueInfo} objects identifying local or parameter
+         * variables present in the frame of the current range.
+         */
+        List<DebugLocalValueInfo> getLocalValueInfo();
+
+        boolean isLeaf();
+    }
+
+    /**
+     * A DebugLocalInfo details a local or parameter variable recording its name and type, the
+     * (abstract machine) local slot index it resides in and the number of slots it occupies.
+     */
+    interface DebugLocalInfo {
+        ResolvedJavaType valueType();
+
+        String name();
+
+        String typeName();
+
+        int slot();
+
+        int slotCount();
+
+        JavaKind javaKind();
+
+        int line();
+    }
+
+    /**
+     * A DebugLocalValueInfo details the value a local or parameter variable present in a specific
+     * frame. The value may be undefined. If not then the instance records its type and either its
+     * (constant) value or the register or stack location in which the value resides.
+     */
+    interface DebugLocalValueInfo extends DebugLocalInfo {
+        enum LocalKind {
+            UNDEFINED,
+            REGISTER,
+            STACKSLOT,
+            CONSTANT
+        }
+
+        LocalKind localKind();
+
+        int regIndex();
+
+        int stackSlot();
+
+        long heapOffset();
+
+        JavaConstant constantValue();
+    }
+
+    interface DebugFrameSizeChange {
+        enum Type {
+            EXTEND,
+            CONTRACT
+        }
+
+        int getOffset();
+
+        Type getType();
+    }
+
+    @SuppressWarnings("unused")
+    Iterable<DebugTypeInfo> typeInfoProvider();
+
+    DebugCodeInfo codeInfoProvider();
+
+    Path getCachePath();
+
+    void recordActivity();
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/CompiledMethodEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/CompiledMethodEntry.java
new file mode 100644
index 000000000000..a09ea7652f57
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/CompiledMethodEntry.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.debugentry;
+
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugFrameSizeChange;
+import com.oracle.objectfile.runtime.debugentry.range.PrimaryRange;
+import com.oracle.objectfile.runtime.debugentry.range.SubRange;
+
+import java.util.ArrayDeque;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Tracks debug info associated with a top level compiled method.
+ */
+public class CompiledMethodEntry {
+    /**
+     * The primary range detailed by this object.
+     */
+    private final PrimaryRange primary;
+    /**
+     * Details of the class owning this range.
+     */
+    private final TypedefEntry typedefEntry;
+    /**
+     * Details of of compiled method frame size changes.
+     */
+    private final List<DebugFrameSizeChange> frameSizeInfos;
+    /**
+     * Size of compiled method frame.
+     */
+    private final int frameSize;
+
+    public CompiledMethodEntry(PrimaryRange primary, List<DebugFrameSizeChange> frameSizeInfos, int frameSize, TypedefEntry typedefEntry) {
+        this.primary = primary;
+        this.typedefEntry = typedefEntry;
+        this.frameSizeInfos = frameSizeInfos;
+        this.frameSize = frameSize;
+    }
+
+    public PrimaryRange getPrimary() {
+        return primary;
+    }
+
+    public TypedefEntry getTypedefEntry() {
+        return typedefEntry;
+    }
+
+    /**
+     * Returns an iterator that traverses all the callees of the method associated with this entry.
+     * The iterator performs a depth-first pre-order traversal of the call tree.
+     *
+     * @return the iterator
+     */
+    public Iterator<SubRange> topDownRangeIterator() {
+        return new Iterator<>() {
+            final ArrayDeque<SubRange> workStack = new ArrayDeque<>();
+            SubRange current = primary.getFirstCallee();
+
+            @Override
+            public boolean hasNext() {
+                return current != null;
+            }
+
+            @Override
+            public SubRange next() {
+                assert hasNext();
+                SubRange result = current;
+                forward();
+                return result;
+            }
+
+            private void forward() {
+                SubRange sibling = current.getSiblingCallee();
+                assert sibling == null || (current.getHi() <= sibling.getLo()) : current.getHi() + " > " + sibling.getLo();
+                if (!current.isLeaf()) {
+                    /* save next sibling while we process the children */
+                    if (sibling != null) {
+                        workStack.push(sibling);
+                    }
+                    current = current.getFirstCallee();
+                } else if (sibling != null) {
+                    current = sibling;
+                } else {
+                    /*
+                     * Return back up to parents' siblings, use pollFirst instead of pop to return
+                     * null in case the work stack is empty
+                     */
+                    current = workStack.pollFirst();
+                }
+            }
+        };
+    }
+
+    /**
+     * Returns an iterator that traverses the callees of the method associated with this entry and
+     * returns only the leafs. The iterator performs a depth-first pre-order traversal of the call
+     * tree returning only ranges with no callees.
+     *
+     * @return the iterator
+     */
+    public Iterator<SubRange> leafRangeIterator() {
+        final Iterator<SubRange> iter = topDownRangeIterator();
+        return new Iterator<>() {
+            SubRange current = forwardLeaf(iter);
+
+            @Override
+            public boolean hasNext() {
+                return current != null;
+            }
+
+            @Override
+            public SubRange next() {
+                assert hasNext();
+                SubRange result = current;
+                current = forwardLeaf(iter);
+                return result;
+            }
+
+            private SubRange forwardLeaf(Iterator<SubRange> t) {
+                if (t.hasNext()) {
+                    SubRange next = t.next();
+                    while (next != null && !next.isLeaf()) {
+                        next = t.next();
+                    }
+                    return next;
+                }
+                return null;
+            }
+        };
+    }
+
+    public List<DebugFrameSizeChange> getFrameSizeInfos() {
+        return frameSizeInfos;
+    }
+
+    public int getFrameSize() {
+        return frameSize;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/DirEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/DirEntry.java
new file mode 100644
index 000000000000..dbdc79566d1b
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/DirEntry.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.debugentry;
+
+import java.nio.file.Path;
+
+/**
+ * Tracks the directory associated with one or more source files.
+ *
+ * This is identified separately from each FileEntry identifying files that reside in the directory.
+ * That is necessary because the line info generator needs to collect and write out directory names
+ * into directory tables once only rather than once per file.
+ */
+public class DirEntry {
+    private final Path path;
+
+    public DirEntry(Path path) {
+        this.path = path;
+    }
+
+    public Path getPath() {
+        return path;
+    }
+
+    public String getPathString() {
+        return path.toString();
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/FileEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/FileEntry.java
new file mode 100644
index 000000000000..65e948ea9988
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/FileEntry.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.debugentry;
+
+/**
+ * Tracks debug info associated with a Java source file.
+ */
+public class FileEntry {
+    private final String fileName;
+    private final DirEntry dirEntry;
+
+    public FileEntry(String fileName, DirEntry dirEntry) {
+        this.fileName = fileName;
+        this.dirEntry = dirEntry;
+    }
+
+    /**
+     * The name of the associated file excluding path elements.
+     */
+    public String getFileName() {
+        return fileName;
+    }
+
+    public String getPathName() {
+        @SuppressWarnings("hiding")
+        DirEntry dirEntry = getDirEntry();
+        if (dirEntry == null) {
+            return "";
+        } else {
+            return dirEntry.getPathString();
+        }
+    }
+
+    public String getFullName() {
+        if (dirEntry == null) {
+            return fileName;
+        } else {
+            return dirEntry.getPath().resolve(getFileName()).toString();
+        }
+    }
+
+    /**
+     * The directory entry associated with this file entry.
+     */
+    public DirEntry getDirEntry() {
+        return dirEntry;
+    }
+
+    @Override
+    public String toString() {
+        if (getDirEntry() == null) {
+            return getFileName() == null ? "-" : getFileName();
+        } else if (getFileName() == null) {
+            return "--";
+        }
+        return String.format("FileEntry(%s)", getFullName());
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/MemberEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/MemberEntry.java
new file mode 100644
index 000000000000..cc3083bafcc0
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/MemberEntry.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.debugentry;
+
+/**
+ * An abstract class providing modelling a generic class member which includes behaviour and data
+ * shared by both field and method entries.
+ */
+public abstract class MemberEntry {
+    protected FileEntry fileEntry;
+    protected final int line;
+    protected final String memberName;
+    protected final StructureTypeEntry ownerType;
+    protected final TypeEntry valueType;
+    protected final int modifiers;
+
+    public MemberEntry(FileEntry fileEntry, String memberName, StructureTypeEntry ownerType, TypeEntry valueType, int modifiers) {
+        this(fileEntry, 0, memberName, ownerType, valueType, modifiers);
+    }
+
+    public MemberEntry(FileEntry fileEntry, int line, String memberName, StructureTypeEntry ownerType, TypeEntry valueType, int modifiers) {
+        assert line >= 0;
+        this.fileEntry = fileEntry;
+        this.line = line;
+        this.memberName = memberName;
+        this.ownerType = ownerType;
+        this.valueType = valueType;
+        this.modifiers = modifiers;
+    }
+
+    public String getFileName() {
+        if (fileEntry != null) {
+            return fileEntry.getFileName();
+        } else {
+            return "";
+        }
+    }
+
+    public String getFullFileName() {
+        if (fileEntry != null) {
+            return fileEntry.getFullName();
+        } else {
+            return null;
+        }
+    }
+
+    @SuppressWarnings("unused")
+    String getDirName() {
+        if (fileEntry != null) {
+            return fileEntry.getPathName();
+        } else {
+            return "";
+        }
+    }
+
+    public FileEntry getFileEntry() {
+        return fileEntry;
+    }
+
+    public int getFileIdx() {
+        if (ownerType instanceof TypedefEntry) {
+            return ((TypedefEntry) ownerType).getFileIdx(fileEntry);
+        }
+        // should not be asking for a file for header fields
+        assert false : "not expecting a file lookup for header fields";
+        return 1;
+    }
+
+    public int getLine() {
+        return line;
+    }
+
+    public StructureTypeEntry ownerType() {
+        return ownerType;
+    }
+
+    public TypeEntry getValueType() {
+        return valueType;
+    }
+
+    public int getModifiers() {
+        return modifiers;
+    }
+
+    public String getModifiersString() {
+        return ownerType.memberModifiers(modifiers);
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/MethodEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/MethodEntry.java
new file mode 100644
index 000000000000..8f76da24a142
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/MethodEntry.java
@@ -0,0 +1,380 @@
+/*
+ * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.debugentry;
+
+import com.oracle.objectfile.runtime.RuntimeDebugInfoBase;
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugCodeInfo;
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocalInfo;
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocalValueInfo;
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocationInfo;
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugMethodInfo;
+import jdk.vm.ci.meta.JavaKind;
+import jdk.vm.ci.meta.ResolvedJavaType;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
+
+public class MethodEntry extends MemberEntry {
+    private final TypeEntry[] paramTypes;
+    private final DebugLocalInfo thisParam;
+    private final List<DebugLocalInfo> paramInfos;
+    private final int firstLocalSlot;
+    // local vars are accumulated as they are referenced sorted by slot, then name, then
+    // type name. we don't currently deal handle references to locals with no slot.
+    private final ArrayList<DebugLocalInfo> locals;
+    static final int DEOPT = 1 << 0;
+    static final int IN_RANGE = 1 << 1;
+    static final int INLINED = 1 << 2;
+    static final int IS_OVERRIDE = 1 << 3;
+    static final int IS_CONSTRUCTOR = 1 << 4;
+    private int flags;
+    private final int vtableOffset;
+    private final String symbolName;
+
+    @SuppressWarnings("this-escape")
+    public MethodEntry(RuntimeDebugInfoBase debugInfoBase, DebugMethodInfo debugMethodInfo,
+                       FileEntry fileEntry, int line, String methodName, TypedefEntry ownerType,
+                       TypeEntry valueType, TypeEntry[] paramTypes, List<DebugLocalInfo> paramInfos, DebugLocalInfo thisParam) {
+        super(fileEntry, line, methodName, ownerType, valueType, debugMethodInfo.modifiers());
+        this.paramTypes = paramTypes;
+        this.paramInfos = paramInfos;
+        this.thisParam = thisParam;
+        this.symbolName = debugMethodInfo.symbolNameForMethod();
+        this.flags = 0;
+        if (debugMethodInfo.isDeoptTarget()) {
+            setIsDeopt();
+        }
+        if (debugMethodInfo.isConstructor()) {
+            setIsConstructor();
+        }
+        if (debugMethodInfo.isOverride()) {
+            setIsOverride();
+        }
+        vtableOffset = debugMethodInfo.vtableOffset();
+        int paramCount = paramInfos.size();
+        if (paramCount > 0) {
+            DebugLocalInfo lastParam = paramInfos.get(paramCount - 1);
+            firstLocalSlot = lastParam.slot() + lastParam.slotCount();
+        } else {
+            firstLocalSlot = (thisParam == null ? 0 : thisParam.slotCount());
+        }
+        locals = new ArrayList<>();
+        updateRangeInfo(debugInfoBase, debugMethodInfo);
+    }
+
+    public String methodName() {
+        return memberName;
+    }
+
+    @Override
+    public TypedefEntry ownerType() {
+        assert ownerType instanceof TypedefEntry;
+        return (TypedefEntry) ownerType;
+    }
+
+    public int getParamCount() {
+        return paramInfos.size();
+    }
+
+    public TypeEntry getParamType(int idx) {
+        assert idx < paramInfos.size();
+        return paramTypes[idx];
+    }
+
+    public TypeEntry[] getParamTypes() {
+        return paramTypes;
+    }
+
+    public String getParamTypeName(int idx) {
+        assert idx < paramTypes.length;
+        return paramTypes[idx].getTypeName();
+    }
+
+    public String getParamName(int idx) {
+        assert idx < paramInfos.size();
+        /* N.b. param names may be null. */
+        return paramInfos.get(idx).name();
+    }
+
+    public int getParamLine(int idx) {
+        assert idx < paramInfos.size();
+        /* N.b. param names may be null. */
+        return paramInfos.get(idx).line();
+    }
+
+    public DebugLocalInfo getParam(int i) {
+        assert i >= 0 && i < paramInfos.size() : "bad param index";
+        return paramInfos.get(i);
+    }
+
+    public DebugLocalInfo getThisParam() {
+        return thisParam;
+    }
+
+    public int getLocalCount() {
+        return locals.size();
+    }
+
+    public DebugLocalInfo getLocal(int i) {
+        assert i >= 0 && i < locals.size() : "bad param index";
+        return locals.get(i);
+    }
+
+    private void setIsDeopt() {
+        flags |= DEOPT;
+    }
+
+    public boolean isDeopt() {
+        return (flags & DEOPT) != 0;
+    }
+
+    private void setIsInRange() {
+        flags |= IN_RANGE;
+    }
+
+    public boolean isInRange() {
+        return (flags & IN_RANGE) != 0;
+    }
+
+    private void setIsInlined() {
+        flags |= INLINED;
+    }
+
+    public boolean isInlined() {
+        return (flags & INLINED) != 0;
+    }
+
+    private void setIsOverride() {
+        flags |= IS_OVERRIDE;
+    }
+
+    public boolean isOverride() {
+        return (flags & IS_OVERRIDE) != 0;
+    }
+
+    private void setIsConstructor() {
+        flags |= IS_CONSTRUCTOR;
+    }
+
+    public boolean isConstructor() {
+        return (flags & IS_CONSTRUCTOR) != 0;
+    }
+
+    /**
+     * Sets {@code isInRange} and ensures that the {@code fileEntry} is up to date. If the
+     * MethodEntry was added by traversing the DeclaredMethods of a Class its fileEntry will point
+     * to the original source file, thus it will be wrong for substituted methods. As a result when
+     * setting a MethodEntry as isInRange we also make sure that its fileEntry reflects the file
+     * info associated with the corresponding Range.
+     *
+     * @param debugInfoBase
+     * @param debugMethodInfo
+     */
+    public void updateRangeInfo(RuntimeDebugInfoBase debugInfoBase, DebugMethodInfo debugMethodInfo) {
+        if (debugMethodInfo instanceof DebugLocationInfo) {
+            DebugLocationInfo locationInfo = (DebugLocationInfo) debugMethodInfo;
+            if (locationInfo.getCaller() != null) {
+                /* this is a real inlined method */
+                setIsInlined();
+            }
+        } else if (debugMethodInfo instanceof DebugCodeInfo) {
+            /* this method is being notified as a top level compiled method */
+            if (isInRange()) {
+                /* it has already been seen -- just check for consistency */
+                assert fileEntry == debugInfoBase.ensureFileEntry(debugMethodInfo);
+            } else {
+                /*
+                 * If the MethodEntry was added by traversing the DeclaredMethods of a Class its
+                 * fileEntry may point to the original source file, which will be wrong for
+                 * substituted methods. As a result when setting a MethodEntry as isInRange we also
+                 * make sure that its fileEntry reflects the file info associated with the
+                 * corresponding Range.
+                 */
+                setIsInRange();
+                fileEntry = debugInfoBase.ensureFileEntry(debugMethodInfo);
+            }
+        }
+    }
+
+    public boolean isVirtual() {
+        return vtableOffset >= 0;
+    }
+
+    public int getVtableOffset() {
+        return vtableOffset;
+    }
+
+    public String getSymbolName() {
+        return symbolName;
+    }
+
+    /**
+     * Return a unique local or parameter variable associated with the value, optionally recording
+     * it as a new local variable or fail, returning null, when the local value does not conform
+     * with existing recorded parameter or local variables. Values with invalid (negative) slots
+     * always fail. Values whose slot is associated with a parameter only conform if their name and
+     * type equal those of the parameter. Values whose slot is in the local range will always
+     * succeed,. either by matchign the slot and name of an existing local or by being recorded as a
+     * new local variable.
+     * 
+     * @param localValueInfo
+     * @return the unique local variable with which this local value can be legitimately associated
+     *         otherwise null.
+     */
+    public DebugLocalInfo recordLocal(DebugLocalValueInfo localValueInfo) {
+        int slot = localValueInfo.slot();
+        if (slot < 0) {
+            return null;
+        } else {
+            if (slot < firstLocalSlot) {
+                return matchParam(localValueInfo);
+            } else {
+                return matchLocal(localValueInfo);
+            }
+        }
+    }
+
+    private DebugLocalInfo matchParam(DebugLocalValueInfo localValueInfo) {
+        if (thisParam != null) {
+            if (checkMatch(thisParam, localValueInfo)) {
+                return thisParam;
+            }
+        }
+        for (int i = 0; i < paramInfos.size(); i++) {
+            DebugLocalInfo paramInfo = paramInfos.get(i);
+            if (checkMatch(paramInfo, localValueInfo)) {
+                return paramInfo;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * wrapper class for a local value that stands in as a unique identifier for the associated
+     * local variable while allowing its line to be adjusted when earlier occurrences of the same
+     * local are identified.
+     */
+    private static class DebugLocalInfoWrapper implements DebugLocalInfo {
+        DebugLocalValueInfo value;
+        int line;
+
+        DebugLocalInfoWrapper(DebugLocalValueInfo value) {
+            this.value = value;
+            this.line = value.line();
+        }
+
+        @Override
+        public ResolvedJavaType valueType() {
+            return value.valueType();
+        }
+
+        @Override
+        public String name() {
+            return value.name();
+        }
+
+        @Override
+        public String typeName() {
+            return value.typeName();
+        }
+
+        @Override
+        public int slot() {
+            return value.slot();
+        }
+
+        @Override
+        public int slotCount() {
+            return value.slotCount();
+        }
+
+        @Override
+        public JavaKind javaKind() {
+            return value.javaKind();
+        }
+
+        @Override
+        public int line() {
+            return line;
+        }
+
+        public void setLine(int line) {
+            this.line = line;
+        }
+    }
+
+    private DebugLocalInfo matchLocal(DebugLocalValueInfo localValueInfo) {
+        ListIterator<DebugLocalInfo> listIterator = locals.listIterator();
+        while (listIterator.hasNext()) {
+            DebugLocalInfoWrapper next = (DebugLocalInfoWrapper) listIterator.next();
+            if (checkMatch(next, localValueInfo)) {
+                int currentLine = next.line();
+                int newLine = localValueInfo.line();
+                if ((currentLine < 0 && newLine >= 0) ||
+                                (newLine >= 0 && newLine < currentLine)) {
+                    next.setLine(newLine);
+                }
+                return next;
+            } else if (next.slot() > localValueInfo.slot()) {
+                // we have iterated just beyond the insertion point
+                // so wind cursor back one element
+                listIterator.previous();
+                break;
+            }
+        }
+        DebugLocalInfoWrapper newLocal = new DebugLocalInfoWrapper(localValueInfo);
+        // add at the current cursor position
+        listIterator.add(newLocal);
+        return newLocal;
+    }
+
+    boolean checkMatch(DebugLocalInfo local, DebugLocalValueInfo value) {
+        boolean isMatch = (local.slot() == value.slot() &&
+                        local.name().equals(value.name()) &&
+                        local.typeName().equals(value.typeName()));
+        assert !isMatch || verifyMatch(local, value) : "failed to verify matched var and value";
+        return isMatch;
+    }
+
+    private static boolean verifyMatch(DebugLocalInfo local, DebugLocalValueInfo value) {
+        // slot counts are normally expected to match
+        if (local.slotCount() == value.slotCount()) {
+            return true;
+        }
+        // we can have a zero count for the local or value if it is undefined
+        if (local.slotCount() == 0 || value.slotCount() == 0) {
+            return true;
+        }
+        // pseudo-object locals can appear as longs
+        if (local.javaKind() == JavaKind.Object && value.javaKind() == JavaKind.Long) {
+            return true;
+        }
+        // something is wrong
+        return false;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/PrimitiveTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/PrimitiveTypeEntry.java
new file mode 100644
index 000000000000..a630e5b0bdac
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/PrimitiveTypeEntry.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.debugentry;
+
+import com.oracle.objectfile.runtime.RuntimeDebugInfoBase;
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugPrimitiveTypeInfo;
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugTypeInfo;
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugTypeInfo.DebugTypeKind;
+import jdk.graal.compiler.debug.DebugContext;
+
+import static com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugPrimitiveTypeInfo.FLAG_INTEGRAL;
+import static com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugPrimitiveTypeInfo.FLAG_NUMERIC;
+import static com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugPrimitiveTypeInfo.FLAG_SIGNED;
+
+public class PrimitiveTypeEntry extends TypeEntry {
+    private char typeChar;
+    private int flags;
+    private int bitCount;
+
+    public PrimitiveTypeEntry(String typeName, int size) {
+        super(typeName, size);
+        typeChar = '#';
+        flags = 0;
+        bitCount = 0;
+    }
+
+    @Override
+    public DebugTypeKind typeKind() {
+        return DebugTypeKind.PRIMITIVE;
+    }
+
+    @Override
+    public void addDebugInfo(RuntimeDebugInfoBase debugInfoBase, DebugTypeInfo debugTypeInfo, DebugContext debugContext) {
+        super.addDebugInfo(debugInfoBase, debugTypeInfo, debugContext);
+        DebugPrimitiveTypeInfo debugPrimitiveTypeInfo = (DebugPrimitiveTypeInfo) debugTypeInfo;
+        flags = debugPrimitiveTypeInfo.flags();
+        typeChar = debugPrimitiveTypeInfo.typeChar();
+        bitCount = debugPrimitiveTypeInfo.bitCount();
+        if (debugContext.isLogEnabled()) {
+            debugContext.log("typename %s %s (%d bits)%n", typeName, decodeFlags(), bitCount);
+        }
+    }
+
+    private String decodeFlags() {
+        StringBuilder builder = new StringBuilder();
+        if ((flags & FLAG_NUMERIC) != 0) {
+            if ((flags & FLAG_INTEGRAL) != 0) {
+                if ((flags & FLAG_SIGNED) != 0) {
+                    builder.append("SIGNED ");
+                } else {
+                    builder.append("UNSIGNED ");
+                }
+                builder.append("INTEGRAL");
+            } else {
+                builder.append("FLOATING");
+            }
+        } else {
+            if (bitCount > 0) {
+                builder.append("LOGICAL");
+            } else {
+                builder.append("VOID");
+            }
+        }
+        return builder.toString();
+    }
+
+    public char getTypeChar() {
+        return typeChar;
+    }
+
+    public int getBitCount() {
+        return bitCount;
+    }
+
+    public int getFlags() {
+        return flags;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/StringEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/StringEntry.java
new file mode 100644
index 000000000000..1aa804a54536
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/StringEntry.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.debugentry;
+
+/**
+ * Used to retain a unique (up to equals) copy of a String. Also flags whether the String needs to
+ * be located in the debug_string section and, if so, tracks the offset at which it gets written.
+ */
+public class StringEntry {
+    private final String string;
+    private int offset;
+    private boolean addToStrSection;
+
+    StringEntry(String string) {
+        this.string = string;
+        this.offset = -1;
+    }
+
+    public String getString() {
+        return string;
+    }
+
+    public int getOffset() {
+        assert offset >= -1;
+        return offset;
+    }
+
+    public void setOffset(int offset) {
+        assert this.offset < 0;
+        assert offset >= 0;
+        this.offset = offset;
+    }
+
+    public boolean isAddToStrSection() {
+        return addToStrSection;
+    }
+
+    public void setAddToStrSection() {
+        this.addToStrSection = true;
+    }
+
+    @Override
+    public boolean equals(Object object) {
+        if (object == null || !(object instanceof StringEntry)) {
+            return false;
+        } else {
+            StringEntry other = (StringEntry) object;
+            return this == other || string.equals(other.string);
+        }
+    }
+
+    @Override
+    public int hashCode() {
+        return string.hashCode() + 37;
+    }
+
+    @Override
+    public String toString() {
+        return string;
+    }
+
+    public boolean isEmpty() {
+        return string.length() == 0;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/StringTable.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/StringTable.java
new file mode 100644
index 000000000000..975b9f4565ae
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/StringTable.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.debugentry;
+
+import java.util.HashMap;
+import java.util.Iterator;
+
+/**
+ * Allows incoming strings to be reduced to unique (up to equals) instances and supports marking of
+ * strings which need to be written to the debug_str section and retrieval of the location offset
+ * after writing.
+ */
+public class StringTable implements Iterable<StringEntry> {
+
+    private final HashMap<String, StringEntry> table;
+
+    public StringTable() {
+        this.table = new HashMap<>();
+    }
+
+    /**
+     * Ensures a unique instance of a string exists in the table, inserting the supplied String if
+     * no equivalent String is already present. This should only be called before the string section
+     * has been written.
+     *
+     * @param string the string to be included in the table
+     * @return the unique instance of the String
+     */
+    public String uniqueString(String string) {
+        return ensureString(string, false);
+    }
+
+    /**
+     * Ensures a unique instance of a string exists in the table and is marked for inclusion in the
+     * debug_str section, inserting the supplied String if no equivalent String is already present.
+     * This should only be called before the string section has been written.
+     *
+     * @param string the string to be included in the table and marked for inclusion in the
+     *            debug_str section
+     * @return the unique instance of the String
+     */
+    public String uniqueDebugString(String string) {
+        return ensureString(string, true);
+    }
+
+    private String ensureString(String string, boolean addToStrSection) {
+        StringEntry stringEntry = table.get(string);
+        if (stringEntry == null) {
+            stringEntry = new StringEntry(string);
+            table.put(string, stringEntry);
+        }
+        if (addToStrSection && !stringEntry.isAddToStrSection()) {
+            stringEntry.setAddToStrSection();
+        }
+        return stringEntry.getString();
+    }
+
+    /**
+     * Retrieves the offset at which a given string was written into the debug_str section. This
+     * should only be called after the string section has been written.
+     *
+     * @param string the string whose offset is to be retrieved
+     * @return the offset or -1 if the string does not define an entry or the entry has not been
+     *         written to the debug_str section
+     */
+    public int debugStringIndex(String string) {
+        StringEntry stringEntry = table.get(string);
+        assert stringEntry != null : "\"" + string + "\" not in string table";
+        if (stringEntry == null) {
+            return -1;
+        }
+        return stringEntry.getOffset();
+    }
+
+    @Override
+    public Iterator<StringEntry> iterator() {
+        return table.values().iterator();
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/StructureTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/StructureTypeEntry.java
new file mode 100644
index 000000000000..a2d8a7785a73
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/StructureTypeEntry.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.debugentry;
+
+import java.lang.reflect.Modifier;
+
+/**
+ * An intermediate type that provides behaviour for managing fields. This unifies code for handling
+ * header structures and Java instance and array classes that both support data members.
+ */
+public abstract class StructureTypeEntry extends TypeEntry {
+    public StructureTypeEntry(String typeName, int size) {
+        super(typeName, size);
+    }
+
+    String memberModifiers(int modifiers) {
+        StringBuilder builder = new StringBuilder();
+        if (Modifier.isPublic(modifiers)) {
+            builder.append("public ");
+        } else if (Modifier.isProtected(modifiers)) {
+            builder.append("protected ");
+        } else if (Modifier.isPrivate(modifiers)) {
+            builder.append("private ");
+        }
+        if (Modifier.isFinal(modifiers)) {
+            builder.append("final ");
+        }
+        if (Modifier.isAbstract(modifiers)) {
+            builder.append("abstract ");
+        } else if (Modifier.isVolatile(modifiers)) {
+            builder.append("volatile ");
+        } else if (Modifier.isTransient(modifiers)) {
+            builder.append("transient ");
+        } else if (Modifier.isSynchronized(modifiers)) {
+            builder.append("synchronized ");
+        }
+        if (Modifier.isNative(modifiers)) {
+            builder.append("native ");
+        }
+        if (Modifier.isStatic(modifiers)) {
+            builder.append("static");
+        } else {
+            builder.append("instance");
+        }
+
+        return builder.toString();
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/TypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/TypeEntry.java
new file mode 100644
index 000000000000..d06def728554
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/TypeEntry.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.debugentry;
+
+import com.oracle.objectfile.runtime.RuntimeDebugInfoBase;
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugTypeInfo;
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugTypeInfo.DebugTypeKind;
+import jdk.graal.compiler.debug.DebugContext;
+
+import static com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugTypeInfo.DebugTypeKind.PRIMITIVE;
+import static com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugTypeInfo.DebugTypeKind.TYPEDEF;
+
+public abstract class TypeEntry {
+    /**
+     * The name of this type.
+     */
+    protected final String typeName;
+
+    /**
+     * The offset of the java.lang.Class instance for this class in the image heap or -1 if no such
+     * object exists.
+     */
+    private long classOffset;
+
+    /**
+     * The size of an occurrence of this type in bytes.
+     */
+    protected final int size;
+
+    protected TypeEntry(String typeName, int size) {
+        this.typeName = typeName;
+        this.size = size;
+        this.classOffset = -1;
+    }
+
+    public long getClassOffset() {
+        return classOffset;
+    }
+
+    public int getSize() {
+        return size;
+    }
+
+    public String getTypeName() {
+        return typeName;
+    }
+
+    public abstract DebugTypeKind typeKind();
+
+    public boolean isPrimitive() {
+        return typeKind() == PRIMITIVE;
+    }
+
+    public boolean isTypedef() {
+        return typeKind() == TYPEDEF;
+    }
+
+    /**
+     * Test whether this entry is a class type, either an instance class, an interface type, an enum
+     * type or a foreign type. The test excludes primitive and array types and the header type.
+     *
+     * n.b. Foreign types are considered to be class types because they appear like interfaces or
+     * classes in the Java source and hence need to be modeled by a ClassEntry which can track
+     * properties of the java type. This also allows them to be decorated with properties that
+     * record details of the generated debug info.
+     *
+     * @return true if this entry is a class type otherwise false.
+     */
+    public boolean isClass() {
+        return isTypedef();
+    }
+
+    public boolean isStructure() {
+        return isTypedef();
+    }
+
+    public void addDebugInfo(@SuppressWarnings("unused") RuntimeDebugInfoBase debugInfoBase, DebugTypeInfo debugTypeInfo, @SuppressWarnings("unused") DebugContext debugContext) {
+        /* Record the location of the Class instance in the heap if there is one */
+        this.classOffset = debugTypeInfo.classOffset();
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/TypedefEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/TypedefEntry.java
new file mode 100644
index 000000000000..8ea0da2ab5c5
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/TypedefEntry.java
@@ -0,0 +1,18 @@
+package com.oracle.objectfile.runtime.debugentry;
+
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugTypeInfo.DebugTypeKind;
+
+public class TypedefEntry extends StructureTypeEntry {
+    public TypedefEntry(String typeName, int size) {
+        super(typeName, size);
+    }
+
+    @Override
+    public DebugTypeKind typeKind() {
+        return DebugTypeKind.TYPEDEF;
+    }
+
+    public int getFileIdx(FileEntry fileEntry) {
+        return 0;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/CallRange.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/CallRange.java
new file mode 100644
index 000000000000..3c94523b41f1
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/CallRange.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2022, 2022, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.debugentry.range;
+
+import com.oracle.objectfile.runtime.debugentry.MethodEntry;
+
+class CallRange extends SubRange {
+    /**
+     * The first direct callee whose range is wholly contained in this range or null if this is a
+     * leaf range.
+     */
+    protected SubRange firstCallee;
+    /**
+     * The last direct callee whose range is wholly contained in this range.
+     */
+    protected SubRange lastCallee;
+
+    protected CallRange(MethodEntry methodEntry, long lo, long hi, int line, PrimaryRange primary, Range caller) {
+        super(methodEntry, lo, hi, line, primary, caller);
+        this.firstCallee = null;
+        this.lastCallee = null;
+    }
+
+    @Override
+    protected void addCallee(SubRange callee) {
+        assert this.lo <= callee.lo;
+        assert this.hi >= callee.hi;
+        assert callee.caller == this;
+        assert callee.siblingCallee == null;
+        if (this.firstCallee == null) {
+            assert this.lastCallee == null;
+            this.firstCallee = this.lastCallee = callee;
+        } else {
+            this.lastCallee.siblingCallee = callee;
+            this.lastCallee = callee;
+        }
+    }
+
+    @Override
+    public SubRange getFirstCallee() {
+        return firstCallee;
+    }
+
+    @Override
+    public boolean isLeaf() {
+        assert firstCallee != null;
+        return false;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/LeafRange.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/LeafRange.java
new file mode 100644
index 000000000000..863d4b8e5541
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/LeafRange.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2022, 2022, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.debugentry.range;
+
+import com.oracle.objectfile.runtime.debugentry.MethodEntry;
+
+class LeafRange extends SubRange {
+    protected LeafRange(MethodEntry methodEntry, long lo, long hi, int line, PrimaryRange primary, Range caller) {
+        super(methodEntry, lo, hi, line, primary, caller);
+    }
+
+    @Override
+    protected void addCallee(SubRange callee) {
+        assert false : "should never be adding callees to a leaf range!";
+    }
+
+    @Override
+    public SubRange getFirstCallee() {
+        return null;
+    }
+
+    @Override
+    public boolean isLeaf() {
+        return true;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/PrimaryRange.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/PrimaryRange.java
new file mode 100644
index 000000000000..ff847d8a0f89
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/PrimaryRange.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2022, 2022, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.debugentry.range;
+
+import com.oracle.objectfile.runtime.debugentry.MethodEntry;
+
+public class PrimaryRange extends Range {
+    /**
+     * The first subrange in the range covered by this primary or null if this primary as no
+     * subranges.
+     */
+    protected SubRange firstCallee;
+    /**
+     * The last subrange in the range covered by this primary.
+     */
+    protected SubRange lastCallee;
+
+    protected PrimaryRange(MethodEntry methodEntry, long lo, long hi, int line) {
+        super(methodEntry, lo, hi, line, -1);
+        this.firstCallee = null;
+        this.lastCallee = null;
+    }
+
+    @Override
+    public boolean isPrimary() {
+        return true;
+    }
+
+    @Override
+    protected void addCallee(SubRange callee) {
+        assert this.lo <= callee.lo;
+        assert this.hi >= callee.hi;
+        assert callee.caller == this;
+        assert callee.siblingCallee == null;
+        if (this.firstCallee == null) {
+            assert this.lastCallee == null;
+            this.firstCallee = this.lastCallee = callee;
+        } else {
+            this.lastCallee.siblingCallee = callee;
+            this.lastCallee = callee;
+        }
+    }
+
+    @Override
+    public SubRange getFirstCallee() {
+        return firstCallee;
+    }
+
+    @Override
+    public boolean isLeaf() {
+        return firstCallee == null;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/Range.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/Range.java
new file mode 100644
index 000000000000..cd02ea163312
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/Range.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.debugentry.range;
+
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocalInfo;
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocalValueInfo;
+import com.oracle.objectfile.runtime.debugentry.FileEntry;
+import com.oracle.objectfile.runtime.debugentry.MethodEntry;
+import com.oracle.objectfile.runtime.debugentry.TypeEntry;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Details of a specific address range in a compiled method either a primary range identifying a
+ * whole compiled method or a sub-range identifying a sub-sequence of the compiled instructions that
+ * may derive from top level or inlined code. Each sub-range is linked with its caller, (which may
+ * be the primary range) and its callees, forming a call tree. Subranges are either leaf nodes with
+ * no children or call nodes which have children.
+ *
+ * <ul>
+ * <li>A leaf node at the top level (depth 0) records the start and extent of a sequence of compiled
+ * code derived from the top level method. The leaf node reports itself as belonging to the top
+ * level method.
+ * <li>A leaf node at depth N records the start and extent of a sequence of compiled code derived
+ * from a leaf inlined method at a call depth of N. The leaf node reports itself as belonging to the
+ * leaf method.
+ * <li>A call node at level 0 records the start and extent of a sequence of compiled code that
+ * includes all compiled code derived from a top level call that has been inlined. All child nodes
+ * of the call node (direct or indirect) should model ranges that lie within the parent range. The
+ * call node reports itself as belonging to the top level method and its file and line information
+ * identify the location of the call.
+ * <li>A call node at level N records the start and extent of a sequence of compiled code that
+ * includes all compiled code derived from an inline call at depth N. All child nodes of the call
+ * node (direct or indirect) should model ranges that lie within the parent range. The call node
+ * reports itself as belonging to the caller method at depth N and its file and line information
+ * identify the location of the call.
+ * <ul>
+ *
+ * Ranges also record the location of local and parameter values that are valid for the range's
+ * extent. Each value maps to a corresponding parameter or local variable attached to the range's
+ * method. So, a leaf or call node at level 0 records local and parameter values for separate
+ * sub-extents of the top level method while a leaf or call node at level N+1 records local and
+ * parameter values for separate sub-extents of an inline called method whose full extent is
+ * represented by the parent call range at level N.
+ */
+public abstract class Range {
+    private static final String CLASS_DELIMITER = ".";
+    protected final MethodEntry methodEntry;
+    protected final long lo;
+    protected long hi;
+    protected final int line;
+    protected final int depth;
+
+    /**
+     * Create a primary range representing the root of the subrange tree for a top level compiled
+     * method.
+     * 
+     * @param methodEntry the top level compiled method for this primary range.
+     * @param lo the lowest address included in the range.
+     * @param hi the first address above the highest address in the range.
+     * @param line the line number associated with the range
+     * @return a new primary range to serve as the root of the subrange tree.
+     */
+    public static PrimaryRange createPrimary(MethodEntry methodEntry, long lo, long hi, int line) {
+        return new PrimaryRange(methodEntry, lo, hi, line);
+    }
+
+    /**
+     * Create a subrange representing a segment of the address range for code of a top level or
+     * inlined compiled method. The result will either be a call or a leaf range.
+     * 
+     * @param methodEntry the method from which code in the subrange is derived.
+     * @param lo the lowest address included in the range.
+     * @param hi the first address above the highest address in the range.
+     * @param line the line number associated with the range
+     * @param primary the primary range to which this subrange belongs
+     * @param caller the range for which this is a subrange, either an inlined call range or the
+     *            primary range.
+     * @param isLeaf true if this is a leaf range with no subranges
+     * @return a new subrange to be linked into the range tree below the primary range.
+     */
+    public static SubRange createSubrange(MethodEntry methodEntry, long lo, long hi, int line, PrimaryRange primary, Range caller, boolean isLeaf) {
+        assert primary != null;
+        assert primary.isPrimary();
+        if (isLeaf) {
+            return new LeafRange(methodEntry, lo, hi, line, primary, caller);
+        } else {
+            return new CallRange(methodEntry, lo, hi, line, primary, caller);
+        }
+    }
+
+    protected Range(MethodEntry methodEntry, long lo, long hi, int line, int depth) {
+        assert methodEntry != null;
+        this.methodEntry = methodEntry;
+        this.lo = lo;
+        this.hi = hi;
+        this.line = line;
+        this.depth = depth;
+    }
+
+    protected abstract void addCallee(SubRange callee);
+
+    public boolean contains(Range other) {
+        return (lo <= other.lo && hi >= other.hi);
+    }
+
+    public abstract boolean isPrimary();
+
+    public String getClassName() {
+        return methodEntry.ownerType().getTypeName();
+    }
+
+    public String getMethodName() {
+        return methodEntry.methodName();
+    }
+
+    public String getSymbolName() {
+        return methodEntry.getSymbolName();
+    }
+
+    public long getHi() {
+        return hi;
+    }
+
+    public long getLo() {
+        return lo;
+    }
+
+    public int getLine() {
+        return line;
+    }
+
+    public String getFullMethodName() {
+        return constructClassAndMethodName();
+    }
+
+    public String getFullMethodNameWithParams() {
+        return constructClassAndMethodNameWithParams();
+    }
+
+    public boolean isDeoptTarget() {
+        return methodEntry.isDeopt();
+    }
+
+    private String getExtendedMethodName(boolean includeClass, boolean includeParams, boolean includeReturnType) {
+        StringBuilder builder = new StringBuilder();
+        if (includeReturnType && methodEntry.getValueType().getTypeName().length() > 0) {
+            builder.append(methodEntry.getValueType().getTypeName());
+            builder.append(' ');
+        }
+        if (includeClass && getClassName() != null) {
+            builder.append(getClassName());
+            builder.append(CLASS_DELIMITER);
+        }
+        builder.append(getMethodName());
+        if (includeParams) {
+            builder.append("(");
+            TypeEntry[] paramTypes = methodEntry.getParamTypes();
+            if (paramTypes != null) {
+                String prefix = "";
+                for (TypeEntry t : paramTypes) {
+                    builder.append(prefix);
+                    builder.append(t.getTypeName());
+                    prefix = ", ";
+                }
+            }
+            builder.append(')');
+        }
+        if (includeReturnType) {
+            builder.append(" ");
+            builder.append(methodEntry.getValueType().getTypeName());
+        }
+        return builder.toString();
+    }
+
+    private String constructClassAndMethodName() {
+        return getExtendedMethodName(true, false, false);
+    }
+
+    private String constructClassAndMethodNameWithParams() {
+        return getExtendedMethodName(true, true, false);
+    }
+
+    public FileEntry getFileEntry() {
+        return methodEntry.getFileEntry();
+    }
+
+    public int getModifiers() {
+        return methodEntry.getModifiers();
+    }
+
+    @Override
+    public String toString() {
+        return String.format("Range(lo=0x%05x hi=0x%05x %s %s:%d)", lo, hi, constructClassAndMethodNameWithParams(), methodEntry.getFullFileName(), line);
+    }
+
+    public String getFileName() {
+        return methodEntry.getFileName();
+    }
+
+    public MethodEntry getMethodEntry() {
+        return methodEntry;
+    }
+
+    public abstract SubRange getFirstCallee();
+
+    public abstract boolean isLeaf();
+
+    public boolean includesInlineRanges() {
+        SubRange child = getFirstCallee();
+        while (child != null && child.isLeaf()) {
+            child = child.getSiblingCallee();
+        }
+        return child != null;
+    }
+
+    public int getDepth() {
+        return depth;
+    }
+
+    public HashMap<DebugLocalInfo, List<SubRange>> getVarRangeMap() {
+        MethodEntry calleeMethod;
+        if (isPrimary()) {
+            calleeMethod = getMethodEntry();
+        } else {
+            assert !isLeaf() : "should only be looking up var ranges for inlined calls";
+            calleeMethod = getFirstCallee().getMethodEntry();
+        }
+        HashMap<DebugLocalInfo, List<SubRange>> varRangeMap = new HashMap<>();
+        if (calleeMethod.getThisParam() != null) {
+            varRangeMap.put(calleeMethod.getThisParam(), new ArrayList<>());
+        }
+        for (int i = 0; i < calleeMethod.getParamCount(); i++) {
+            varRangeMap.put(calleeMethod.getParam(i), new ArrayList<>());
+        }
+        for (int i = 0; i < calleeMethod.getLocalCount(); i++) {
+            varRangeMap.put(calleeMethod.getLocal(i), new ArrayList<>());
+        }
+        return updateVarRangeMap(varRangeMap);
+    }
+
+    public HashMap<DebugLocalInfo, List<SubRange>> updateVarRangeMap(HashMap<DebugLocalInfo, List<SubRange>> varRangeMap) {
+        // leaf subranges of the current range may provide values for param or local vars
+        // of this range's method. find them and index the range so that we can identify
+        // both the local/param and the associated range.
+        SubRange subRange = this.getFirstCallee();
+        while (subRange != null) {
+            addVarRanges(subRange, varRangeMap);
+            subRange = subRange.siblingCallee;
+        }
+        return varRangeMap;
+    }
+
+    public void addVarRanges(SubRange subRange, HashMap<DebugLocalInfo, List<SubRange>> varRangeMap) {
+        int localValueCount = subRange.getLocalValueCount();
+        for (int i = 0; i < localValueCount; i++) {
+            DebugLocalValueInfo localValueInfo = subRange.getLocalValue(i);
+            DebugLocalInfo local = subRange.getLocal(i);
+            if (local != null) {
+                switch (localValueInfo.localKind()) {
+                    case REGISTER:
+                    case STACKSLOT:
+                    case CONSTANT:
+                        List<SubRange> varRanges = varRangeMap.get(local);
+                        assert varRanges != null : "local not present in var to ranges map!";
+                        varRanges.add(subRange);
+                        break;
+                    case UNDEFINED:
+                        break;
+                }
+            }
+        }
+    }
+
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/SubRange.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/SubRange.java
new file mode 100644
index 000000000000..12e6458abbd6
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/SubRange.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2022, 2022, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.debugentry.range;
+
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider;
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocalInfo;
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocalValueInfo;
+import com.oracle.objectfile.runtime.debugentry.MethodEntry;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public abstract class SubRange extends Range {
+    /**
+     * The root of the call tree the subrange belongs to.
+     */
+    private final PrimaryRange primary;
+    /**
+     * The range for the caller or the primary range when this range is for top level method code.
+     */
+    protected Range caller;
+    /**
+     * A link to a sibling callee, i.e., a range sharing the same caller with this range.
+     */
+    protected SubRange siblingCallee;
+    /**
+     * Values for the associated method's local and parameter variables that are available or,
+     * alternatively, marked as invalid in this range.
+     */
+    private List<DebugLocalValueInfo> localValueInfos;
+    /**
+     * The set of local or parameter variables with which each corresponding local value in field
+     * localValueInfos is associated. Local values which are associated with the same local or
+     * parameter variable will share the same reference in the corresponding array entries. Local
+     * values with which no local variable can be associated will have a null reference in the
+     * corresponding array. The latter case can happen when a local value has an invalid slot or
+     * when a local value that maps to a parameter slot has a different name or type to the
+     * parameter.
+     */
+    private List<DebugLocalInfo> localInfos;
+
+    @SuppressWarnings("this-escape")
+    protected SubRange(MethodEntry methodEntry, long lo, long hi, int line, PrimaryRange primary, Range caller) {
+        super(methodEntry, lo, hi, line, (caller == null ? 0 : caller.depth + 1));
+        this.caller = caller;
+        if (caller != null) {
+            caller.addCallee(this);
+        }
+        assert primary != null;
+        this.primary = primary;
+    }
+
+    public Range getPrimary() {
+        return primary;
+    }
+
+    @Override
+    public boolean isPrimary() {
+        return false;
+    }
+
+    public Range getCaller() {
+        return caller;
+    }
+
+    @Override
+    public abstract SubRange getFirstCallee();
+
+    @Override
+    public abstract boolean isLeaf();
+
+    public int getLocalValueCount() {
+        return localValueInfos.size();
+    }
+
+    public DebugLocalValueInfo getLocalValue(int i) {
+        assert i >= 0 && i < localValueInfos.size() : "bad index";
+        return localValueInfos.get(i);
+    }
+
+    public DebugLocalInfo getLocal(int i) {
+        assert i >= 0 && i < localInfos.size() : "bad index";
+        return localInfos.get(i);
+    }
+
+    public void setLocalValueInfo(List<DebugLocalValueInfo> localValueInfos) {
+        this.localValueInfos = localValueInfos;
+        this.localInfos = new ArrayList<>();
+        // set up mapping from local values to local variables
+        for (DebugLocalValueInfo localValueInfo : localValueInfos) {
+            localInfos.add(methodEntry.recordLocal(localValueInfo));
+        }
+    }
+
+    public DebugLocalValueInfo lookupValue(DebugLocalInfo local) {
+        for (int i = 0; i < localValueInfos.size(); i++) {
+            if (getLocal(i).equals(local)) {
+                return getLocalValue(i);
+            }
+        }
+        return null;
+    }
+
+    public SubRange getSiblingCallee() {
+        return siblingCallee;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfAbbrevSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfAbbrevSectionImpl.java
new file mode 100644
index 000000000000..400346fa6724
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfAbbrevSectionImpl.java
@@ -0,0 +1,344 @@
+/*
+ * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.dwarf;
+
+import com.oracle.objectfile.runtime.dwarf.RuntimeDwarfDebugInfo.AbbrevCode;
+import com.oracle.objectfile.runtime.dwarf.constants.DwarfAttribute;
+import com.oracle.objectfile.runtime.dwarf.constants.DwarfForm;
+import com.oracle.objectfile.runtime.dwarf.constants.DwarfHasChildren;
+import com.oracle.objectfile.runtime.dwarf.constants.DwarfSectionName;
+import com.oracle.objectfile.runtime.dwarf.constants.DwarfTag;
+import jdk.graal.compiler.debug.DebugContext;
+
+public class RuntimeDwarfAbbrevSectionImpl extends RuntimeDwarfSectionImpl {
+
+    public RuntimeDwarfAbbrevSectionImpl(RuntimeDwarfDebugInfo dwarfSections) {
+        // abbrev section depends on ranges section
+        super(dwarfSections, DwarfSectionName.DW_ABBREV_SECTION, DwarfSectionName.DW_FRAME_SECTION);
+    }
+
+    @Override
+    public void createContent() {
+        assert !contentByteArrayCreated();
+
+        int pos = 0;
+        pos = writeAbbrevs(null, null, pos);
+
+        byte[] buffer = new byte[pos];
+        super.setContent(buffer);
+    }
+
+    @Override
+    public void writeContent(DebugContext context) {
+        assert contentByteArrayCreated();
+
+        byte[] buffer = getContent();
+        int size = buffer.length;
+        int pos = 0;
+
+        enableLog(context, pos);
+
+        pos = writeAbbrevs(context, buffer, pos);
+
+        assert pos == size;
+    }
+
+    public int writeAbbrevs(DebugContext context, byte[] buffer, int p) {
+        int pos = p;
+        // Level 0
+        pos = writeMethodCompileUnitAbbrev(context, buffer, pos);
+
+        // Level 1
+        pos = writePrimitiveTypeAbbrev(context, buffer, pos);
+        pos = writeTypedefAbbrev(context, buffer, pos);
+        pos = writeTypedefPointerAbbrev(context, buffer, pos);
+        pos = writeMethodLocationAbbrevs(context, buffer, pos);
+        pos = writeAbstractInlineMethodAbbrev(context, buffer, pos);
+
+        // Level 2 + inline depth
+        pos = writeInlinedSubroutineAbbrev(buffer, pos);
+        pos = writeParameterLocationAbbrevs(context, buffer, pos);
+        pos = writeLocalLocationAbbrevs(context, buffer, pos);
+
+        /* write a null abbrev to terminate the sequence */
+        pos = writeNullAbbrev(context, buffer, pos);
+        return pos;
+    }
+
+    private int writeAttrType(DwarfAttribute attribute, byte[] buffer, int pos) {
+        return writeULEB(attribute.value(), buffer, pos);
+    }
+
+    private int writeAttrForm(DwarfForm dwarfForm, byte[] buffer, int pos) {
+        return writeULEB(dwarfForm.value(), buffer, pos);
+    }
+
+    private int writeHasChildren(DwarfHasChildren hasChildren, byte[] buffer, int pos) {
+        return writeByte(hasChildren.value(), buffer, pos);
+    }
+
+    private int writeMethodCompileUnitAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
+        int pos = p;
+        pos = writeAbbrevCode(AbbrevCode.METHOD_UNIT, buffer, pos);
+        pos = writeTag(DwarfTag.DW_TAG_compile_unit, buffer, pos);
+        pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_yes, buffer, pos);
+        // pos = writeAttrType(DwarfAttribute.DW_AT_producer, buffer, pos);
+        // pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_language, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_use_UTF8, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_comp_dir, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_low_pc, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_addr, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_hi_pc, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_addr, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_stmt_list, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_sec_offset, buffer, pos);
+        /*
+         * Now terminate.
+         */
+        pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos);
+        return pos;
+    }
+
+    private int writePrimitiveTypeAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
+        int pos = p;
+        pos = writeAbbrevCode(AbbrevCode.PRIMITIVE_TYPE, buffer, pos);
+        pos = writeTag(DwarfTag.DW_TAG_base_type, buffer, pos);
+        pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_no, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_byte_size, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_bit_size, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_encoding, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos);
+        /*
+         * Now terminate.
+         */
+        pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos);
+        return pos;
+    }
+
+
+
+    private int writeTypedefAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
+        int pos = p;
+        /* A pointer to the class struct type. */
+        pos = writeAbbrevCode(AbbrevCode.TYPEDEF, buffer, pos);
+        pos = writeTag(DwarfTag.DW_TAG_typedef, buffer, pos);
+        pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_no, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos);
+        /*
+         * Now terminate.
+         */
+        pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos);
+        return pos;
+    }
+
+    private int writeTypedefPointerAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
+        int pos = p;
+        /* A pointer to a typedef. */
+        pos = writeAbbrevCode(AbbrevCode.TYPEDEF_POINTER, buffer, pos);
+        pos = writeTag(DwarfTag.DW_TAG_pointer_type, buffer, pos);
+        pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_no, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_byte_size, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_type, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_ref4, buffer, pos);
+        /*
+         * Now terminate.
+         */
+        pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos);
+        return pos;
+    }
+
+
+    private int writeMethodLocationAbbrevs(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
+        int pos = p;
+        pos = writeMethodLocationAbbrev(context, AbbrevCode.METHOD_LOCATION, buffer, pos);
+        pos = writeMethodLocationAbbrev(context, AbbrevCode.METHOD_LOCATION_STATIC, buffer, pos);
+        return pos;
+    }
+
+    private int writeMethodLocationAbbrev(@SuppressWarnings("unused") DebugContext context, AbbrevCode abbrevCode, byte[] buffer, int p) {
+        int pos = p;
+        pos = writeAbbrevCode(abbrevCode, buffer, pos);
+        pos = writeTag(DwarfTag.DW_TAG_subprogram, buffer, pos);
+        pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_yes, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_external, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos);
+//        pos = writeAttrType(DwarfAttribute.DW_AT_decl_file, buffer, pos);
+//        pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos);
+//        pos = writeAttrType(DwarfAttribute.DW_AT_decl_line, buffer, pos);
+//        pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos);
+//        pos = writeAttrType(DwarfAttribute.DW_AT_linkage_name, buffer, pos);
+//        pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_low_pc, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_addr, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_hi_pc, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_addr, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_type, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_ref_addr, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_artificial, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_accessibility, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos);
+        /* This is not in DWARF2 */
+        // pos = writeAttrType(DW_AT_virtuality, buffer, pos);
+        // pos = writeAttrForm(DW_FORM_data1, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_containing_type, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_ref_addr, buffer, pos);
+        if (abbrevCode == AbbrevCode.METHOD_LOCATION) {
+            pos = writeAttrType(DwarfAttribute.DW_AT_object_pointer, buffer, pos);
+            pos = writeAttrForm(DwarfForm.DW_FORM_ref4, buffer, pos);
+        }
+        /*
+         * Now terminate.
+         */
+        pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos);
+        return pos;
+    }
+
+    private int writeAbstractInlineMethodAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
+        int pos = p;
+        pos = writeAbbrevCode(AbbrevCode.ABSTRACT_INLINE_METHOD, buffer, pos);
+        pos = writeTag(DwarfTag.DW_TAG_subprogram, buffer, pos);
+        pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_yes, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_inline, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_external, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_type, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_ref_addr, buffer, pos);
+        /*
+         * Now terminate.
+         */
+        pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos);
+        return pos;
+    }
+
+
+    private int writeInlinedSubroutineAbbrev(byte[] buffer, int p) {
+        int pos = p;
+        pos = writeAbbrevCode(AbbrevCode.INLINED_SUBROUTINE, buffer, pos);
+        pos = writeTag(DwarfTag.DW_TAG_inlined_subroutine, buffer, pos);
+        pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_yes, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_abstract_origin, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_ref4, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_low_pc, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_addr, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_hi_pc, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_addr, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_call_file, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_data4, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_call_line, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_data4, buffer, pos);
+        /* Now terminate. */
+        pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos);
+        return pos;
+    }
+
+    private int writeParameterLocationAbbrevs(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
+        int pos = p;
+        pos = writeParameterLocationAbbrev(context, AbbrevCode.METHOD_PARAMETER_LOCATION_1, buffer, pos);
+        pos = writeParameterLocationAbbrev(context, AbbrevCode.METHOD_PARAMETER_LOCATION_2, buffer, pos);
+        return pos;
+    }
+
+    private int writeLocalLocationAbbrevs(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
+        int pos = p;
+        pos = writeLocalLocationAbbrev(context, AbbrevCode.METHOD_LOCAL_LOCATION_1, buffer, pos);
+        pos = writeLocalLocationAbbrev(context, AbbrevCode.METHOD_LOCAL_LOCATION_2, buffer, pos);
+        return pos;
+    }
+
+    private int writeParameterLocationAbbrev(@SuppressWarnings("unused") DebugContext context, AbbrevCode abbrevCode, byte[] buffer, int p) {
+        int pos = p;
+        pos = writeAbbrevCode(abbrevCode, buffer, pos);
+        pos = writeTag(DwarfTag.DW_TAG_formal_parameter, buffer, pos);
+        pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_no, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_type, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_ref_addr, buffer, pos);
+        if (abbrevCode == AbbrevCode.METHOD_PARAMETER_LOCATION_2) {
+            pos = writeAttrType(DwarfAttribute.DW_AT_location, buffer, pos);
+            pos = writeAttrForm(DwarfForm.DW_FORM_sec_offset, buffer, pos);
+        }
+        /*
+         * Now terminate.
+         */
+        pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos);
+        return pos;
+    }
+
+    private int writeLocalLocationAbbrev(@SuppressWarnings("unused") DebugContext context, AbbrevCode abbrevCode, byte[] buffer, int p) {
+        int pos = p;
+        pos = writeAbbrevCode(abbrevCode, buffer, pos);
+        pos = writeTag(DwarfTag.DW_TAG_variable, buffer, pos);
+        pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_no, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_type, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_ref_addr, buffer, pos);
+        if (abbrevCode == AbbrevCode.METHOD_LOCAL_LOCATION_2) {
+            pos = writeAttrType(DwarfAttribute.DW_AT_location, buffer, pos);
+            pos = writeAttrForm(DwarfForm.DW_FORM_sec_offset, buffer, pos);
+        }
+        /*
+         * Now terminate.
+         */
+        pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos);
+        return pos;
+    }
+
+    private int writeNullAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
+        int pos = p;
+        pos = writeAbbrevCode(AbbrevCode.NULL, buffer, pos);
+        return pos;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfDebugInfo.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfDebugInfo.java
new file mode 100644
index 000000000000..c6ed71782697
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfDebugInfo.java
@@ -0,0 +1,525 @@
+/*
+ * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.dwarf;
+
+import com.oracle.objectfile.elf.ELFMachine;
+import com.oracle.objectfile.runtime.RuntimeDebugInfoBase;
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocalInfo;
+import com.oracle.objectfile.runtime.debugentry.CompiledMethodEntry;
+import com.oracle.objectfile.runtime.debugentry.MethodEntry;
+import com.oracle.objectfile.runtime.debugentry.StructureTypeEntry;
+import com.oracle.objectfile.runtime.debugentry.TypeEntry;
+import com.oracle.objectfile.runtime.debugentry.range.Range;
+import com.oracle.objectfile.runtime.dwarf.constants.DwarfLanguage;
+import org.graalvm.collections.EconomicMap;
+
+import java.nio.ByteOrder;
+import java.util.HashMap;
+
+/**
+ * A class that models the debug info in an organization that facilitates generation of the required
+ * DWARF sections. It groups common data and behaviours for use by the various subclasses of class
+ * DwarfSectionImpl that take responsibility for generating content for a specific section type.
+ */
+public class RuntimeDwarfDebugInfo extends RuntimeDebugInfoBase {
+    /*
+     * Define all the abbrev section codes we need for our DIEs.
+     */
+    enum AbbrevCode {
+        /* null marker which must come first as its ordinal has to equal zero */
+        NULL,
+        /* Level 0 DIEs. */
+        METHOD_UNIT,
+        /* Level 1 DIEs. */
+        PRIMITIVE_TYPE,
+        TYPEDEF,
+        TYPEDEF_POINTER,
+        METHOD_LOCATION,
+        METHOD_LOCATION_STATIC,
+        ABSTRACT_INLINE_METHOD,
+        /* Level 2 DIEs. */
+        /* Level 2+K DIEs (where inline depth K >= 0) */
+        INLINED_SUBROUTINE,
+        METHOD_PARAMETER_LOCATION_1,
+        METHOD_PARAMETER_LOCATION_2,
+        METHOD_LOCAL_LOCATION_1,
+        METHOD_LOCAL_LOCATION_2,
+    }
+
+    /**
+     * This field defines the value used for the DW_AT_language attribute of compile units.
+     *
+     */
+    public static final DwarfLanguage LANG_ENCODING = DwarfLanguage.DW_LANG_Java;
+
+    /* Register constants for AArch64. */
+    public static final byte rheapbase_aarch64 = (byte) 27;
+    public static final byte rthread_aarch64 = (byte) 28;
+    /* Register constants for x86. */
+    public static final byte rheapbase_x86 = (byte) 14;
+    public static final byte rthread_x86 = (byte) 15;
+
+    /* Full byte/word values. */
+    private final RuntimeDwarfStrSectionImpl dwarfStrSection;
+    private final RuntimeDwarfAbbrevSectionImpl dwarfAbbrevSection;
+    private final RuntimeDwarfInfoSectionImpl dwarfInfoSection;
+    private final RuntimeDwarfLocSectionImpl dwarfLocSection;
+    private final RuntimeDwarfLineSectionImpl dwarfLineSection;
+    private final RuntimeDwarfFrameSectionImpl dwarfFameSection;
+    public final ELFMachine elfMachine;
+    /**
+     * Register used to hold the heap base.
+     */
+    private final byte heapbaseRegister;
+    /**
+     * Register used to hold the current thread.
+     */
+    private final byte threadRegister;
+
+    /**
+     * A collection of properties associated with each generated type record indexed by type name.
+     * n.b. this collection includes entries for the structure types used to define the object and
+     * array headers which do not have an associated TypeEntry.
+     */
+    private final EconomicMap<TypeEntry, DwarfTypeProperties> typePropertiesIndex = EconomicMap.create();
+
+    /**
+     * A collection of method properties associated with each generated method record.
+     */
+    private final EconomicMap<MethodEntry, DwarfMethodProperties> methodPropertiesIndex = EconomicMap.create();
+
+    /**
+     * A collection of local variable properties associated with an inlined subrange.
+     */
+    private final EconomicMap<Range, DwarfLocalProperties> rangeLocalPropertiesIndex = EconomicMap.create();
+
+    @SuppressWarnings("this-escape")
+    public RuntimeDwarfDebugInfo(ELFMachine elfMachine, ByteOrder byteOrder) {
+        super(byteOrder);
+        this.elfMachine = elfMachine;
+        dwarfStrSection = new RuntimeDwarfStrSectionImpl(this);
+        dwarfAbbrevSection = new RuntimeDwarfAbbrevSectionImpl(this);
+        dwarfInfoSection = new RuntimeDwarfInfoSectionImpl(this);
+        dwarfLocSection = new RuntimeDwarfLocSectionImpl(this);
+        dwarfLineSection = new RuntimeDwarfLineSectionImpl(this);
+
+        if (elfMachine == ELFMachine.AArch64) {
+            dwarfFameSection = new RuntimeDwarfFrameSectionImplAArch64(this);
+            this.heapbaseRegister = rheapbase_aarch64;
+            this.threadRegister = rthread_aarch64;
+        } else {
+            dwarfFameSection = new RuntimeDwarfFrameSectionImplX86_64(this);
+            this.heapbaseRegister = rheapbase_x86;
+            this.threadRegister = rthread_x86;
+        }
+    }
+
+    public RuntimeDwarfStrSectionImpl getStrSectionImpl() {
+        return dwarfStrSection;
+    }
+
+    public RuntimeDwarfAbbrevSectionImpl getAbbrevSectionImpl() {
+        return dwarfAbbrevSection;
+    }
+
+    public RuntimeDwarfFrameSectionImpl getFrameSectionImpl() {
+        return dwarfFameSection;
+    }
+
+    public RuntimeDwarfInfoSectionImpl getInfoSectionImpl() {
+        return dwarfInfoSection;
+    }
+
+    public RuntimeDwarfLocSectionImpl getLocSectionImpl() {
+        return dwarfLocSection;
+    }
+
+    public RuntimeDwarfLineSectionImpl getLineSectionImpl() {
+        return dwarfLineSection;
+    }
+
+    public byte getHeapbaseRegister() {
+        return heapbaseRegister;
+    }
+
+    public byte getThreadRegister() {
+        return threadRegister;
+    }
+
+    /**
+     * A class used to associate properties with a specific type, the most important one being its
+     * index in the info section.
+     */
+    static class DwarfTypeProperties {
+        /**
+         * Index in debug_info section of type declaration for this class.
+         */
+        private int typeInfoIndex;
+        /**
+         * Index in debug_info section of indirect type declaration for this class.
+         *
+         * this is normally just the same as the index of the normal type declaration, however, when
+         * oops are stored in static and instance fields as offsets from the heapbase register gdb
+         * needs to be told how to convert these oops to raw addresses and this requires attaching a
+         * data_location address translation expression to an indirect type that wraps the object
+         * layout type. so, with that encoding this field will identify the wrapper type whenever
+         * the original type is an object, interface or array layout. primitive types and header
+         * types do not need translating.
+         */
+        private int indirectTypeInfoIndex;
+        /**
+         * The type entry with which these properties are associated.
+         */
+        private final TypeEntry typeEntry;
+
+        public int getTypeInfoIndex() {
+            return typeInfoIndex;
+        }
+
+        public void setTypeInfoIndex(int typeInfoIndex) {
+            this.typeInfoIndex = typeInfoIndex;
+        }
+
+        public int getIndirectTypeInfoIndex() {
+            return indirectTypeInfoIndex;
+        }
+
+        public void setIndirectTypeInfoIndex(int typeInfoIndex) {
+            this.indirectTypeInfoIndex = typeInfoIndex;
+        }
+
+        public TypeEntry getTypeEntry() {
+            return typeEntry;
+        }
+
+        DwarfTypeProperties(TypeEntry typeEntry) {
+            this.typeEntry = typeEntry;
+            this.typeInfoIndex = -1;
+            this.indirectTypeInfoIndex = -1;
+        }
+
+    }
+
+    /**
+     * A class used to associate extra properties with an instance class type.
+     */
+
+    static class DwarfClassProperties extends DwarfTypeProperties {
+        /**
+         * Index of the class entry's compile unit in the debug_info section.
+         */
+        private int cuIndex;
+        /**
+         * Index of the class entry's class_layout DIE in the debug_info section.
+         */
+        private int layoutIndex;
+        /**
+         * Index of the class entry's indirect layout DIE in the debug_info section.
+         */
+        private int indirectLayoutIndex;
+        /**
+         * Index of the class entry's code ranges data in the debug_ranges section.
+         */
+        private int codeRangesIndex;
+        /**
+         * Index of the class entry's line data in the debug_line section.
+         */
+        private int lineIndex;
+        /**
+         * Size of the class entry's prologue in the debug_line section.
+         */
+        private int linePrologueSize;
+        /**
+         * Map from field names to info section index for the field declaration.
+         */
+        private EconomicMap<String, Integer> fieldDeclarationIndex;
+
+        DwarfClassProperties(StructureTypeEntry entry) {
+            super(entry);
+            this.cuIndex = -1;
+            this.layoutIndex = -1;
+            this.indirectLayoutIndex = -1;
+            this.codeRangesIndex = -1;
+            this.lineIndex = -1;
+            this.linePrologueSize = -1;
+            fieldDeclarationIndex = null;
+        }
+    }
+
+    /**
+     * A class used to associate properties with a specific method.
+     */
+    static class DwarfMethodProperties {
+        /**
+         * The index in the info section at which the method's declaration resides.
+         */
+        private int methodDeclarationIndex;
+
+        /**
+         * Per class map that identifies the info declarations for a top level method declaration or
+         * an abstract inline method declaration.
+         */
+        private HashMap<CompiledMethodEntry, DwarfLocalProperties> localPropertiesMap;
+
+        /**
+         * Per class map that identifies the info declaration for an abstract inline method.
+         */
+        private HashMap<CompiledMethodEntry, Integer> abstractInlineMethodIndex;
+
+        DwarfMethodProperties() {
+            methodDeclarationIndex = -1;
+            localPropertiesMap = null;
+            abstractInlineMethodIndex = null;
+        }
+
+        public int getMethodDeclarationIndex() {
+            assert methodDeclarationIndex >= 0 : "unset declaration index";
+            return methodDeclarationIndex;
+        }
+
+        public void setMethodDeclarationIndex(int pos) {
+            assert methodDeclarationIndex == -1 || methodDeclarationIndex == pos : "bad declaration index";
+            methodDeclarationIndex = pos;
+        }
+
+        public DwarfLocalProperties getLocalProperties(CompiledMethodEntry compiledEntry) {
+            if (localPropertiesMap == null) {
+                localPropertiesMap = new HashMap<>();
+            }
+            DwarfLocalProperties localProperties = localPropertiesMap.get(compiledEntry);
+            if (localProperties == null) {
+                localProperties = new DwarfLocalProperties();
+                localPropertiesMap.put(compiledEntry, localProperties);
+            }
+            return localProperties;
+        }
+
+        public void setAbstractInlineMethodIndex(CompiledMethodEntry compiledEntry, int pos) {
+            if (abstractInlineMethodIndex == null) {
+                abstractInlineMethodIndex = new HashMap<>();
+            }
+            // replace but check it did not change
+            Integer val = abstractInlineMethodIndex.put(compiledEntry, pos);
+            assert val == null || val == pos;
+        }
+
+        public int getAbstractInlineMethodIndex(CompiledMethodEntry compiledEntry) {
+            // should be set before we get here but an NPE will guard that
+            return abstractInlineMethodIndex.get(compiledEntry);
+        }
+    }
+
+    private DwarfTypeProperties addTypeProperties(TypeEntry typeEntry) {
+        assert typeEntry != null;
+        assert !typeEntry.isClass();
+        assert typePropertiesIndex.get(typeEntry) == null;
+        DwarfTypeProperties typeProperties = new DwarfTypeProperties(typeEntry);
+        this.typePropertiesIndex.put(typeEntry, typeProperties);
+        return typeProperties;
+    }
+
+    private DwarfClassProperties addClassProperties(StructureTypeEntry entry) {
+        assert typePropertiesIndex.get(entry) == null;
+        DwarfClassProperties classProperties = new DwarfClassProperties(entry);
+        this.typePropertiesIndex.put(entry, classProperties);
+        return classProperties;
+    }
+
+    private DwarfMethodProperties addMethodProperties(MethodEntry methodEntry) {
+        assert methodPropertiesIndex.get(methodEntry) == null;
+        DwarfMethodProperties methodProperties = new DwarfMethodProperties();
+        this.methodPropertiesIndex.put(methodEntry, methodProperties);
+        return methodProperties;
+    }
+
+    private DwarfTypeProperties lookupTypeProperties(TypeEntry typeEntry) {
+        DwarfTypeProperties typeProperties = typePropertiesIndex.get(typeEntry);
+        if (typeProperties == null) {
+            typeProperties = addTypeProperties(typeEntry);
+        }
+        return typeProperties;
+    }
+
+    private DwarfClassProperties lookupClassProperties(StructureTypeEntry entry) {
+        DwarfTypeProperties typeProperties = typePropertiesIndex.get(entry);
+        assert typeProperties == null || typeProperties instanceof DwarfClassProperties;
+        DwarfClassProperties classProperties = (DwarfClassProperties) typeProperties;
+        if (classProperties == null) {
+            classProperties = addClassProperties(entry);
+        }
+        return classProperties;
+    }
+
+    private DwarfMethodProperties lookupMethodProperties(MethodEntry methodEntry) {
+        DwarfMethodProperties methodProperties = methodPropertiesIndex.get(methodEntry);
+        if (methodProperties == null) {
+            methodProperties = addMethodProperties(methodEntry);
+        }
+        return methodProperties;
+    }
+
+    void setTypeIndex(TypeEntry typeEntry, int idx) {
+        assert idx >= 0;
+        DwarfTypeProperties typeProperties = lookupTypeProperties(typeEntry);
+        assert typeProperties.getTypeInfoIndex() == -1 || typeProperties.getTypeInfoIndex() == idx;
+        typeProperties.setTypeInfoIndex(idx);
+    }
+
+    int getTypeIndex(TypeEntry typeEntry) {
+        DwarfTypeProperties typeProperties = lookupTypeProperties(typeEntry);
+        return getTypeIndex(typeProperties);
+    }
+
+    int getTypeIndex(DwarfTypeProperties typeProperties) {
+        assert typeProperties.getTypeInfoIndex() >= 0;
+        return typeProperties.getTypeInfoIndex();
+    }
+
+    void setIndirectTypeIndex(TypeEntry typeEntry, int idx) {
+        assert idx >= 0;
+        DwarfTypeProperties typeProperties = lookupTypeProperties(typeEntry);
+        assert typeProperties.getIndirectTypeInfoIndex() == -1 || typeProperties.getIndirectTypeInfoIndex() == idx;
+        typeProperties.setIndirectTypeInfoIndex(idx);
+    }
+
+    int getIndirectTypeIndex(TypeEntry typeEntry) {
+        DwarfTypeProperties typeProperties = lookupTypeProperties(typeEntry);
+        return getIndirectTypeIndex(typeProperties);
+    }
+
+    int getIndirectTypeIndex(DwarfTypeProperties typeProperties) {
+        assert typeProperties.getIndirectTypeInfoIndex() >= 0;
+        return typeProperties.getIndirectTypeInfoIndex();
+    }
+
+    public void setFieldDeclarationIndex(StructureTypeEntry entry, String fieldName, int pos) {
+        DwarfClassProperties classProperties;
+        classProperties = lookupClassProperties(entry);
+        assert classProperties.getTypeEntry() == entry;
+        EconomicMap<String, Integer> fieldDeclarationIndex = classProperties.fieldDeclarationIndex;
+        if (fieldDeclarationIndex == null) {
+            classProperties.fieldDeclarationIndex = fieldDeclarationIndex = EconomicMap.create();
+        }
+        if (fieldDeclarationIndex.get(fieldName) != null) {
+            assert fieldDeclarationIndex.get(fieldName) == pos : entry.getTypeName() + fieldName;
+        } else {
+            fieldDeclarationIndex.put(fieldName, pos);
+        }
+    }
+
+    public int getFieldDeclarationIndex(StructureTypeEntry entry, String fieldName) {
+        DwarfClassProperties classProperties;
+        classProperties = lookupClassProperties(entry);
+        assert classProperties.getTypeEntry() == entry;
+        EconomicMap<String, Integer> fieldDeclarationIndex = classProperties.fieldDeclarationIndex;
+        assert fieldDeclarationIndex != null : fieldName;
+        assert fieldDeclarationIndex.get(fieldName) != null : entry.getTypeName() + fieldName;
+        return fieldDeclarationIndex.get(fieldName);
+    }
+
+    public void setMethodDeclarationIndex(MethodEntry methodEntry, int pos) {
+        DwarfMethodProperties methodProperties = lookupMethodProperties(methodEntry);
+        methodProperties.setMethodDeclarationIndex(pos);
+    }
+
+    public int getMethodDeclarationIndex(MethodEntry methodEntry) {
+        DwarfMethodProperties methodProperties = lookupMethodProperties(methodEntry);
+        return methodProperties.getMethodDeclarationIndex();
+    }
+
+    /**
+     * A class used to associate properties with a specific param or local whether top level or
+     * inline.
+     */
+
+    static final class DwarfLocalProperties {
+        private EconomicMap<DebugLocalInfo, Integer> locals;
+
+        private DwarfLocalProperties() {
+            locals = EconomicMap.create();
+        }
+
+        int getIndex(DebugLocalInfo localInfo) {
+            return locals.get(localInfo);
+        }
+
+        void setIndex(DebugLocalInfo localInfo, int index) {
+            if (locals.get(localInfo) != null) {
+                assert locals.get(localInfo) == index;
+            } else {
+                locals.put(localInfo, index);
+            }
+        }
+    }
+
+    public void setAbstractInlineMethodIndex(CompiledMethodEntry compiledEntry, MethodEntry methodEntry, int pos) {
+        lookupMethodProperties(methodEntry).setAbstractInlineMethodIndex(compiledEntry, pos);
+    }
+
+    public int getAbstractInlineMethodIndex(CompiledMethodEntry compiledEntry, MethodEntry methodEntry) {
+        return lookupMethodProperties(methodEntry).getAbstractInlineMethodIndex(compiledEntry);
+    }
+
+    private DwarfLocalProperties addRangeLocalProperties(Range range) {
+        DwarfLocalProperties localProperties = new DwarfLocalProperties();
+        rangeLocalPropertiesIndex.put(range, localProperties);
+        return localProperties;
+    }
+
+    public DwarfLocalProperties lookupLocalProperties(CompiledMethodEntry compiledEntry, MethodEntry methodEntry) {
+        return lookupMethodProperties(methodEntry).getLocalProperties(compiledEntry);
+    }
+
+    public void setMethodLocalIndex(CompiledMethodEntry compiledEntry, MethodEntry methodEntry, DebugLocalInfo localInfo, int index) {
+        DwarfLocalProperties localProperties = lookupLocalProperties(compiledEntry, methodEntry);
+        localProperties.setIndex(localInfo, index);
+    }
+
+    public int getMethodLocalIndex(CompiledMethodEntry compiledEntry, MethodEntry methodEntry, DebugLocalInfo localInfo) {
+        DwarfLocalProperties localProperties = lookupLocalProperties(compiledEntry, methodEntry);
+        assert localProperties != null : "get of non-existent local index";
+        int index = localProperties.getIndex(localInfo);
+        assert index >= 0 : "get of local index before it was set";
+        return index;
+    }
+
+    public void setRangeLocalIndex(Range range, DebugLocalInfo localInfo, int index) {
+        DwarfLocalProperties rangeProperties = rangeLocalPropertiesIndex.get(range);
+        if (rangeProperties == null) {
+            rangeProperties = addRangeLocalProperties(range);
+        }
+        rangeProperties.setIndex(localInfo, index);
+    }
+
+    public int getRangeLocalIndex(Range range, DebugLocalInfo localinfo) {
+        DwarfLocalProperties rangeProperties = rangeLocalPropertiesIndex.get(range);
+        assert rangeProperties != null : "get of non-existent local index";
+        int index = rangeProperties.getIndex(localinfo);
+        assert index >= 0 : "get of local index before it was set";
+        return index;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImpl.java
new file mode 100644
index 000000000000..22615c8b2984
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImpl.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.dwarf;
+
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider;
+import com.oracle.objectfile.runtime.debugentry.CompiledMethodEntry;
+import com.oracle.objectfile.runtime.debugentry.range.Range;
+import com.oracle.objectfile.runtime.dwarf.constants.DwarfFrameValue;
+import com.oracle.objectfile.runtime.dwarf.constants.DwarfSectionName;
+import jdk.graal.compiler.debug.DebugContext;
+
+import java.util.List;
+
+/**
+ * Section generic generator for debug_frame section.
+ */
+public abstract class RuntimeDwarfFrameSectionImpl extends RuntimeDwarfSectionImpl {
+
+    private static final int PADDING_NOPS_ALIGNMENT = 8;
+
+    private static final int CFA_CIE_id_default = -1;
+
+    public RuntimeDwarfFrameSectionImpl(RuntimeDwarfDebugInfo dwarfSections) {
+        // debug_frame section depends on debug_line section
+        super(dwarfSections, DwarfSectionName.DW_FRAME_SECTION, DwarfSectionName.DW_LINE_SECTION);
+    }
+
+    @Override
+    public void createContent() {
+        assert !contentByteArrayCreated();
+
+        int pos = 0;
+
+        /*
+         * The frame section contains one CIE at offset 0 followed by an FIE for each compiled
+         * method.
+         */
+        pos = writeCIE(null, pos);
+        pos = writeMethodFrames(null, pos);
+
+        byte[] buffer = new byte[pos];
+        super.setContent(buffer);
+    }
+
+    @Override
+    public void writeContent(DebugContext context) {
+        assert contentByteArrayCreated();
+
+        byte[] buffer = getContent();
+        int size = buffer.length;
+        int pos = 0;
+
+        enableLog(context, pos);
+
+        /*
+         * There are entries for the prologue region where the stack is being built, the method body
+         * region(s) where the code executes with a fixed size frame and the epilogue region(s)
+         * where the stack is torn down.
+         */
+        pos = writeCIE(buffer, pos);
+        pos = writeMethodFrames(buffer, pos);
+
+        if (pos != size) {
+            System.out.format("pos = 0x%x  size = 0x%x", pos, size);
+        }
+        assert pos == size;
+    }
+
+    private int writeCIE(byte[] buffer, int p) {
+        /*
+         * We only need a vanilla CIE with default fields because we have to have at least one the
+         * layout is:
+         *
+         * <ul>
+         *
+         * <li><code>uint32 : length ............... length of remaining fields in this CIE</code>
+         *
+         * <li><code>uint32 : CIE_id ................ unique id for CIE == 0xffffffff</code>
+         *
+         * <li><code>uint8 : version ................ == 1</code>
+         *
+         * <li><code>uint8[] : augmentation ......... == "" so always 1 byte</code>
+         *
+         * <li><code>ULEB : code_alignment_factor ... == 1 (could use 4 for Aarch64)</code>
+         *
+         * <li><code>ULEB : data_alignment_factor ... == -8</code>
+         *
+         * <li><code>byte : ret_addr reg id ......... x86_64 => 16 AArch64 => 30</code>
+         *
+         * <li><code>byte[] : initial_instructions .. includes pad to 8-byte boundary</code>
+         *
+         * </ul>
+         */
+        int pos = p;
+        int lengthPos = pos;
+        pos = writeInt(0, buffer, pos);
+        pos = writeInt(CFA_CIE_id_default, buffer, pos);
+        pos = writeCIEVersion(buffer, pos);
+        pos = writeByte((byte) 0, buffer, pos);
+        pos = writeULEB(1, buffer, pos);
+        pos = writeSLEB(-8, buffer, pos);
+        pos = writeByte((byte) getReturnPCIdx(), buffer, pos);
+        /*
+         * Write insns to set up empty frame.
+         */
+        pos = writeInitialInstructions(buffer, pos);
+        /*
+         * Pad to word alignment.
+         */
+        pos = writePaddingNops(buffer, pos);
+        patchLength(lengthPos, buffer, pos);
+        return pos;
+    }
+
+    private int writeCIEVersion(byte[] buffer, int pos) {
+        return writeByte(DwarfFrameValue.DW_CFA_CIE_version.value(), buffer, pos);
+    }
+
+    private int writeMethodFrame(CompiledMethodEntry compiledEntry, byte[] buffer, int p) {
+        int pos = p;
+        int lengthPos = pos;
+        Range range = compiledEntry.getPrimary();
+        long lo = range.getLo();
+        long hi = range.getHi();
+        pos = writeFDEHeader(lo, hi, buffer, pos);
+        pos = writeFDEs(compiledEntry.getFrameSize(), compiledEntry.getFrameSizeInfos(), buffer, pos);
+        pos = writePaddingNops(buffer, pos);
+        patchLength(lengthPos, buffer, pos);
+        return pos;
+    }
+
+    private int writeMethodFrames(byte[] buffer, int p) {
+        int pos = p;
+        pos = writeMethodFrame(dwarfSections.getCompiledMethod(), buffer, pos);
+        return pos;
+    }
+
+    protected abstract int writeFDEs(int frameSize, List<RuntimeDebugInfoProvider.DebugFrameSizeChange> frameSizeInfos, byte[] buffer, int pos);
+
+    private int writeFDEHeader(long lo, long hi, byte[] buffer, int p) {
+        /*
+         * We only need a vanilla FDE header with default fields the layout is:
+         *
+         * <ul>
+         *
+         * <li><code>uint32 : length ............ length of remaining fields in this FDE</code>
+         *
+         * <li><code>uint32 : CIE_offset ........ always 0 i.e. identifies our only CIE
+         * header</code>
+         *
+         * <li><code>uint64 : initial_location .. i.e. method lo address</code>
+         *
+         * <li><code>uint64 : address_range ..... i.e. method hi - lo</code>
+         *
+         * <li><code>byte[] : instructions ...... includes pad to 8-byte boundary</code>
+         *
+         * </ul>
+         */
+
+        int pos = p;
+        /* Dummy length. */
+        pos = writeInt(0, buffer, pos);
+        /* CIE_offset */
+        pos = writeInt(0, buffer, pos);
+        /* Initial address. */
+        pos = writeRelocatableCodeOffset(lo, buffer, pos);
+        /* Address range. */
+        return writeLong(hi - lo, buffer, pos);
+    }
+
+    private int writePaddingNops(byte[] buffer, int p) {
+        int pos = p;
+        while ((pos & (PADDING_NOPS_ALIGNMENT - 1)) != 0) {
+            pos = writeByte(DwarfFrameValue.DW_CFA_nop.value(), buffer, pos);
+        }
+        return pos;
+    }
+
+    protected int writeDefCFA(int register, int offset, byte[] buffer, int p) {
+        int pos = p;
+        pos = writeByte(DwarfFrameValue.DW_CFA_def_cfa.value(), buffer, pos);
+        pos = writeULEB(register, buffer, pos);
+        return writeULEB(offset, buffer, pos);
+    }
+
+    protected int writeDefCFAOffset(int offset, byte[] buffer, int p) {
+        int pos = p;
+        byte op = DwarfFrameValue.DW_CFA_def_cfa_offset.value();
+        pos = writeByte(op, buffer, pos);
+        return writeULEB(offset, buffer, pos);
+    }
+
+    protected int writeAdvanceLoc(int offset, byte[] buffer, int pos) {
+        if (offset <= 0x3f) {
+            return writeAdvanceLoc0((byte) offset, buffer, pos);
+        } else if (offset <= 0xff) {
+            return writeAdvanceLoc1((byte) offset, buffer, pos);
+        } else if (offset <= 0xffff) {
+            return writeAdvanceLoc2((short) offset, buffer, pos);
+        } else {
+            return writeAdvanceLoc4(offset, buffer, pos);
+        }
+    }
+
+    protected int writeAdvanceLoc0(byte offset, byte[] buffer, int pos) {
+        byte op = advanceLoc0Op(offset);
+        return writeByte(op, buffer, pos);
+    }
+
+    protected int writeAdvanceLoc1(byte offset, byte[] buffer, int p) {
+        int pos = p;
+        byte op = DwarfFrameValue.DW_CFA_advance_loc1.value();
+        pos = writeByte(op, buffer, pos);
+        return writeByte(offset, buffer, pos);
+    }
+
+    protected int writeAdvanceLoc2(short offset, byte[] buffer, int p) {
+        byte op = DwarfFrameValue.DW_CFA_advance_loc2.value();
+        int pos = p;
+        pos = writeByte(op, buffer, pos);
+        return writeShort(offset, buffer, pos);
+    }
+
+    protected int writeAdvanceLoc4(int offset, byte[] buffer, int p) {
+        byte op = DwarfFrameValue.DW_CFA_advance_loc4.value();
+        int pos = p;
+        pos = writeByte(op, buffer, pos);
+        return writeInt(offset, buffer, pos);
+    }
+
+    protected int writeOffset(int register, int offset, byte[] buffer, int p) {
+        byte op = offsetOp(register);
+        int pos = p;
+        pos = writeByte(op, buffer, pos);
+        return writeULEB(offset, buffer, pos);
+    }
+
+    protected int writeRestore(int register, byte[] buffer, int p) {
+        byte op = restoreOp(register);
+        int pos = p;
+        return writeByte(op, buffer, pos);
+    }
+
+    @SuppressWarnings("unused")
+    protected int writeRegister(int savedReg, int savedToReg, byte[] buffer, int p) {
+        int pos = p;
+        byte op = DwarfFrameValue.DW_CFA_register.value();
+        pos = writeByte(op, buffer, pos);
+        pos = writeULEB(savedReg, buffer, pos);
+        return writeULEB(savedToReg, buffer, pos);
+    }
+
+    protected abstract int getReturnPCIdx();
+
+    @SuppressWarnings("unused")
+    protected abstract int getSPIdx();
+
+    protected abstract int writeInitialInstructions(byte[] buffer, int pos);
+
+    private static byte offsetOp(int register) {
+        byte op = DwarfFrameValue.DW_CFA_offset.value();
+        return encodeOp(op, register);
+    }
+
+    private static byte restoreOp(int register) {
+        byte op = DwarfFrameValue.DW_CFA_restore.value();
+        return encodeOp(op, register);
+    }
+
+    private static byte advanceLoc0Op(int offset) {
+        byte op = DwarfFrameValue.DW_CFA_advance_loc.value();
+        return encodeOp(op, offset);
+    }
+
+    private static byte encodeOp(byte op, int value) {
+        assert (value >> 6) == 0;
+        return (byte) ((op << 6) | value);
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImplAArch64.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImplAArch64.java
new file mode 100644
index 000000000000..bbab12626a7b
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImplAArch64.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.dwarf;
+
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider;
+
+import java.util.List;
+
+/**
+ * AArch64-specific section generator for debug_frame section that knows details of AArch64
+ * registers and frame layout.
+ */
+public class RuntimeDwarfFrameSectionImplAArch64 extends RuntimeDwarfFrameSectionImpl {
+    public static final int CFA_FP_IDX = 29;
+    private static final int CFA_LR_IDX = 30;
+    private static final int CFA_SP_IDX = 31;
+    @SuppressWarnings("unused") private static final int CFA_PC_IDX = 32;
+
+    public RuntimeDwarfFrameSectionImplAArch64(RuntimeDwarfDebugInfo dwarfSections) {
+        super(dwarfSections);
+    }
+
+    @Override
+    public int getReturnPCIdx() {
+        return CFA_LR_IDX;
+    }
+
+    @Override
+    public int getSPIdx() {
+        return CFA_SP_IDX;
+    }
+
+    @Override
+    public int writeInitialInstructions(byte[] buffer, int p) {
+        int pos = p;
+        /*
+         * Invariant: CFA identifies last word of caller stack.
+         *
+         * So initial cfa is at rsp + 0:
+         *
+         * <ul>
+         *
+         * <li><code>def_cfa r31 (sp) offset 0</code>
+         *
+         * </ul>
+         */
+        pos = writeDefCFA(CFA_SP_IDX, 0, buffer, pos);
+        return pos;
+    }
+
+    @Override
+    protected int writeFDEs(int frameSize, List<RuntimeDebugInfoProvider.DebugFrameSizeChange> frameSizeInfos, byte[] buffer, int p) {
+        int pos = p;
+        int currentOffset = 0;
+        for (RuntimeDebugInfoProvider.DebugFrameSizeChange debugFrameSizeInfo : frameSizeInfos) {
+            int advance = debugFrameSizeInfo.getOffset() - currentOffset;
+            currentOffset += advance;
+            pos = writeAdvanceLoc(advance, buffer, pos);
+            if (debugFrameSizeInfo.getType() == RuntimeDebugInfoProvider.DebugFrameSizeChange.Type.EXTEND) {
+                /*
+                 * SP has been extended so rebase CFA using full frame.
+                 *
+                 * Invariant: CFA identifies last word of caller stack.
+                 */
+                pos = writeDefCFAOffset(frameSize, buffer, pos);
+                /*
+                 * Notify push of lr and fp to stack slots 1 and 2.
+                 *
+                 * Scaling by -8 is automatic.
+                 */
+                pos = writeOffset(CFA_LR_IDX, 1, buffer, pos);
+                pos = writeOffset(CFA_FP_IDX, 2, buffer, pos);
+            } else {
+                /*
+                 * SP will have been contracted so rebase CFA using empty frame.
+                 *
+                 * Invariant: CFA identifies last word of caller stack.
+                 */
+                pos = writeDefCFAOffset(0, buffer, pos);
+                /*
+                 * notify restore of fp and lr
+                 */
+                pos = writeRestore(CFA_FP_IDX, buffer, pos);
+                pos = writeRestore(CFA_LR_IDX, buffer, pos);
+            }
+        }
+        return pos;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImplX86_64.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImplX86_64.java
new file mode 100644
index 000000000000..3662dbb3fb78
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImplX86_64.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.dwarf;
+
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider;
+
+import java.util.List;
+
+/**
+ * x86_64-specific section generator for debug_frame section that knows details of x86_64 registers
+ * and frame layout.
+ */
+public class RuntimeDwarfFrameSectionImplX86_64 extends RuntimeDwarfFrameSectionImpl {
+    private static final int CFA_RSP_IDX = 7;
+    private static final int CFA_RIP_IDX = 16;
+
+    public RuntimeDwarfFrameSectionImplX86_64(RuntimeDwarfDebugInfo dwarfSections) {
+        super(dwarfSections);
+    }
+
+    @Override
+    public int getReturnPCIdx() {
+        return CFA_RIP_IDX;
+    }
+
+    @Override
+    public int getSPIdx() {
+        return CFA_RSP_IDX;
+    }
+
+    @Override
+    public int writeInitialInstructions(byte[] buffer, int p) {
+        int pos = p;
+        /*
+         * Invariant: CFA identifies last word of caller stack.
+         *
+         * Register rsp points at the word containing the saved rip so the frame base (cfa) is at
+         * rsp + 8:
+         *
+         * <ul>
+         *
+         * <li><code>def_cfa r7 (sp) offset 8</code>
+         *
+         * </ul>
+         */
+        pos = writeDefCFA(CFA_RSP_IDX, 8, buffer, pos);
+        /*
+         * Register rip is saved in slot 1.
+         *
+         * Scaling by -8 is automatic.
+         *
+         * <ul>
+         *
+         * <li><code>offset r16 (rip) cfa - 8</code>
+         *
+         * </ul>
+         */
+        pos = writeOffset(CFA_RIP_IDX, 1, buffer, pos);
+        return pos;
+    }
+
+    @Override
+    protected int writeFDEs(int frameSize, List<RuntimeDebugInfoProvider.DebugFrameSizeChange> frameSizeInfos, byte[] buffer, int p) {
+        int pos = p;
+        int currentOffset = 0;
+        for (RuntimeDebugInfoProvider.DebugFrameSizeChange debugFrameSizeInfo : frameSizeInfos) {
+            int advance = debugFrameSizeInfo.getOffset() - currentOffset;
+            currentOffset += advance;
+            pos = writeAdvanceLoc(advance, buffer, pos);
+            if (debugFrameSizeInfo.getType() == RuntimeDebugInfoProvider.DebugFrameSizeChange.Type.EXTEND) {
+                /*
+                 * SP has been extended so rebase CFA using full frame.
+                 *
+                 * Invariant: CFA identifies last word of caller stack.
+                 */
+                pos = writeDefCFAOffset(frameSize, buffer, pos);
+            } else {
+                /*
+                 * SP has been contracted so rebase CFA using empty frame.
+                 *
+                 * Invariant: CFA identifies last word of caller stack.
+                 */
+                pos = writeDefCFAOffset(8, buffer, pos);
+            }
+        }
+        return pos;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfInfoSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfInfoSectionImpl.java
new file mode 100644
index 000000000000..d72393e2fed6
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfInfoSectionImpl.java
@@ -0,0 +1,601 @@
+/*
+ * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.dwarf;
+
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocalInfo;
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocalValueInfo;
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugPrimitiveTypeInfo;
+import com.oracle.objectfile.runtime.debugentry.CompiledMethodEntry;
+import com.oracle.objectfile.runtime.debugentry.FileEntry;
+import com.oracle.objectfile.runtime.debugentry.MethodEntry;
+import com.oracle.objectfile.runtime.debugentry.PrimitiveTypeEntry;
+import com.oracle.objectfile.runtime.debugentry.TypeEntry;
+import com.oracle.objectfile.runtime.debugentry.TypedefEntry;
+import com.oracle.objectfile.runtime.debugentry.range.PrimaryRange;
+import com.oracle.objectfile.runtime.debugentry.range.Range;
+import com.oracle.objectfile.runtime.debugentry.range.SubRange;
+import com.oracle.objectfile.runtime.dwarf.RuntimeDwarfDebugInfo.AbbrevCode;
+import com.oracle.objectfile.runtime.dwarf.constants.DwarfAccess;
+import com.oracle.objectfile.runtime.dwarf.constants.DwarfEncoding;
+import com.oracle.objectfile.runtime.dwarf.constants.DwarfExpressionOpcode;
+import com.oracle.objectfile.runtime.dwarf.constants.DwarfFlag;
+import com.oracle.objectfile.runtime.dwarf.constants.DwarfInline;
+import com.oracle.objectfile.runtime.dwarf.constants.DwarfLanguage;
+import com.oracle.objectfile.runtime.dwarf.constants.DwarfSectionName;
+import com.oracle.objectfile.runtime.dwarf.constants.DwarfVersion;
+import jdk.graal.compiler.debug.DebugContext;
+import jdk.vm.ci.meta.JavaConstant;
+import jdk.vm.ci.meta.JavaKind;
+import jdk.vm.ci.meta.PrimitiveConstant;
+
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+import static com.oracle.objectfile.runtime.dwarf.RuntimeDwarfDebugInfo.LANG_ENCODING;
+
+/**
+ * Section generator for debug_info section.
+ */
+public class RuntimeDwarfInfoSectionImpl extends RuntimeDwarfSectionImpl {
+
+    /**
+     * An info header section always contains a fixed number of bytes.
+     */
+    private static final int DIE_HEADER_SIZE = 11;
+
+    /**
+     * Normally the offset of DWARF type declarations are tracked using the type/class entry
+     * properties but that means they are only available to be read during the second pass when
+     * filling in type cross-references. However, we need to use the offset of the void type during
+     * the first pass as the target of later-generated foreign pointer types. So, this field saves
+     * it up front.
+     */
+    private int cuStart;
+
+    public RuntimeDwarfInfoSectionImpl(RuntimeDwarfDebugInfo dwarfSections) {
+        // debug_info section depends on loc section
+        super(dwarfSections, DwarfSectionName.DW_INFO_SECTION, DwarfSectionName.DW_LOC_SECTION);
+        // initialize CU start to an invalid value
+        cuStart = -1;
+    }
+
+    @Override
+    public void createContent() {
+        assert !contentByteArrayCreated();
+
+        byte[] buffer = null;
+        int len = generateContent(null, buffer);
+
+        buffer = new byte[len];
+        super.setContent(buffer);
+    }
+
+    @Override
+    public void writeContent(DebugContext context) {
+        assert contentByteArrayCreated();
+
+        byte[] buffer = getContent();
+        int size = buffer.length;
+        int pos = 0;
+
+        enableLog(context, pos);
+        log(context, "  [0x%08x] DEBUG_INFO", pos);
+        log(context, "  [0x%08x] size = 0x%08x", pos, size);
+
+        pos = generateContent(context, buffer);
+        assert pos == size;
+    }
+
+    DwarfEncoding computeEncoding(int flags, int bitCount) {
+        assert bitCount > 0;
+        if ((flags & DebugPrimitiveTypeInfo.FLAG_NUMERIC) != 0) {
+            if (((flags & DebugPrimitiveTypeInfo.FLAG_INTEGRAL) != 0)) {
+                if ((flags & DebugPrimitiveTypeInfo.FLAG_SIGNED) != 0) {
+                    switch (bitCount) {
+                        case 8:
+                            return DwarfEncoding.DW_ATE_signed_char;
+                        default:
+                            assert bitCount == 16 || bitCount == 32 || bitCount == 64;
+                            return DwarfEncoding.DW_ATE_signed;
+                    }
+                } else {
+                    assert bitCount == 16;
+                    return DwarfEncoding.DW_ATE_unsigned;
+                }
+            } else {
+                assert bitCount == 32 || bitCount == 64;
+                return DwarfEncoding.DW_ATE_float;
+            }
+        } else {
+            assert bitCount == 1;
+            return DwarfEncoding.DW_ATE_boolean;
+        }
+    }
+
+    // generate a single CU for the runtime compiled method
+    public int generateContent(DebugContext context, byte[] buffer) {
+        int pos = 0;
+
+        pos = writeCU(context, buffer, pos);
+
+        /* Write all primitive types and types referenced from build time debuginfo */
+        pos = writeTypes(context, buffer, pos);
+
+        /* Write the runtime compiled method */
+        pos = writeMethod(context, compiledMethod(), buffer, pos);
+
+        /* Write abstract inlined methods */
+        pos = writeAbstractInlineMethods(context, compiledMethod(), buffer, pos);
+
+        /*
+         * Write a terminating null attribute.
+         */
+        pos = writeAttrNull(buffer, pos);
+
+        patchLength(0, buffer, pos);
+        return pos;
+    }
+
+    public int writeCU(DebugContext context, byte[] buffer, int p) {
+        int pos = p;
+        pos = writeCUHeader(buffer, pos);
+        assert pos == p + DIE_HEADER_SIZE;
+        AbbrevCode abbrevCode = AbbrevCode.METHOD_UNIT;
+        log(context, "  [0x%08x] <0> Abbrev Number %d", pos, abbrevCode.ordinal());
+        pos = writeAbbrevCode(abbrevCode, buffer, pos);
+        log(context, "  [0x%08x]     language  %s", pos, "DW_LANG_Java");
+        pos = writeAttrLanguage(LANG_ENCODING, buffer, pos);
+        log(context, "  [0x%08x]     use_UTF8", pos);
+        pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos);
+        String name = uniqueDebugString(dwarfSections.cuName());
+        log(context, "  [0x%08x]     name  0x%x (%s)", pos, debugStringIndex(name), name);
+        pos = writeStrSectionOffset(name, buffer, pos);
+        String compilationDirectory = uniqueDebugString(dwarfSections.getCachePath());
+        log(context, "  [0x%08x]     comp_dir  0x%x (%s)", pos, debugStringIndex(compilationDirectory), compilationDirectory);
+        pos = writeStrSectionOffset(compilationDirectory, buffer, pos);
+        long lo = compiledMethod().getPrimary().getLo();
+        log(context, "  [0x%08x]     low_pc  0x%x", pos, lo);
+        pos = writeAttrAddress(lo, buffer, pos);
+        long hi = compiledMethod().getPrimary().getHi();
+        log(context, "  [0x%08x]     high_pc  0x%x", pos, hi);
+        pos = writeAttrAddress(hi, buffer, pos);
+        int lineIndex = 0; // getLineIndex(compiledMethodEntry);
+        log(context, "  [0x%08x]     stmt_list  0x%x", pos, lineIndex);
+        return writeLineSectionOffset(lineIndex, buffer, pos);
+    }
+
+    public int writeTypes(DebugContext context, byte[] buffer, int p) {
+        int pos = p;
+        // write primitives
+        for (PrimitiveTypeEntry primitiveType : primitiveTypes()) {
+            pos = writePrimitiveType(context, primitiveType, buffer, pos);
+        }
+
+        // write typedefs
+        for (TypedefEntry typedef : typedefs()) {
+            pos = writeTypedef(context, typedef, buffer, pos);
+        }
+        return pos;
+    }
+
+    public int writePrimitiveType(DebugContext context, PrimitiveTypeEntry primitiveTypeEntry, byte[] buffer, int p) {
+        assert primitiveTypeEntry.getBitCount() > 0;
+        int pos = p;
+        log(context, "  [0x%08x] primitive type %s", pos, primitiveTypeEntry.getTypeName());
+        /* Record the location of this type entry. */
+        setTypeIndex(primitiveTypeEntry, pos);
+
+        AbbrevCode abbrevCode = AbbrevCode.PRIMITIVE_TYPE;
+        log(context, "  [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal());
+        pos = writeAbbrevCode(abbrevCode, buffer, pos);
+        byte byteSize = (byte) primitiveTypeEntry.getSize();
+        log(context, "  [0x%08x]     byte_size  %d", pos, byteSize);
+        pos = writeAttrData1(byteSize, buffer, pos);
+        byte bitCount = (byte) primitiveTypeEntry.getBitCount();
+        log(context, "  [0x%08x]     bitCount  %d", pos, bitCount);
+        pos = writeAttrData1(bitCount, buffer, pos);
+        DwarfEncoding encoding = computeEncoding(primitiveTypeEntry.getFlags(), bitCount);
+        log(context, "  [0x%08x]     encoding  0x%x", pos, encoding.value());
+        pos = writeAttrEncoding(encoding, buffer, pos);
+        String name = uniqueDebugString(primitiveTypeEntry.getTypeName());
+        log(context, "  [0x%08x]     name  0x%x (%s)", pos, debugStringIndex(name), name);
+        return writeStrSectionOffset(name, buffer, pos);
+    }
+
+    public int writeTypedef(DebugContext context, TypedefEntry typedefEntry, byte[] buffer, int p) {
+        int pos = p;
+        log(context, "  [0x%08x] typedef %s", pos, typedefEntry.getTypeName());
+        int typedefIndex = pos;
+        AbbrevCode abbrevCode = AbbrevCode.TYPEDEF;
+        log(context, "  [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal());
+        pos = writeAbbrevCode(abbrevCode, buffer, pos);
+        String name = uniqueDebugString(typedefEntry.getTypeName());
+        log(context, "  [0x%08x]     name  0x%x (%s)", pos, debugStringIndex(name), name);
+        pos = writeStrSectionOffset(name, buffer, pos);
+
+
+        log(context, "  [0x%08x] typedef pointer type %s", pos, typedefEntry.getTypeName());
+        /* Record the location of this type entry. */
+        setTypeIndex(typedefEntry, pos);
+        abbrevCode = AbbrevCode.TYPEDEF_POINTER;
+        log(context, "  [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal());
+        pos = writeAbbrevCode(abbrevCode, buffer, pos);
+        int pointerSize = dwarfSections.pointerSize();
+        log(context, "  [0x%08x]     byte_size 0x%x", pos, pointerSize);
+        pos = writeAttrData1((byte) pointerSize, buffer, pos);
+        log(context, "  [0x%08x]     type 0x%x", pos, typedefIndex);
+        return writeInfoSectionOffset(typedefIndex, buffer, pos);
+    }
+
+    public int writeMethod(DebugContext context, CompiledMethodEntry compiledEntry, byte[] buffer, int p) {
+        int pos = p;
+
+        MethodEntry methodEntry = compiledEntry.getPrimary().getMethodEntry();
+        String methodKey = uniqueDebugString(methodEntry.getSymbolName());
+        setMethodDeclarationIndex(methodEntry, pos);
+        int modifiers = methodEntry.getModifiers();
+        boolean isStatic = Modifier.isStatic(modifiers);
+
+        log(context, "  [0x%08x] method location %s::%s", pos, methodEntry.ownerType().getTypeName(), methodEntry.methodName());
+        AbbrevCode abbrevCode = (isStatic ? AbbrevCode.METHOD_LOCATION_STATIC : AbbrevCode.METHOD_LOCATION);
+        log(context, "  [0x%08x] <2> Abbrev Number %d", pos, abbrevCode.ordinal());
+        pos = writeAbbrevCode(abbrevCode, buffer, pos);
+        log(context, "  [0x%08x]     external  true", pos);
+        pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos);
+        String name = uniqueDebugString(methodEntry.methodName());
+        log(context, "  [0x%08x]     name 0x%x (%s)", pos, debugStringIndex(name), name);
+        pos = writeStrSectionOffset(name, buffer, pos);
+        Range primary = compiledEntry.getPrimary();
+        log(context, "  [0x%08x]     lo_pc  0x%08x", pos, primary.getLo());
+        pos = writeAttrAddress(primary.getLo(), buffer, pos);
+        log(context, "  [0x%08x]     hi_pc  0x%08x", pos, primary.getHi());
+        pos = writeAttrAddress(primary.getHi(), buffer, pos);
+        TypeEntry returnType = methodEntry.getValueType();
+        int retTypeIdx = getTypeIndex(returnType);
+        log(context, "  [0x%08x]     type 0x%x (%s)", pos, retTypeIdx, returnType.getTypeName());
+        pos = writeInfoSectionOffset(retTypeIdx, buffer, pos);
+        log(context, "  [0x%08x]     artificial %s", pos, methodEntry.isDeopt() ? "true" : "false");
+        pos = writeFlag((methodEntry.isDeopt() ? DwarfFlag.DW_FLAG_true : DwarfFlag.DW_FLAG_false), buffer, pos);
+        log(context, "  [0x%08x]     accessibility %s", pos, "public");
+        pos = writeAttrAccessibility(modifiers, buffer, pos);
+        int typeIdx = getTypeIndex(methodEntry.ownerType());
+        log(context, "  [0x%08x]     containing_type 0x%x (%s)", pos, typeIdx, methodEntry.ownerType().getTypeName());
+        pos = writeInfoSectionOffset(typeIdx, buffer, pos); //writeAttrRef4(typeIdx, buffer, pos);
+        if (!isStatic) {
+            /* Record the current position so we can back patch the object pointer. */
+            int objectPointerIndex = pos;
+            /*
+             * Write a dummy ref address to move pos on to where the first parameter gets written.
+             */
+            pos = writeAttrRef4(0, null, pos);
+            /*
+             * Now backpatch object pointer slot with current pos, identifying the first parameter.
+             */
+            log(context, "  [0x%08x]     object_pointer 0x%x", objectPointerIndex, pos);
+            writeAttrRef4(pos, buffer, objectPointerIndex);
+        }
+
+        HashMap<DebugLocalInfo, List<SubRange>> varRangeMap = primary.getVarRangeMap();
+        /* Write method parameter declarations. */
+        pos = writeMethodParameterLocations(context, compiledEntry, varRangeMap, primary, 2, buffer, pos);
+        /* write method local declarations */
+        pos = writeMethodLocalLocations(context, compiledEntry, varRangeMap, primary, 2, buffer, pos);
+
+        if (primary.includesInlineRanges()) {
+            /*
+             * the method has inlined ranges so write concrete inlined method entries as its
+             * children
+             */
+            pos = generateConcreteInlinedMethods(context, compiledEntry, buffer, pos);
+        }
+
+        /*
+         * Write a terminating null attribute.
+         */
+        return writeAttrNull(buffer, pos);
+    }
+
+
+    private int writeMethodParameterLocations(DebugContext context, CompiledMethodEntry compiledEntry, HashMap<DebugLocalInfo, List<SubRange>> varRangeMap, Range range, int depth, byte[] buffer, int p) {
+        int pos = p;
+        MethodEntry methodEntry;
+        if (range.isPrimary()) {
+            methodEntry = range.getMethodEntry();
+        } else {
+            assert !range.isLeaf() : "should only be looking up var ranges for inlined calls";
+            methodEntry = range.getFirstCallee().getMethodEntry();
+        }
+        if (!Modifier.isStatic(methodEntry.getModifiers())) {
+            DebugLocalInfo thisParamInfo = methodEntry.getThisParam();
+            List<SubRange> ranges = varRangeMap.get(thisParamInfo);
+            pos = writeMethodLocalLocation(context, range, thisParamInfo, ranges, depth, true, buffer, pos);
+        }
+        for (int i = 0; i < methodEntry.getParamCount(); i++) {
+            DebugLocalInfo paramInfo = methodEntry.getParam(i);
+            List<SubRange> ranges = varRangeMap.get(paramInfo);
+            pos = writeMethodLocalLocation(context, range, paramInfo, ranges, depth, true, buffer, pos);
+        }
+        return pos;
+    }
+
+    private int writeMethodLocalLocations(DebugContext context, CompiledMethodEntry compiledEntry, HashMap<DebugLocalInfo, List<SubRange>> varRangeMap, Range range, int depth, byte[] buffer, int p) {
+        int pos = p;
+        MethodEntry methodEntry;
+        if (range.isPrimary()) {
+            methodEntry = range.getMethodEntry();
+        } else {
+            assert !range.isLeaf() : "should only be looking up var ranges for inlined calls";
+            methodEntry = range.getFirstCallee().getMethodEntry();
+        }
+        int count = methodEntry.getLocalCount();
+        for (int i = 0; i < count; i++) {
+            DebugLocalInfo localInfo = methodEntry.getLocal(i);
+            List<SubRange> ranges = varRangeMap.get(localInfo);
+            pos = writeMethodLocalLocation(context, range, localInfo, ranges, depth, false, buffer, pos);
+        }
+        return pos;
+    }
+
+    private int writeMethodLocalLocation(DebugContext context, Range range, DebugLocalInfo localInfo, List<SubRange> ranges, int depth, boolean isParam, byte[] buffer,
+                                         int p) {
+        int pos = p;
+        log(context, "  [0x%08x] method %s location %s:%s", pos, (isParam ? "parameter" : "local"), localInfo.name(), localInfo.typeName());
+        List<DebugLocalValueInfo> localValues = new ArrayList<>();
+        for (SubRange subrange : ranges) {
+            DebugLocalValueInfo value = subrange.lookupValue(localInfo);
+            if (value != null) {
+                log(context, "  [0x%08x]     local  %s:%s [0x%x, 0x%x] = %s", pos, value.name(), value.typeName(), subrange.getLo(), subrange.getHi(), formatValue(value));
+                switch (value.localKind()) {
+                    case REGISTER:
+                    case STACKSLOT:
+                        localValues.add(value);
+                        break;
+                    case CONSTANT:
+                        JavaConstant constant = value.constantValue();
+                        // can only handle primitive or null constants just now
+                        if (constant instanceof PrimitiveConstant || constant.getJavaKind() == JavaKind.Object) {
+                            localValues.add(value);
+                        }
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+        AbbrevCode abbrevCode;
+        if (localValues.isEmpty()) {
+            abbrevCode = (isParam ? AbbrevCode.METHOD_PARAMETER_LOCATION_1 : AbbrevCode.METHOD_LOCAL_LOCATION_1);
+        } else {
+            abbrevCode = (isParam ? AbbrevCode.METHOD_PARAMETER_LOCATION_2 : AbbrevCode.METHOD_LOCAL_LOCATION_2);
+        }
+        log(context, "  [0x%08x] <%d> Abbrev Number %d", pos, depth, abbrevCode.ordinal());
+        pos = writeAbbrevCode(abbrevCode, buffer, pos);
+
+
+        String name = uniqueDebugString(localInfo.name());
+        log(context, "  [0x%08x]     name 0x%x (%s)", pos, debugStringIndex(name), name);
+        pos = writeStrSectionOffset(name, buffer, pos);
+        TypeEntry valueType = lookupType(localInfo.valueType());
+        int retTypeIdx = getTypeIndex(valueType);
+        log(context, "  [0x%08x]     type 0x%x (%s)", pos, retTypeIdx, valueType.getTypeName());
+        pos = writeInfoSectionOffset(retTypeIdx, buffer, pos);
+
+        if (!localValues.isEmpty()) {
+            int locRefAddr = getRangeLocalIndex(range, localInfo);
+            log(context, "  [0x%08x]     loc list  0x%x", pos, locRefAddr);
+            pos = writeLocSectionOffset(locRefAddr, buffer, pos);
+        }
+        return pos;
+    }
+
+    /**
+     * Go through the subranges and generate concrete debug entries for inlined methods.
+     */
+    private int generateConcreteInlinedMethods(DebugContext context, CompiledMethodEntry compiledEntry, byte[] buffer, int p) {
+        Range primary = compiledEntry.getPrimary();
+        if (primary.isLeaf()) {
+            return p;
+        }
+        int pos = p;
+        log(context, "  [0x%08x] concrete entries [0x%x,0x%x] %s", pos, primary.getLo(), primary.getHi(), primary.getFullMethodName());
+        int depth = 0;
+        Iterator<SubRange> iterator = compiledEntry.topDownRangeIterator();
+        while (iterator.hasNext()) {
+            SubRange subrange = iterator.next();
+            if (subrange.isLeaf()) {
+                // we only generate concrete methods for non-leaf entries
+                continue;
+            }
+            // if we just stepped out of a child range write nulls for each step up
+            while (depth > subrange.getDepth()) {
+                pos = writeAttrNull(buffer, pos);
+                depth--;
+            }
+            depth = subrange.getDepth();
+            pos = writeInlineSubroutine(context, compiledEntry, subrange, depth + 2, buffer, pos);
+            HashMap<DebugLocalInfo, List<SubRange>> varRangeMap = subrange.getVarRangeMap();
+            // increment depth to account for parameter and method locations
+            depth++;
+            pos = writeMethodParameterLocations(context, compiledEntry, varRangeMap, subrange, depth + 2, buffer, pos);
+            pos = writeMethodLocalLocations(context, compiledEntry, varRangeMap, subrange, depth + 2, buffer, pos);
+        }
+        // if we just stepped out of a child range write nulls for each step up
+        while (depth > 0) {
+            pos = writeAttrNull(buffer, pos);
+            depth--;
+        }
+        return pos;
+    }
+
+    private int writeInlineSubroutine(DebugContext context, CompiledMethodEntry compiledEntry, SubRange caller, int depth, byte[] buffer, int p) {
+        assert !caller.isLeaf();
+        // the supplied range covers an inline call and references the caller method entry. its
+        // child ranges all reference the same inlined called method. leaf children cover code for
+        // that inlined method. non-leaf children cover code for recursively inlined methods.
+        // identify the inlined method by looking at the first callee
+        Range callee = caller.getFirstCallee();
+        MethodEntry methodEntry = callee.getMethodEntry();
+        String methodKey = uniqueDebugString(methodEntry.getSymbolName());
+        /* the abstract index was written in the method's class entry */
+        int abstractOriginIndex = getAbstractInlineMethodIndex(compiledEntry, methodEntry); // getMethodDeclarationIndex(methodEntry);
+
+        int pos = p;
+        log(context, "  [0x%08x] concrete inline subroutine [0x%x, 0x%x] %s", pos, caller.getLo(), caller.getHi(), methodKey);
+
+        int callLine = caller.getLine();
+        assert callLine >= -1 : callLine;
+        int fileIndex = 0;
+        final AbbrevCode abbrevCode = AbbrevCode.INLINED_SUBROUTINE;
+        log(context, "  [0x%08x] <%d> Abbrev Number %d", pos, depth, abbrevCode.ordinal());
+        pos = writeAbbrevCode(abbrevCode, buffer, pos);
+        log(context, "  [0x%08x]     abstract_origin  0x%x", pos, abstractOriginIndex);
+        pos = writeAttrRef4(abstractOriginIndex, buffer, pos);
+        log(context, "  [0x%08x]     lo_pc  0x%08x", pos, caller.getLo());
+        pos = writeAttrAddress(caller.getLo(), buffer, pos);
+        log(context, "  [0x%08x]     hi_pc  0x%08x", pos, caller.getHi());
+        pos = writeAttrAddress(caller.getHi(), buffer, pos);
+        log(context, "  [0x%08x]     call_file  %d", pos, fileIndex);
+        pos = writeAttrData4(fileIndex, buffer, pos);
+        log(context, "  [0x%08x]     call_line  %d", pos, callLine);
+        pos = writeAttrData4(callLine, buffer, pos);
+        return pos;
+    }
+
+    private int writeAbstractInlineMethods(DebugContext context, CompiledMethodEntry compiledEntry, byte[] buffer, int p) {
+        int pos = p;
+        PrimaryRange primary = compiledEntry.getPrimary();
+        if (primary.isLeaf()) {
+            return pos;
+        }
+        verboseLog(context, "  [0x%08x] collect abstract inlined methods %s", p, primary.getFullMethodName());
+        Iterator<SubRange> iterator = compiledEntry.topDownRangeIterator();
+        while (iterator.hasNext()) {
+            SubRange subrange = iterator.next();
+            if (subrange.isLeaf()) {
+                // we only generate abstract inline methods for non-leaf entries
+                continue;
+            }
+            // the subrange covers an inline call and references the caller method entry. its
+            // child ranges all reference the same inlined called method. leaf children cover code
+            // for
+            // that inlined method. non-leaf children cover code for recursively inlined methods.
+            // identify the inlined method by looking at the first callee
+            Range callee = subrange.getFirstCallee();
+            MethodEntry methodEntry = callee.getMethodEntry();
+
+            // n.b. class entry used to index the method belongs to the inlining method
+            // not the inlined method
+            setAbstractInlineMethodIndex(compiledEntry, methodEntry, pos);
+            pos = writeAbstractInlineMethod(context, compiledEntry, methodEntry, buffer, pos);
+        }
+        return pos;
+    }
+
+    private int writeAbstractInlineMethod(DebugContext context, CompiledMethodEntry compiledEntry, MethodEntry method, byte[] buffer, int p) {
+        int pos = p;
+        log(context, "  [0x%08x] abstract inline method %s", pos, method.methodName());
+        AbbrevCode abbrevCode = AbbrevCode.ABSTRACT_INLINE_METHOD;
+        log(context, "  [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal());
+        pos = writeAbbrevCode(abbrevCode, buffer, pos);
+        log(context, "  [0x%08x]     inline  0x%x", pos, DwarfInline.DW_INL_inlined.value());
+        pos = writeAttrInline(DwarfInline.DW_INL_inlined, buffer, pos);
+        /*
+         * Should pass true only if method is non-private.
+         */
+        log(context, "  [0x%08x]     external  true", pos);
+        pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos);
+
+        String name = uniqueDebugString(method.methodName());
+        log(context, "  [0x%08x]     name 0x%x (%s)", pos, debugStringIndex(name), name);
+        pos = writeStrSectionOffset(name, buffer, pos);
+        TypeEntry returnType = method.getValueType();
+        int retTypeIdx = getTypeIndex(returnType);
+        log(context, "  [0x%08x]     type 0x%x (%s)", pos, retTypeIdx, returnType.getTypeName());
+        pos = writeInfoSectionOffset(retTypeIdx, buffer, pos);
+
+        /*
+         * Write a terminating null attribute.
+         */
+        return writeAttrNull(buffer, pos);
+    }
+
+    private int writeAttrRef4(int reference, byte[] buffer, int p) {
+        // make sure we have actually started writing a CU
+        assert cuStart >= 0;
+        // writes a CU-relative offset
+        return writeAttrData4(reference - cuStart, buffer, p);
+    }
+
+    private int writeCUHeader(byte[] buffer, int p) {
+        int pos = p;
+        /* CU length. */
+        pos = writeInt(0, buffer, pos);
+        /* DWARF version. */
+        pos = writeDwarfVersion(DwarfVersion.DW_VERSION_4, buffer, pos);
+        /* Abbrev offset. */
+        pos = writeAbbrevSectionOffset(0, buffer, pos);
+        /* Address size. */
+        return writeByte((byte) 8, buffer, pos);
+    }
+
+    @SuppressWarnings("unused")
+    public int writeAttrString(String value, byte[] buffer, int p) {
+        int pos = p;
+        return writeUTF8StringBytes(value, buffer, pos);
+    }
+
+    public int writeAttrLanguage(DwarfLanguage language, byte[] buffer, int p) {
+        int pos = p;
+        return writeByte(language.value(), buffer, pos);
+    }
+
+    public int writeAttrEncoding(DwarfEncoding encoding, byte[] buffer, int p) {
+        return writeByte(encoding.value(), buffer, p);
+    }
+
+    public int writeAttrInline(DwarfInline inline, byte[] buffer, int p) {
+        return writeByte(inline.value(), buffer, p);
+    }
+
+    public int writeAttrAccessibility(int modifiers, byte[] buffer, int p) {
+        DwarfAccess access;
+        if (Modifier.isPublic(modifiers)) {
+            access = DwarfAccess.DW_ACCESS_public;
+        } else if (Modifier.isProtected(modifiers)) {
+            access = DwarfAccess.DW_ACCESS_protected;
+        } else if (Modifier.isPrivate(modifiers)) {
+            access = DwarfAccess.DW_ACCESS_private;
+        } else {
+            /* Actually package private -- make it public for now. */
+            access = DwarfAccess.DW_ACCESS_public;
+        }
+        return writeByte(access.value(), buffer, p);
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfLineSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfLineSectionImpl.java
new file mode 100644
index 000000000000..d2dfbd898aa0
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfLineSectionImpl.java
@@ -0,0 +1,791 @@
+/*
+ * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.dwarf;
+
+import com.oracle.objectfile.LayoutDecision;
+import com.oracle.objectfile.LayoutDecisionMap;
+import com.oracle.objectfile.ObjectFile;
+import com.oracle.objectfile.debugentry.ClassEntry;
+import com.oracle.objectfile.elf.dwarf.DwarfSectionImpl;
+import com.oracle.objectfile.runtime.debugentry.CompiledMethodEntry;
+import com.oracle.objectfile.runtime.debugentry.DirEntry;
+import com.oracle.objectfile.runtime.debugentry.FileEntry;
+import com.oracle.objectfile.runtime.debugentry.range.Range;
+import com.oracle.objectfile.runtime.debugentry.range.SubRange;
+import com.oracle.objectfile.runtime.dwarf.constants.DwarfLineOpcode;
+import com.oracle.objectfile.runtime.dwarf.constants.DwarfSectionName;
+import com.oracle.objectfile.runtime.dwarf.constants.DwarfVersion;
+import jdk.graal.compiler.debug.DebugContext;
+
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Section generator for debug_line section.
+ */
+public class RuntimeDwarfLineSectionImpl extends RuntimeDwarfSectionImpl {
+    /**
+     * 0 is used to indicate an invalid opcode.
+     */
+    private static final int LN_undefined = 0;
+
+    /**
+     * Line header section always contains fixed number of bytes.
+     */
+    private static final int LN_HEADER_SIZE = 28;
+    /**
+     * Current generator follows C++ with line base -5.
+     */
+    private static final int LN_LINE_BASE = -5;
+    /**
+     * Current generator follows C++ with line range 14 giving full range -5 to 8.
+     */
+    private static final int LN_LINE_RANGE = 14;
+    /**
+     * Current generator uses opcode base of 13 which must equal DW_LNS_set_isa + 1.
+     */
+    private static final int LN_OPCODE_BASE = 13;
+
+    RuntimeDwarfLineSectionImpl(RuntimeDwarfDebugInfo dwarfSections) {
+        // line section depends on string section
+        super(dwarfSections, DwarfSectionName.DW_LINE_SECTION, DwarfSectionName.DW_STR_SECTION);
+    }
+
+    private int linePrologueSize;
+
+    @Override
+    public void createContent() {
+        assert !contentByteArrayCreated();
+
+        /*
+         * We need to create a header, dir table, file table and line number table encoding for each
+         * class CU that contains compiled methods.
+         */
+
+        CompiledMethodEntry compiledEntry = compiledMethod();
+        //setLineIndex(compiledEntry, byteCount.get());
+        int headerSize = headerSize();
+        int dirTableSize = computeDirTableSize(compiledEntry);
+        int fileTableSize = computeFileTableSize(compiledEntry);
+        linePrologueSize = headerSize + dirTableSize + fileTableSize;
+        // mark the start of the line table for this entry
+        int lineNumberTableSize = computeLineNumberTableSize(compiledEntry);
+        int totalSize = linePrologueSize + lineNumberTableSize;
+
+        byte[] buffer = new byte[totalSize];
+        super.setContent(buffer);
+    }
+
+    private static int headerSize() {
+        /*
+         * Header size is standard 31 bytes:
+         *
+         * <ul>
+         *
+         * <li><code>uint32 total_length</code>
+         *
+         * <li><code>uint16 version</code>
+         *
+         * <li><code>uint32 header_length</code>
+         *
+         * <li><code>uint8 min_insn_length</code>
+         *
+         * <li><code>uint8 max_operations_per_instruction</code>
+         *
+         * <li><code>uint8 default_is_stmt</code>
+         *
+         * <li><code>int8 line_base</code>
+         *
+         * <li><code>uint8 line_range</code>
+         *
+         * <li><code>uint8 opcode_base</code>
+         *
+         * <li><code>uint8[opcode_base-1] standard_opcode_lengths</code>
+         *
+         * </ul>
+         */
+
+        return LN_HEADER_SIZE;
+    }
+
+    private int computeDirTableSize(CompiledMethodEntry compiledEntry) {
+        /*
+         * Table contains a sequence of 'nul'-terminated UTF8 dir name bytes followed by an extra
+         * 'nul'.
+         */
+        // TODO also check for subranges
+        DirEntry dirEntry = compiledEntry.getPrimary().getFileEntry().getDirEntry();
+        int length = countUTF8Bytes(dirEntry.getPathString()) + 1;
+        assert length > 1; // no empty strings
+        /*
+         * Allow for terminator nul.
+         */
+        return length + 1;
+    }
+
+    private int computeFileTableSize(CompiledMethodEntry compiledEntry) {
+        /*
+         * Table contains a sequence of file entries followed by an extra 'nul'
+         *
+         * each file entry consists of a 'nul'-terminated UTF8 file name, a dir entry idx and two 0
+         * time stamps
+         */
+        // TODO also check subranges
+        FileEntry fileEntry = compiledEntry.getPrimary().getFileEntry();
+        int length = countUTF8Bytes(fileEntry.getFileName()) + 1;
+        assert length > 1;
+        // The dir index gets written as a ULEB
+        int dirIdx = 1;
+        length += writeULEB(dirIdx, scratch, 0);
+        // The two zero timestamps require 1 byte each
+        length += 2;
+
+        /*
+         * Allow for terminator nul.
+         */
+        return length + 1;
+    }
+
+    @Override
+    public byte[] getOrDecideContent(Map<ObjectFile.Element, LayoutDecisionMap> alreadyDecided, byte[] contentHint) {
+        ObjectFile.Element textElement = getElement().getOwner().elementForName(".text");
+        LayoutDecisionMap decisionMap = alreadyDecided.get(textElement);
+        if (decisionMap != null) {
+            Object valueObj = decisionMap.getDecidedValue(LayoutDecision.Kind.VADDR);
+            if (valueObj != null && valueObj instanceof Number) {
+                /*
+                 * This may not be the final vaddr for the text segment but it will be close enough
+                 * to make debug easier i.e. to within a 4k page or two.
+                 */
+                debugTextBase = ((Number) valueObj).longValue();
+            }
+        }
+        return super.getOrDecideContent(alreadyDecided, contentHint);
+    }
+
+    @Override
+    public void writeContent(DebugContext context) {
+        assert contentByteArrayCreated();
+
+        byte[] buffer = getContent();
+        int pos = 0;
+
+        CompiledMethodEntry compiledEntry = compiledMethod();
+
+        enableLog(context, pos);
+        log(context, "  [0x%08x] DEBUG_LINE", pos);
+        // setLineIndex(classEntry, pos);
+        int lengthPos = pos;
+        pos = writeHeader(buffer, pos);
+        log(context, "  [0x%08x] headerSize = 0x%08x", pos, pos);
+        int dirTablePos = pos;
+        pos = writeDirTable(context, compiledEntry, buffer, pos);
+        log(context, "  [0x%08x] dirTableSize = 0x%08x", pos, pos - dirTablePos);
+        int fileTablePos = pos;
+        pos = writeFileTable(context, compiledEntry, buffer, pos);
+        log(context, "  [0x%08x] fileTableSize = 0x%08x", pos, pos - fileTablePos);
+        int lineNumberTablePos = pos;
+        pos = writeLineNumberTable(context, compiledEntry, buffer, pos);
+        log(context, "  [0x%08x] lineNumberTableSize = 0x%x", pos, pos - lineNumberTablePos);
+        log(context, "  [0x%08x] size = 0x%x", pos, pos - lengthPos);
+        patchLength(lengthPos, buffer, pos);
+        assert pos == buffer.length;
+    }
+
+    private int writeHeader(byte[] buffer, int p) {
+        int pos = p;
+        /*
+         * write dummy 4 ubyte length field.
+         */
+        pos = writeInt(0, buffer, pos);
+        /*
+         * 2 ubyte version is always 2.
+         */
+        pos = writeDwarfVersion(DwarfVersion.DW_VERSION_4, buffer, pos);
+        /*
+         * 4 ubyte prologue length includes rest of header and dir + file table section.
+         */
+        int prologueSize = linePrologueSize - (4 + 2 + 4);
+        pos = writeInt(prologueSize, buffer, pos);
+        /*
+         * 1 ubyte min instruction length is always 1.
+         */
+        pos = writeByte((byte) 1, buffer, pos);
+        /*
+         * 1 ubyte max operations per instruction is always 1.
+         */
+        pos = writeByte((byte) 1, buffer, pos);
+        /*
+         * 1 byte default is_stmt is always 1.
+         */
+        pos = writeByte((byte) 1, buffer, pos);
+        /*
+         * 1 byte line base is always -5.
+         */
+        pos = writeByte((byte) LN_LINE_BASE, buffer, pos);
+        /*
+         * 1 ubyte line range is always 14 giving range -5 to 8.
+         */
+        pos = writeByte((byte) LN_LINE_RANGE, buffer, pos);
+        /*
+         * 1 ubyte opcode base is always 13.
+         */
+        pos = writeByte((byte) LN_OPCODE_BASE, buffer, pos);
+        /*
+         * specify opcode arg sizes for the standard opcodes.
+         */
+        /* DW_LNS_copy */
+        writeByte((byte) 0, buffer, pos);
+        /* DW_LNS_advance_pc */
+        writeByte((byte) 1, buffer, pos + 1);
+        /* DW_LNS_advance_line */
+        writeByte((byte) 1, buffer, pos + 2);
+        /* DW_LNS_set_file */
+        writeByte((byte) 1, buffer, pos + 3);
+        /* DW_LNS_set_column */
+        writeByte((byte) 1, buffer, pos + 4);
+        /* DW_LNS_negate_stmt */
+        writeByte((byte) 0, buffer, pos + 5);
+        /* DW_LNS_set_basic_block */
+        writeByte((byte) 0, buffer, pos + 6);
+        /* DW_LNS_const_add_pc */
+        writeByte((byte) 0, buffer, pos + 7);
+        /* DW_LNS_fixed_advance_pc */
+        writeByte((byte) 1, buffer, pos + 8);
+        /* DW_LNS_set_prologue_end */
+        writeByte((byte) 0, buffer, pos + 9);
+        /* DW_LNS_set_epilogue_begin */
+        writeByte((byte) 0, buffer, pos + 10);
+        /* DW_LNS_set_isa */
+        pos = writeByte((byte) 1, buffer, pos + 11);
+        return pos;
+    }
+
+    private int writeDirTable(DebugContext context, CompiledMethodEntry compiledEntry, byte[] buffer, int p) {
+        verboseLog(context, "  [0x%08x] Dir Name", p);
+        /*
+         * Write out the list of dirs
+         */
+        int pos = p;
+        int idx = 1;
+
+        // TODO: foreach compiledEntry.dirs() - also check subranges
+        DirEntry dirEntry = compiledEntry.getPrimary().getMethodEntry().getFileEntry().getDirEntry();
+        String dirPath = dirEntry.getPathString();
+        pos = writeUTF8StringBytes(dirPath, buffer, pos);
+        idx++;
+
+//        classEntry.dirStream().forEach(dirEntry -> {
+//            int dirIdx = idx.get();
+//            assert (classEntry.getDirIdx(dirEntry) == dirIdx);
+//            String dirPath = dirEntry.getPathString();
+//            verboseLog(context, "  [0x%08x] %-4d %s", cursor.get(), dirIdx, dirPath);
+//            cursor.set(writeUTF8StringBytes(dirPath, buffer, cursor.get()));
+//            idx.add(1);
+//        });
+        /*
+         * Separate dirs from files with a nul.
+         */
+        return writeByte((byte) 0, buffer, pos);
+    }
+
+    private int writeFileTable(DebugContext context, CompiledMethodEntry compiledEntry, byte[] buffer, int p) {
+        verboseLog(context, "  [0x%08x] Entry Dir  Name", p);
+        /*
+         * Write out the list of files
+         */
+        int pos = p;
+        int idx = 1;
+
+        // TODO: foreach compiledEntry.files() - also check subranges
+
+        FileEntry fileEntry = compiledEntry.getPrimary().getMethodEntry().getFileEntry();
+        String baseName = fileEntry.getFileName();
+        pos = writeUTF8StringBytes(baseName, buffer, pos);
+        pos = writeULEB(1, buffer, pos);  // TODO set correct dir index, if more than 1 dir in subranges
+        pos = writeULEB(0, buffer, pos);
+        pos = writeULEB(0, buffer, pos);
+        idx++;
+
+
+//        classEntry.fileStream().forEach(fileEntry -> {
+//            int pos = cursor.get();
+//            int fileIdx = idx.get();
+//            assert classEntry.getFileIdx(fileEntry) == fileIdx;
+//            int dirIdx = classEntry.getDirIdx(fileEntry);
+//            String baseName = fileEntry.getFileName();
+//            verboseLog(context, "  [0x%08x] %-5d %-5d %s", pos, fileIdx, dirIdx, baseName);
+//            pos = writeUTF8StringBytes(baseName, buffer, pos);
+//            pos = writeULEB(dirIdx, buffer, pos);
+//            pos = writeULEB(0, buffer, pos);
+//            pos = writeULEB(0, buffer, pos);
+//            cursor.set(pos);
+//            idx.add(1);
+//        });
+        /*
+         * Terminate files with a nul.
+         */
+        return writeByte((byte) 0, buffer, pos);
+    }
+
+    private long debugLine = 1;
+    private int debugCopyCount = 0;
+
+    private int writeCompiledMethodLineInfo(DebugContext context, CompiledMethodEntry compiledEntry, byte[] buffer, int p) {
+        int pos = p;
+        Range primaryRange = compiledEntry.getPrimary();
+        // the compiled method might be a substitution and not in the file of the class entry
+        FileEntry fileEntry = primaryRange.getFileEntry();
+        if (fileEntry == null) {
+            log(context, "  [0x%08x] primary range [0x%08x, 0x%08x] skipped (no file) %s", pos, debugTextBase + primaryRange.getLo(), debugTextBase + primaryRange.getHi(),
+                            primaryRange.getFullMethodNameWithParams());
+            return pos;
+        }
+        String file = fileEntry.getFileName();
+        int fileIdx = 1; // TODO classEntry.getFileIdx(fileEntry);
+        /*
+         * Each primary represents a method i.e. a contiguous sequence of subranges. For normal
+         * methods we expect the first leaf range to start at offset 0 covering the method prologue.
+         * In that case we can rely on it to set the initial file, line and address for the state
+         * machine. Otherwise we need to default the initial state and copy it to the file.
+         */
+        long line = primaryRange.getLine();
+        long address = primaryRange.getLo();
+        Range prologueRange = prologueLeafRange(compiledEntry);
+        if (prologueRange != null) {
+            // use the line for the range and use its file if available
+            line = prologueRange.getLine();
+            if (line > 0) {
+                FileEntry firstFileEntry = prologueRange.getFileEntry();
+                if (firstFileEntry != null) {
+                    fileIdx = 1; // TODO classEntry.getFileIdx(firstFileEntry);
+                }
+            }
+        }
+        if (line < 0) {
+            // never emit a negative line
+            line = 0;
+        }
+
+        /*
+         * Set state for primary.
+         */
+        log(context, "  [0x%08x] primary range [0x%08x, 0x%08x] %s %s:%d", pos, debugTextBase + primaryRange.getLo(), debugTextBase + primaryRange.getHi(),
+                        primaryRange.getFullMethodNameWithParams(),
+                        file, primaryRange.getLine());
+
+        /*
+         * Initialize and write a row for the start of the compiled method.
+         */
+        pos = writeSetFileOp(context, file, fileIdx, buffer, pos);
+        pos = writeSetBasicBlockOp(context, buffer, pos);
+        /*
+         * Address is currently at offset 0.
+         */
+        pos = writeSetAddressOp(context, address, buffer, pos);
+        /*
+         * State machine value of line is currently 1 increment to desired line.
+         */
+        if (line != 1) {
+            pos = writeAdvanceLineOp(context, line - 1, buffer, pos);
+        }
+        pos = writeCopyOp(context, buffer, pos);
+
+        /*
+         * Now write a row for each subrange lo and hi.
+         */
+        Iterator<SubRange> iterator = compiledEntry.leafRangeIterator();
+        if (prologueRange != null) {
+            // skip already processed range
+            SubRange first = iterator.next();
+            assert first == prologueRange;
+        }
+        while (iterator.hasNext()) {
+            SubRange subrange = iterator.next();
+            assert subrange.getLo() >= primaryRange.getLo();
+            assert subrange.getHi() <= primaryRange.getHi();
+            FileEntry subFileEntry = subrange.getFileEntry();
+            if (subFileEntry == null) {
+                continue;
+            }
+            String subfile = subFileEntry.getFileName();
+            int subFileIdx = 0; // TODO classEntry.getFileIdx(subFileEntry);
+            assert subFileIdx > 0;
+            long subLine = subrange.getLine();
+            long subAddressLo = subrange.getLo();
+            long subAddressHi = subrange.getHi();
+            log(context, "  [0x%08x] sub range [0x%08x, 0x%08x] %s %s:%d", pos, debugTextBase + subAddressLo, debugTextBase + subAddressHi, subrange.getFullMethodNameWithParams(), subfile,
+                            subLine);
+            if (subLine < 0) {
+                /*
+                 * No line info so stay at previous file:line.
+                 */
+                subLine = line;
+                subfile = file;
+                subFileIdx = fileIdx;
+                verboseLog(context, "  [0x%08x] missing line info - staying put at %s:%d", pos, file, line);
+            }
+            /*
+             * There is a temptation to append end sequence at here when the hiAddress lies strictly
+             * between the current address and the start of the next subrange because, ostensibly,
+             * we have void space between the end of the current subrange and the start of the next
+             * one. however, debug works better if we treat all the insns up to the next range start
+             * as belonging to the current line.
+             *
+             * If we have to update to a new file then do so.
+             */
+            if (subFileIdx != fileIdx) {
+                /*
+                 * Update the current file.
+                 */
+                pos = writeSetFileOp(context, subfile, subFileIdx, buffer, pos);
+                file = subfile;
+                fileIdx = subFileIdx;
+            }
+            long lineDelta = subLine - line;
+            long addressDelta = subAddressLo - address;
+            /*
+             * Check if we can advance line and/or address in one byte with a special opcode.
+             */
+            byte opcode = isSpecialOpcode(addressDelta, lineDelta);
+            if (opcode != LN_undefined) {
+                /*
+                 * Ignore pointless write when addressDelta == lineDelta == 0.
+                 */
+                if (addressDelta != 0 || lineDelta != 0) {
+                    pos = writeSpecialOpcode(context, opcode, buffer, pos);
+                }
+            } else {
+                /*
+                 * Does it help to divide and conquer using a fixed address increment.
+                 */
+                int remainder = isConstAddPC(addressDelta);
+                if (remainder > 0) {
+                    pos = writeConstAddPCOp(context, buffer, pos);
+                    /*
+                     * The remaining address can be handled with a special opcode but what about the
+                     * line delta.
+                     */
+                    opcode = isSpecialOpcode(remainder, lineDelta);
+                    if (opcode != LN_undefined) {
+                        /*
+                         * Address remainder and line now fit.
+                         */
+                        pos = writeSpecialOpcode(context, opcode, buffer, pos);
+                    } else {
+                        /*
+                         * Ok, bump the line separately then use a special opcode for the address
+                         * remainder.
+                         */
+                        opcode = isSpecialOpcode(remainder, 0);
+                        assert opcode != LN_undefined;
+                        pos = writeAdvanceLineOp(context, lineDelta, buffer, pos);
+                        pos = writeSpecialOpcode(context, opcode, buffer, pos);
+                    }
+                } else {
+                    /*
+                     * Increment line and pc separately.
+                     */
+                    if (lineDelta != 0) {
+                        pos = writeAdvanceLineOp(context, lineDelta, buffer, pos);
+                    }
+                    /*
+                     * n.b. we might just have had an out of range line increment with a zero
+                     * address increment.
+                     */
+                    if (addressDelta > 0) {
+                        /*
+                         * See if we can use a ushort for the increment.
+                         */
+                        if (isFixedAdvancePC(addressDelta)) {
+                            pos = writeFixedAdvancePCOp(context, (short) addressDelta, buffer, pos);
+                        } else {
+                            pos = writeAdvancePCOp(context, addressDelta, buffer, pos);
+                        }
+                    }
+                    pos = writeCopyOp(context, buffer, pos);
+                }
+            }
+            /*
+             * Move line and address range on.
+             */
+            line += lineDelta;
+            address += addressDelta;
+        }
+        /*
+         * Append a final end sequence just below the next primary range.
+         */
+        if (address < primaryRange.getHi()) {
+            long addressDelta = primaryRange.getHi() - address;
+            /*
+             * Increment address before we write the end sequence.
+             */
+            pos = writeAdvancePCOp(context, addressDelta, buffer, pos);
+        }
+        pos = writeEndSequenceOp(context, buffer, pos);
+
+        return pos;
+    }
+
+    private int computeLineNumberTableSize(CompiledMethodEntry compiledEntry) {
+        /*
+         * Sigh -- we have to do this by generating the content even though we cannot write it into
+         * a byte[].
+         */
+        return writeLineNumberTable(null, compiledEntry, null, 0);
+    }
+
+    private int writeLineNumberTable(DebugContext context, CompiledMethodEntry compiledEntry, byte[] buffer, int p) {
+        int pos = p;
+        String methodName = compiledEntry.getPrimary().getFullMethodNameWithParams();
+        String fileName = compiledEntry.getTypedefEntry().getTypeName();
+        log(context, "  [0x%08x] %s %s", pos, methodName, fileName);
+        pos = writeCompiledMethodLineInfo(context, compiledEntry, buffer, pos);
+        return pos;
+    }
+
+    private static SubRange prologueLeafRange(CompiledMethodEntry compiledEntry) {
+        Iterator<SubRange> iterator = compiledEntry.leafRangeIterator();
+        if (iterator.hasNext()) {
+            SubRange range = iterator.next();
+            if (range.getLo() == compiledEntry.getPrimary().getLo()) {
+                return range;
+            }
+        }
+        return null;
+    }
+
+    private int writeCopyOp(DebugContext context, byte[] buffer, int p) {
+        DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNS_copy;
+        int pos = p;
+        debugCopyCount++;
+        verboseLog(context, "  [0x%08x] Copy %d", pos, debugCopyCount);
+        return writeLineOpcode(opcode, buffer, pos);
+    }
+
+    private int writeAdvancePCOp(DebugContext context, long uleb, byte[] buffer, int p) {
+        DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNS_advance_pc;
+        int pos = p;
+        debugAddress += uleb;
+        verboseLog(context, "  [0x%08x] Advance PC by %d to 0x%08x", pos, uleb, debugAddress);
+        pos = writeLineOpcode(opcode, buffer, pos);
+        return writeULEB(uleb, buffer, pos);
+    }
+
+    private int writeAdvanceLineOp(DebugContext context, long sleb, byte[] buffer, int p) {
+        DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNS_advance_line;
+        int pos = p;
+        debugLine += sleb;
+        verboseLog(context, "  [0x%08x] Advance Line by %d to %d", pos, sleb, debugLine);
+        pos = writeLineOpcode(opcode, buffer, pos);
+        return writeSLEB(sleb, buffer, pos);
+    }
+
+    private int writeSetFileOp(DebugContext context, String file, long uleb, byte[] buffer, int p) {
+        DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNS_set_file;
+        int pos = p;
+        verboseLog(context, "  [0x%08x] Set File Name to entry %d in the File Name Table (%s)", pos, uleb, file);
+        pos = writeLineOpcode(opcode, buffer, pos);
+        return writeULEB(uleb, buffer, pos);
+    }
+
+    @SuppressWarnings("unused")
+    private int writeSetColumnOp(DebugContext context, long uleb, byte[] buffer, int p) {
+        DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNS_set_column;
+        int pos = p;
+        pos = writeLineOpcode(opcode, buffer, pos);
+        return writeULEB(uleb, buffer, pos);
+    }
+
+    @SuppressWarnings("unused")
+    private int writeNegateStmtOp(DebugContext context, byte[] buffer, int p) {
+        DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNS_negate_stmt;
+        int pos = p;
+        return writeLineOpcode(opcode, buffer, pos);
+    }
+
+    private int writeSetBasicBlockOp(DebugContext context, byte[] buffer, int p) {
+        DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNS_set_basic_block;
+        int pos = p;
+        verboseLog(context, "  [0x%08x] Set basic block", pos);
+        return writeLineOpcode(opcode, buffer, pos);
+    }
+
+    private int writeConstAddPCOp(DebugContext context, byte[] buffer, int p) {
+        DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNS_const_add_pc;
+        int pos = p;
+        int advance = opcodeAddress((byte) 255);
+        debugAddress += advance;
+        verboseLog(context, "  [0x%08x] Advance PC by constant %d to 0x%08x", pos, advance, debugAddress);
+        return writeLineOpcode(opcode, buffer, pos);
+    }
+
+    private int writeFixedAdvancePCOp(DebugContext context, short arg, byte[] buffer, int p) {
+        DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNS_fixed_advance_pc;
+        int pos = p;
+        debugAddress += arg;
+        verboseLog(context, "  [0x%08x] Fixed advance Address by %d to 0x%08x", pos, arg, debugAddress);
+        pos = writeLineOpcode(opcode, buffer, pos);
+        return writeShort(arg, buffer, pos);
+    }
+
+    private int writeEndSequenceOp(DebugContext context, byte[] buffer, int p) {
+        DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNE_end_sequence;
+        int pos = p;
+        verboseLog(context, "  [0x%08x] Extended opcode 1: End sequence", pos);
+        debugAddress = debugTextBase;
+        debugLine = 1;
+        debugCopyCount = 0;
+        pos = writePrefixOpcode(buffer, pos);
+        /*
+         * Insert extended insn byte count as ULEB.
+         */
+        pos = writeULEB(1, buffer, pos);
+        return writeLineOpcode(opcode, buffer, pos);
+    }
+
+    private int writeSetAddressOp(DebugContext context, long arg, byte[] buffer, int p) {
+        DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNE_set_address;
+        int pos = p;
+        debugAddress = debugTextBase + (int) arg;
+        verboseLog(context, "  [0x%08x] Extended opcode 2: Set Address to 0x%08x", pos, debugAddress);
+        pos = writePrefixOpcode(buffer, pos);
+        /*
+         * Insert extended insn byte count as ULEB.
+         */
+        pos = writeULEB(9, buffer, pos);
+        pos = writeLineOpcode(opcode, buffer, pos);
+        return writeRelocatableCodeOffset(arg, buffer, pos);
+    }
+
+    @SuppressWarnings("unused")
+    private int writeDefineFileOp(DebugContext context, String file, long uleb1, long uleb2, long uleb3, byte[] buffer, int p) {
+        DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNE_define_file;
+        int pos = p;
+        /*
+         * Calculate bytes needed for opcode + args.
+         */
+        int fileBytes = countUTF8Bytes(file) + 1;
+        long insnBytes = 1;
+        insnBytes += fileBytes;
+        insnBytes += writeULEB(uleb1, scratch, 0);
+        insnBytes += writeULEB(uleb2, scratch, 0);
+        insnBytes += writeULEB(uleb3, scratch, 0);
+        verboseLog(context, "  [0x%08x] Extended opcode 3: Define File %s idx %d ts1 %d ts2 %d", pos, file, uleb1, uleb2, uleb3);
+        pos = writePrefixOpcode(buffer, pos);
+        /*
+         * Insert insn length as uleb.
+         */
+        pos = writeULEB(insnBytes, buffer, pos);
+        /*
+         * Insert opcode and args.
+         */
+        pos = writeLineOpcode(opcode, buffer, pos);
+        pos = writeUTF8StringBytes(file, buffer, pos);
+        pos = writeULEB(uleb1, buffer, pos);
+        pos = writeULEB(uleb2, buffer, pos);
+        return writeULEB(uleb3, buffer, pos);
+    }
+
+    private int writePrefixOpcode(byte[] buffer, int p) {
+        return writeLineOpcode(DwarfLineOpcode.DW_LNS_extended_prefix, buffer, p);
+    }
+
+    private int writeLineOpcode(DwarfLineOpcode opcode, byte[] buffer, int p) {
+        return writeByte(opcode.value(), buffer, p);
+    }
+
+    private static int opcodeId(byte opcode) {
+        int iopcode = opcode & 0xff;
+        return iopcode - LN_OPCODE_BASE;
+    }
+
+    private static int opcodeAddress(byte opcode) {
+        int iopcode = opcode & 0xff;
+        return (iopcode - LN_OPCODE_BASE) / LN_LINE_RANGE;
+    }
+
+    private static int opcodeLine(byte opcode) {
+        int iopcode = opcode & 0xff;
+        return ((iopcode - LN_OPCODE_BASE) % LN_LINE_RANGE) + LN_LINE_BASE;
+    }
+
+    private int writeSpecialOpcode(DebugContext context, byte opcode, byte[] buffer, int p) {
+        int pos = p;
+        if (debug && opcode == 0) {
+            verboseLog(context, "  [0x%08x] ERROR Special Opcode %d: Address 0x%08x Line %d", debugAddress, debugLine);
+        }
+        debugAddress += opcodeAddress(opcode);
+        debugLine += opcodeLine(opcode);
+        verboseLog(context, "  [0x%08x] Special Opcode %d: advance Address by %d to 0x%08x and Line by %d to %d",
+                        pos, opcodeId(opcode), opcodeAddress(opcode), debugAddress, opcodeLine(opcode), debugLine);
+        return writeByte(opcode, buffer, pos);
+    }
+
+    private static final int MAX_ADDRESS_ONLY_DELTA = (0xff - LN_OPCODE_BASE) / LN_LINE_RANGE;
+    private static final int MAX_ADDPC_DELTA = MAX_ADDRESS_ONLY_DELTA + (MAX_ADDRESS_ONLY_DELTA - 1);
+
+    private static byte isSpecialOpcode(long addressDelta, long lineDelta) {
+        if (addressDelta < 0) {
+            return LN_undefined;
+        }
+        if (lineDelta >= LN_LINE_BASE) {
+            long offsetLineDelta = lineDelta - LN_LINE_BASE;
+            if (offsetLineDelta < LN_LINE_RANGE) {
+                /*
+                 * The line delta can be encoded. Check if address is ok.
+                 */
+                if (addressDelta <= MAX_ADDRESS_ONLY_DELTA) {
+                    long opcode = LN_OPCODE_BASE + (addressDelta * LN_LINE_RANGE) + offsetLineDelta;
+                    if (opcode <= 255) {
+                        return (byte) opcode;
+                    }
+                }
+            }
+        }
+
+        /*
+         * Answer no by returning an invalid opcode.
+         */
+        return LN_undefined;
+    }
+
+    private static int isConstAddPC(long addressDelta) {
+        if (addressDelta < MAX_ADDRESS_ONLY_DELTA) {
+            return 0;
+        }
+        if (addressDelta <= MAX_ADDPC_DELTA) {
+            return (int) (addressDelta - MAX_ADDRESS_ONLY_DELTA);
+        } else {
+            return 0;
+        }
+    }
+
+    private static boolean isFixedAdvancePC(long addressDiff) {
+        return addressDiff >= 0 && addressDiff < 0xffff;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfLocSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfLocSectionImpl.java
new file mode 100644
index 000000000000..689193dd2158
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfLocSectionImpl.java
@@ -0,0 +1,608 @@
+/*
+ * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2022, 2022, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.dwarf;
+
+import com.oracle.objectfile.BuildDependency;
+import com.oracle.objectfile.LayoutDecision;
+import com.oracle.objectfile.LayoutDecisionMap;
+import com.oracle.objectfile.ObjectFile;
+import com.oracle.objectfile.elf.ELFMachine;
+import com.oracle.objectfile.elf.ELFObjectFile;
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocalInfo;
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocalValueInfo;
+import com.oracle.objectfile.runtime.debugentry.CompiledMethodEntry;
+import com.oracle.objectfile.runtime.debugentry.range.Range;
+import com.oracle.objectfile.runtime.debugentry.range.SubRange;
+import com.oracle.objectfile.runtime.dwarf.constants.DwarfExpressionOpcode;
+import com.oracle.objectfile.runtime.dwarf.constants.DwarfSectionName;
+import jdk.graal.compiler.debug.DebugContext;
+import jdk.vm.ci.aarch64.AArch64;
+import jdk.vm.ci.amd64.AMD64;
+import jdk.vm.ci.meta.JavaConstant;
+import jdk.vm.ci.meta.JavaKind;
+import jdk.vm.ci.meta.PrimitiveConstant;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Section generator for debug_loc section.
+ */
+public class RuntimeDwarfLocSectionImpl extends RuntimeDwarfSectionImpl {
+
+    /*
+     * array used to map compiler register indices to the indices expected by DWARF
+     */
+    private int[] dwarfRegMap;
+
+    /*
+     * index used by DWARF for the stack pointer register
+     */
+    private int dwarfStackRegister;
+
+    private static final LayoutDecision.Kind[] targetLayoutKinds = {
+                    LayoutDecision.Kind.CONTENT,
+                    LayoutDecision.Kind.SIZE,
+                    /* Add this so we can use the text section base address for debug. */
+                    LayoutDecision.Kind.VADDR};
+
+    public RuntimeDwarfLocSectionImpl(RuntimeDwarfDebugInfo dwarfSections) {
+        // debug_loc section depends on text section
+        super(dwarfSections, DwarfSectionName.DW_LOC_SECTION, DwarfSectionName.TEXT_SECTION, targetLayoutKinds);
+        initDwarfRegMap();
+    }
+
+    @Override
+    public Set<BuildDependency> getDependencies(Map<ObjectFile.Element, LayoutDecisionMap> decisions) {
+        Set<BuildDependency> deps = super.getDependencies(decisions);
+        LayoutDecision ourContent = decisions.get(getElement()).getDecision(LayoutDecision.Kind.CONTENT);
+        /*
+         * Order all content decisions after all size decisions by making loc section content depend
+         * on abbrev section size.
+         */
+        String abbrevSectionName = dwarfSections.getAbbrevSectionImpl().getSectionName();
+        ELFObjectFile.ELFSection abbrevSection = (ELFObjectFile.ELFSection) getElement().getOwner().elementForName(abbrevSectionName);
+        LayoutDecision sizeDecision = decisions.get(abbrevSection).getDecision(LayoutDecision.Kind.SIZE);
+        deps.add(BuildDependency.createOrGet(ourContent, sizeDecision));
+        return deps;
+    }
+
+    @Override
+    public void createContent() {
+        assert !contentByteArrayCreated();
+
+        byte[] buffer = null;
+        int len = generateContent(null, buffer);
+
+        buffer = new byte[len];
+        super.setContent(buffer);
+    }
+
+    @Override
+    public void writeContent(DebugContext context) {
+        assert contentByteArrayCreated();
+
+        byte[] buffer = getContent();
+        int size = buffer.length;
+        int pos = 0;
+
+        enableLog(context, pos);
+        log(context, "  [0x%08x] DEBUG_LOC", pos);
+        log(context, "  [0x%08x] size = 0x%08x", pos, size);
+
+        pos = generateContent(context, buffer);
+        assert pos == size;
+    }
+
+    private int generateContent(DebugContext context, byte[] buffer) {
+        int pos = 0;
+        // n.b. We could do this by iterating over the compiled methods sequence. the
+        // reason for doing it in class entry order is to because it mirrors the
+        // order in which entries appear in the info section. That stops objdump
+        // posting spurious messages about overlaps and holes in the var ranges.
+        CompiledMethodEntry compiledEntry = dwarfSections.getCompiledMethod();
+        pos = writeCompiledMethodLocation(context, compiledEntry, buffer, pos);
+        return pos;
+    }
+
+    private int writeCompiledMethodLocation(DebugContext context, CompiledMethodEntry compiledEntry, byte[] buffer, int p) {
+        int pos = p;
+        Range primary = compiledEntry.getPrimary();
+        /*
+         * Note that offsets are written relative to the primary range base. This requires writing a
+         * base address entry before each of the location list ranges. It is possible to default the
+         * base address to the low_pc value of the compile unit for the compiled method's owning
+         * class, saving two words per location list. However, that forces the debugger to do a lot
+         * more up-front cross-referencing of CUs when it needs to resolve code addresses e.g. to
+         * set a breakpoint, leading to a very slow response for the user.
+         */
+        long base = primary.getLo();
+        log(context, "  [0x%08x] top level locations [0x%x, 0x%x] method %s", pos, primary.getLo(), primary.getHi(), primary.getFullMethodNameWithParams());
+        pos = writeTopLevelLocations(context, compiledEntry, base, buffer, pos);
+        if (!primary.isLeaf()) {
+            log(context, "  [0x%08x] inline locations %s", pos, primary.getFullMethodNameWithParams());
+            pos = writeInlineLocations(context, compiledEntry, base, buffer, pos);
+        }
+        return pos;
+    }
+
+    private int writeTopLevelLocations(DebugContext context, CompiledMethodEntry compiledEntry, long base, byte[] buffer, int p) {
+        int pos = p;
+        Range primary = compiledEntry.getPrimary();
+        HashMap<DebugLocalInfo, List<SubRange>> varRangeMap = primary.getVarRangeMap();
+        for (DebugLocalInfo local : varRangeMap.keySet()) {
+            List<SubRange> rangeList = varRangeMap.get(local);
+            if (!rangeList.isEmpty()) {
+                setRangeLocalIndex(primary, local, pos);
+                pos = writeVarLocations(context, local, base, rangeList, buffer, pos);
+            }
+        }
+        return pos;
+    }
+
+    private int writeInlineLocations(DebugContext context, CompiledMethodEntry compiledEntry, long base, byte[] buffer, int p) {
+        int pos = p;
+        Range primary = compiledEntry.getPrimary();
+        assert !primary.isLeaf();
+        Iterator<SubRange> iterator = compiledEntry.topDownRangeIterator();
+        while (iterator.hasNext()) {
+            SubRange subrange = iterator.next();
+            if (subrange.isLeaf()) {
+                continue;
+            }
+            HashMap<DebugLocalInfo, List<SubRange>> varRangeMap = subrange.getVarRangeMap();
+            for (DebugLocalInfo local : varRangeMap.keySet()) {
+                List<SubRange> rangeList = varRangeMap.get(local);
+                if (!rangeList.isEmpty()) {
+                    setRangeLocalIndex(subrange, local, pos);
+                    pos = writeVarLocations(context, local, base, rangeList, buffer, pos);
+                }
+            }
+        }
+        return pos;
+    }
+
+    private int writeVarLocations(DebugContext context, DebugLocalInfo local, long base, List<SubRange> rangeList, byte[] buffer, int p) {
+        assert !rangeList.isEmpty();
+        int pos = p;
+        // collect ranges and values, merging adjacent ranges that have equal value
+        List<LocalValueExtent> extents = LocalValueExtent.coalesce(local, rangeList);
+
+        // write start of primary range as base address - see comment above for reasons why
+        // we choose ot do this rather than use the relevant compile unit low_pc
+        pos = writeAttrData8(-1L, buffer, pos);
+        pos = writeAttrAddress(base, buffer, pos);
+        // write ranges as offsets from base
+        boolean first = true;
+        for (LocalValueExtent extent : extents) {
+            DebugLocalValueInfo value = extent.value;
+            assert (value != null);
+            System.out.printf("  [0x%08x]     local  %s:%s [0x%x, 0x%x] = %s%n", pos, value.name(), value.typeName(), extent.getLo(), extent.getHi(), formatValue(value));
+            log(context, "  [0x%08x]     local  %s:%s [0x%x, 0x%x] = %s", pos, value.name(), value.typeName(), extent.getLo(), extent.getHi(), formatValue(value));
+            if (first) {
+                pos = writeAttrData8(0, buffer, pos);
+                first = false;
+            } else {
+                pos = writeAttrData8(extent.getLo() - base, buffer, pos);
+            }
+            pos = writeAttrData8(extent.getHi() - base, buffer, pos);
+            switch (value.localKind()) {
+                case REGISTER:
+                    pos = writeRegisterLocation(context, value.regIndex(), buffer, pos);
+                    break;
+                case STACKSLOT:
+                    pos = writeStackLocation(context, value.stackSlot(), buffer, pos);
+                    break;
+                case CONSTANT:
+                    JavaConstant constant = value.constantValue();
+                    if (constant instanceof PrimitiveConstant) {
+                        pos = writePrimitiveConstantLocation(context, value.constantValue(), buffer, pos);
+                    } else if (constant.isNull()) {
+                        pos = writeNullConstantLocation(context, value.constantValue(), buffer, pos);
+                    }
+                    break;
+                default:
+                    assert false : "Should not reach here!";
+                    break;
+            }
+        }
+        // write list terminator
+        pos = writeAttrData8(0, buffer, pos);
+        pos = writeAttrData8(0, buffer, pos);
+
+        return pos;
+    }
+
+    private int writeRegisterLocation(DebugContext context, int regIndex, byte[] buffer, int p) {
+        int targetIdx = mapToDwarfReg(regIndex);
+        assert targetIdx >= 0;
+        int pos = p;
+        if (targetIdx < 0x20) {
+            // can write using DW_OP_reg<n>
+            short byteCount = 1;
+            byte reg = (byte) targetIdx;
+            pos = writeShort(byteCount, buffer, pos);
+            pos = writeExprOpcodeReg(reg, buffer, pos);
+            verboseLog(context, "  [0x%08x]     REGOP count %d op 0x%x", pos, byteCount, DwarfExpressionOpcode.DW_OP_reg0.value() + reg);
+        } else {
+            // have to write using DW_OP_regx + LEB operand
+            assert targetIdx < 128 : "unexpectedly high reg index!";
+            short byteCount = 2;
+            pos = writeShort(byteCount, buffer, pos);
+            pos = writeExprOpcode(DwarfExpressionOpcode.DW_OP_regx, buffer, pos);
+            pos = writeULEB(targetIdx, buffer, pos);
+            verboseLog(context, "  [0x%08x]     REGOP count %d op 0x%x reg %d", pos, byteCount, DwarfExpressionOpcode.DW_OP_regx.value(), targetIdx);
+            // target idx written as ULEB should fit in one byte
+            assert pos == p + 4 : "wrote the wrong number of bytes!";
+        }
+        return pos;
+    }
+
+    private int writeStackLocation(DebugContext context, int offset, byte[] buffer, int p) {
+        int pos = p;
+        short byteCount = 0;
+        byte sp = (byte) getDwarfStackRegister();
+        int patchPos = pos;
+        pos = writeShort(byteCount, buffer, pos);
+        int zeroPos = pos;
+        if (sp < 0x20) {
+            // fold the base reg index into the op
+            pos = writeExprOpcodeBReg(sp, buffer, pos);
+        } else {
+            pos = writeExprOpcode(DwarfExpressionOpcode.DW_OP_bregx, buffer, pos);
+            // pass base reg index as a ULEB operand
+            pos = writeULEB(sp, buffer, pos);
+        }
+        pos = writeSLEB(offset, buffer, pos);
+        // now backpatch the byte count
+        byteCount = (byte) (pos - zeroPos);
+        writeShort(byteCount, buffer, patchPos);
+        if (sp < 0x20) {
+            verboseLog(context, "  [0x%08x]     STACKOP count %d op 0x%x offset %d", pos, byteCount, (DwarfExpressionOpcode.DW_OP_breg0.value() + sp), 0 - offset);
+        } else {
+            verboseLog(context, "  [0x%08x]     STACKOP count %d op 0x%x reg %d offset %d", pos, byteCount, DwarfExpressionOpcode.DW_OP_bregx.value(), sp, 0 - offset);
+        }
+        return pos;
+    }
+
+    private int writePrimitiveConstantLocation(DebugContext context, JavaConstant constant, byte[] buffer, int p) {
+        assert constant instanceof PrimitiveConstant;
+        int pos = p;
+        DwarfExpressionOpcode op = DwarfExpressionOpcode.DW_OP_implicit_value;
+        JavaKind kind = constant.getJavaKind();
+        int dataByteCount = kind.getByteCount();
+        // total bytes is op + uleb + dataByteCount
+        int byteCount = 1 + 1 + dataByteCount;
+        pos = writeShort((short) byteCount, buffer, pos);
+        pos = writeExprOpcode(op, buffer, pos);
+        pos = writeULEB(dataByteCount, buffer, pos);
+        if (dataByteCount == 1) {
+            if (kind == JavaKind.Boolean) {
+                pos = writeByte((byte) (constant.asBoolean() ? 1 : 0), buffer, pos);
+            } else {
+                pos = writeByte((byte) constant.asInt(), buffer, pos);
+            }
+        } else if (dataByteCount == 2) {
+            pos = writeShort((short) constant.asInt(), buffer, pos);
+        } else if (dataByteCount == 4) {
+            int i = (kind == JavaKind.Int ? constant.asInt() : Float.floatToRawIntBits(constant.asFloat()));
+            pos = writeInt(i, buffer, pos);
+        } else {
+            long l = (kind == JavaKind.Long ? constant.asLong() : Double.doubleToRawLongBits(constant.asDouble()));
+            pos = writeLong(l, buffer, pos);
+        }
+        verboseLog(context, "  [0x%08x]     CONSTANT (primitive) %s", pos, constant.toValueString());
+        return pos;
+    }
+
+    private int writeNullConstantLocation(DebugContext context, JavaConstant constant, byte[] buffer, int p) {
+        assert constant.isNull();
+        int pos = p;
+        DwarfExpressionOpcode op = DwarfExpressionOpcode.DW_OP_implicit_value;
+        int dataByteCount = 8;
+        // total bytes is op + uleb + dataByteCount
+        int byteCount = 1 + 1 + dataByteCount;
+        pos = writeShort((short) byteCount, buffer, pos);
+        pos = writeExprOpcode(op, buffer, pos);
+        pos = writeULEB(dataByteCount, buffer, pos);
+        pos = writeAttrData8(0, buffer, pos);
+        verboseLog(context, "  [0x%08x]     CONSTANT (null) %s", pos, constant.toValueString());
+        return pos;
+    }
+
+    // auxiliary class used to collect per-range locations for a given local
+    // merging adjacent ranges with the same location
+    static class LocalValueExtent {
+        long lo;
+        long hi;
+        DebugLocalValueInfo value;
+
+        LocalValueExtent(long lo, long hi, DebugLocalValueInfo value) {
+            this.lo = lo;
+            this.hi = hi;
+            this.value = value;
+        }
+
+        @SuppressWarnings("unused")
+        boolean shouldMerge(long otherLo, long otherHi, DebugLocalValueInfo otherValue) {
+            // ranges need to be contiguous to merge
+            if (hi != otherLo) {
+                return false;
+            }
+            return value.equals(otherValue);
+        }
+
+        private LocalValueExtent maybeMerge(long otherLo, long otherHi, DebugLocalValueInfo otherValue) {
+            if (shouldMerge(otherLo, otherHi, otherValue)) {
+                // We can extend the current extent to cover the next one.
+                this.hi = otherHi;
+                return null;
+            } else {
+                // we need a new extent
+                return new LocalValueExtent(otherLo, otherHi, otherValue);
+            }
+        }
+
+        public long getLo() {
+            return lo;
+        }
+
+        public long getHi() {
+            return hi;
+        }
+
+        public DebugLocalValueInfo getValue() {
+            return value;
+        }
+
+        public static List<LocalValueExtent> coalesce(DebugLocalInfo local, List<SubRange> rangeList) {
+            List<LocalValueExtent> extents = new ArrayList<>();
+            LocalValueExtent current = null;
+            for (SubRange range : rangeList) {
+                if (current == null) {
+                    current = new LocalValueExtent(range.getLo(), range.getHi(), range.lookupValue(local));
+                    extents.add(current);
+                } else {
+                    LocalValueExtent toAdd = current.maybeMerge(range.getLo(), range.getHi(), range.lookupValue(local));
+                    if (toAdd != null) {
+                        extents.add(toAdd);
+                        current = toAdd;
+                    }
+                }
+            }
+            return extents;
+        }
+    }
+
+    private int getDwarfStackRegister() {
+        return dwarfStackRegister;
+    }
+
+    private int mapToDwarfReg(int regIdx) {
+        assert regIdx >= 0 : "negative register index!";
+        assert regIdx < dwarfRegMap.length : String.format("register index %d exceeds map range %d", regIdx, dwarfRegMap.length);
+        return dwarfRegMap[regIdx];
+    }
+
+    private void initDwarfRegMap() {
+        if (dwarfSections.elfMachine == ELFMachine.AArch64) {
+            dwarfRegMap = GRAAL_AARCH64_TO_DWARF_REG_MAP;
+            dwarfStackRegister = DwarfRegEncodingAArch64.SP.getDwarfEncoding();
+        } else {
+            assert dwarfSections.elfMachine == ELFMachine.X86_64 : "must be";
+            dwarfRegMap = GRAAL_X86_64_TO_DWARF_REG_MAP;
+            dwarfStackRegister = DwarfRegEncodingAMD64.RSP.getDwarfEncoding();
+        }
+    }
+
+    // Register numbers used by DWARF for AArch64 registers encoded
+    // along with their respective GraalVM compiler number.
+    public enum DwarfRegEncodingAArch64 {
+        R0(0, AArch64.r0.number),
+        R1(1, AArch64.r1.number),
+        R2(2, AArch64.r2.number),
+        R3(3, AArch64.r3.number),
+        R4(4, AArch64.r4.number),
+        R5(5, AArch64.r5.number),
+        R6(6, AArch64.r6.number),
+        R7(7, AArch64.r7.number),
+        R8(8, AArch64.r8.number),
+        R9(9, AArch64.r9.number),
+        R10(10, AArch64.r10.number),
+        R11(11, AArch64.r11.number),
+        R12(12, AArch64.r12.number),
+        R13(13, AArch64.r13.number),
+        R14(14, AArch64.r14.number),
+        R15(15, AArch64.r15.number),
+        R16(16, AArch64.r16.number),
+        R17(17, AArch64.r17.number),
+        R18(18, AArch64.r18.number),
+        R19(19, AArch64.r19.number),
+        R20(20, AArch64.r20.number),
+        R21(21, AArch64.r21.number),
+        R22(22, AArch64.r22.number),
+        R23(23, AArch64.r23.number),
+        R24(24, AArch64.r24.number),
+        R25(25, AArch64.r25.number),
+        R26(26, AArch64.r26.number),
+        R27(27, AArch64.r27.number),
+        R28(28, AArch64.r28.number),
+        R29(29, AArch64.r29.number),
+        R30(30, AArch64.r30.number),
+        R31(31, AArch64.r31.number),
+        ZR(31, AArch64.zr.number),
+        SP(31, AArch64.sp.number),
+        V0(64, AArch64.v0.number),
+        V1(65, AArch64.v1.number),
+        V2(66, AArch64.v2.number),
+        V3(67, AArch64.v3.number),
+        V4(68, AArch64.v4.number),
+        V5(69, AArch64.v5.number),
+        V6(70, AArch64.v6.number),
+        V7(71, AArch64.v7.number),
+        V8(72, AArch64.v8.number),
+        V9(73, AArch64.v9.number),
+        V10(74, AArch64.v10.number),
+        V11(75, AArch64.v11.number),
+        V12(76, AArch64.v12.number),
+        V13(77, AArch64.v13.number),
+        V14(78, AArch64.v14.number),
+        V15(79, AArch64.v15.number),
+        V16(80, AArch64.v16.number),
+        V17(81, AArch64.v17.number),
+        V18(82, AArch64.v18.number),
+        V19(83, AArch64.v19.number),
+        V20(84, AArch64.v20.number),
+        V21(85, AArch64.v21.number),
+        V22(86, AArch64.v22.number),
+        V23(87, AArch64.v23.number),
+        V24(88, AArch64.v24.number),
+        V25(89, AArch64.v25.number),
+        V26(90, AArch64.v26.number),
+        V27(91, AArch64.v27.number),
+        V28(92, AArch64.v28.number),
+        V29(93, AArch64.v29.number),
+        V30(94, AArch64.v30.number),
+        V31(95, AArch64.v31.number);
+
+        private final int dwarfEncoding;
+        private final int graalEncoding;
+
+        DwarfRegEncodingAArch64(int dwarfEncoding, int graalEncoding) {
+            this.dwarfEncoding = dwarfEncoding;
+            this.graalEncoding = graalEncoding;
+        }
+
+        public static int graalOrder(DwarfRegEncodingAArch64 e1, DwarfRegEncodingAArch64 e2) {
+            return Integer.compare(e1.graalEncoding, e2.graalEncoding);
+        }
+
+        public int getDwarfEncoding() {
+            return dwarfEncoding;
+        }
+    }
+
+    // Map from compiler AArch64 register numbers to corresponding DWARF AArch64 register encoding.
+    // Register numbers for compiler general purpose and float registers occupy index ranges 0-31
+    // and 34-65 respectively. Table entries provided the corresponding number used by DWARF to
+    // identify the same register. Note that the table includes entries for ZR (32) and SP (33)
+    // even though we should not see those register numbers appearing in location values.
+    private static final int[] GRAAL_AARCH64_TO_DWARF_REG_MAP = Arrays.stream(DwarfRegEncodingAArch64.values()).sorted(DwarfRegEncodingAArch64::graalOrder)
+                    .mapToInt(DwarfRegEncodingAArch64::getDwarfEncoding).toArray();
+
+    // Register numbers used by DWARF for AMD64 registers encoded
+    // along with their respective GraalVM compiler number. n.b. some of the initial
+    // 8 general purpose registers have different Dwarf and GraalVM encodings. For
+    // example the compiler number for RDX is 3 while the DWARF number for RDX is 1.
+    public enum DwarfRegEncodingAMD64 {
+        RAX(0, AMD64.rax.number),
+        RDX(1, AMD64.rdx.number),
+        RCX(2, AMD64.rcx.number),
+        RBX(3, AMD64.rbx.number),
+        RSI(4, AMD64.rsi.number),
+        RDI(5, AMD64.rdi.number),
+        RBP(6, AMD64.rbp.number),
+        RSP(7, AMD64.rsp.number),
+        R8(8, AMD64.r8.number),
+        R9(9, AMD64.r9.number),
+        R10(10, AMD64.r10.number),
+        R11(11, AMD64.r11.number),
+        R12(12, AMD64.r12.number),
+        R13(13, AMD64.r13.number),
+        R14(14, AMD64.r14.number),
+        R15(15, AMD64.r15.number),
+        XMM0(17, AMD64.xmm0.number),
+        XMM1(18, AMD64.xmm1.number),
+        XMM2(19, AMD64.xmm2.number),
+        XMM3(20, AMD64.xmm3.number),
+        XMM4(21, AMD64.xmm4.number),
+        XMM5(22, AMD64.xmm5.number),
+        XMM6(23, AMD64.xmm6.number),
+        XMM7(24, AMD64.xmm7.number),
+        XMM8(25, AMD64.xmm8.number),
+        XMM9(26, AMD64.xmm9.number),
+        XMM10(27, AMD64.xmm10.number),
+        XMM11(28, AMD64.xmm11.number),
+        XMM12(29, AMD64.xmm12.number),
+        XMM13(30, AMD64.xmm13.number),
+        XMM14(31, AMD64.xmm14.number),
+        XMM15(32, AMD64.xmm15.number),
+        XMM16(60, AMD64.xmm16.number),
+        XMM17(61, AMD64.xmm17.number),
+        XMM18(62, AMD64.xmm18.number),
+        XMM19(63, AMD64.xmm19.number),
+        XMM20(64, AMD64.xmm20.number),
+        XMM21(65, AMD64.xmm21.number),
+        XMM22(66, AMD64.xmm22.number),
+        XMM23(67, AMD64.xmm23.number),
+        XMM24(68, AMD64.xmm24.number),
+        XMM25(69, AMD64.xmm25.number),
+        XMM26(70, AMD64.xmm26.number),
+        XMM27(71, AMD64.xmm27.number),
+        XMM28(72, AMD64.xmm28.number),
+        XMM29(73, AMD64.xmm29.number),
+        XMM30(74, AMD64.xmm30.number),
+        XMM31(75, AMD64.xmm31.number),
+        K0(118, AMD64.k0.number),
+        K1(119, AMD64.k1.number),
+        K2(120, AMD64.k2.number),
+        K3(121, AMD64.k3.number),
+        K4(122, AMD64.k4.number),
+        K5(123, AMD64.k5.number),
+        K6(124, AMD64.k6.number),
+        K7(125, AMD64.k7.number);
+
+        private final int dwarfEncoding;
+        private final int graalEncoding;
+
+        DwarfRegEncodingAMD64(int dwarfEncoding, int graalEncoding) {
+            this.dwarfEncoding = dwarfEncoding;
+            this.graalEncoding = graalEncoding;
+        }
+
+        public static int graalOrder(DwarfRegEncodingAMD64 e1, DwarfRegEncodingAMD64 e2) {
+            return Integer.compare(e1.graalEncoding, e2.graalEncoding);
+        }
+
+        public int getDwarfEncoding() {
+            return dwarfEncoding;
+        }
+    }
+
+    // Map from compiler X86_64 register numbers to corresponding DWARF AMD64 register encoding.
+    // Register numbers for general purpose and float registers occupy index ranges 0-15 and 16-31
+    // respectively. Table entries provide the corresponding number used by DWARF to identify the
+    // same register.
+    private static final int[] GRAAL_X86_64_TO_DWARF_REG_MAP = Arrays.stream(DwarfRegEncodingAMD64.values()).sorted(DwarfRegEncodingAMD64::graalOrder).mapToInt(DwarfRegEncodingAMD64::getDwarfEncoding)
+                    .toArray();
+
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfSectionImpl.java
new file mode 100644
index 000000000000..7d674335a638
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfSectionImpl.java
@@ -0,0 +1,795 @@
+/*
+ * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.dwarf;
+
+import com.oracle.objectfile.BasicProgbitsSectionImpl;
+import com.oracle.objectfile.BuildDependency;
+import com.oracle.objectfile.LayoutDecision;
+import com.oracle.objectfile.LayoutDecisionMap;
+import com.oracle.objectfile.ObjectFile;
+import com.oracle.objectfile.elf.ELFMachine;
+import com.oracle.objectfile.elf.ELFObjectFile;
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocalInfo;
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocalValueInfo;
+import com.oracle.objectfile.runtime.debugentry.CompiledMethodEntry;
+import com.oracle.objectfile.runtime.debugentry.DirEntry;
+import com.oracle.objectfile.runtime.debugentry.FileEntry;
+import com.oracle.objectfile.runtime.debugentry.MethodEntry;
+import com.oracle.objectfile.runtime.debugentry.PrimitiveTypeEntry;
+import com.oracle.objectfile.runtime.debugentry.TypeEntry;
+import com.oracle.objectfile.runtime.debugentry.TypedefEntry;
+import com.oracle.objectfile.runtime.debugentry.range.Range;
+import com.oracle.objectfile.runtime.dwarf.RuntimeDwarfDebugInfo.AbbrevCode;
+import com.oracle.objectfile.runtime.dwarf.constants.DwarfExpressionOpcode;
+import com.oracle.objectfile.runtime.dwarf.constants.DwarfFlag;
+import com.oracle.objectfile.runtime.dwarf.constants.DwarfSectionName;
+import com.oracle.objectfile.runtime.dwarf.constants.DwarfTag;
+import com.oracle.objectfile.runtime.dwarf.constants.DwarfVersion;
+import jdk.graal.compiler.debug.DebugContext;
+import jdk.vm.ci.meta.ResolvedJavaType;
+
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A class from which all DWARF debug sections inherit providing common behaviours.
+ */
+public abstract class RuntimeDwarfSectionImpl extends BasicProgbitsSectionImpl {
+    // auxiliary class used to track byte array positions
+    protected class Cursor {
+        private int pos;
+
+        public Cursor() {
+            this(0);
+        }
+
+        public Cursor(int p) {
+            assert p >= 0;
+            set(p);
+        }
+
+        public void set(int p) {
+            assert p >= 0;
+            pos = p;
+        }
+
+        public int add(int d) {
+            assert pos + d >= 0;
+            pos += d;
+            return pos;
+        }
+
+        public int get() {
+            return pos;
+        }
+    }
+
+    protected final RuntimeDwarfDebugInfo dwarfSections;
+    protected boolean debug = false;
+    protected long debugTextBase = 0;
+    protected long debugAddress = 0;
+    protected int debugBase = 0;
+
+    /**
+     * The name of this section.
+     */
+    private final DwarfSectionName sectionName;
+
+    /**
+     * The name of the section which needs to have been created prior to creating this section.
+     */
+    private final DwarfSectionName targetSectionName;
+
+    /**
+     * The layout properties of the target section which need to have been decided before the
+     * contents of this section can be created.
+     */
+    private final LayoutDecision.Kind[] targetSectionKinds;
+    /**
+     * The default layout properties.
+     */
+    private static final LayoutDecision.Kind[] defaultTargetSectionKinds = {
+                    LayoutDecision.Kind.CONTENT,
+                    LayoutDecision.Kind.SIZE
+    };
+
+    public RuntimeDwarfSectionImpl(RuntimeDwarfDebugInfo dwarfSections, DwarfSectionName name, DwarfSectionName targetName) {
+        this(dwarfSections, name, targetName, defaultTargetSectionKinds);
+    }
+
+    public RuntimeDwarfSectionImpl(RuntimeDwarfDebugInfo dwarfSections, DwarfSectionName sectionName, DwarfSectionName targetSectionName, LayoutDecision.Kind[] targetKinds) {
+        this.dwarfSections = dwarfSections;
+        this.sectionName = sectionName;
+        this.targetSectionName = targetSectionName;
+        this.targetSectionKinds = targetKinds;
+    }
+
+    public boolean isAArch64() {
+        return dwarfSections.elfMachine == ELFMachine.AArch64;
+    }
+
+    /**
+     * Creates the target byte[] array used to define the section contents.
+     *
+     * The main task of this method is to precompute the size of the debug section. given the
+     * complexity of the data layouts that invariably requires performing a dummy write of the
+     * contents, inserting bytes into a small, scratch buffer only when absolutely necessary.
+     * subclasses may also cache some information for use when writing the contents.
+     */
+    public abstract void createContent();
+
+    /**
+     * Populates the byte[] array used to contain the section contents.
+     *
+     * In most cases this task reruns the operations performed under createContent but this time
+     * actually writing data to the target byte[].
+     */
+    public abstract void writeContent(DebugContext debugContext);
+
+    /**
+     * Check whether the contents byte array has been sized and created. n.b. this does not imply
+     * that data has been written to the byte array.
+     * 
+     * @return true if the contents byte array has been sized and created otherwise false.
+     */
+    public boolean contentByteArrayCreated() {
+        return getContent() != null;
+    }
+
+    @Override
+    public boolean isLoadable() {
+        /*
+         * Even though we're a progbits section impl we're not actually loadable.
+         */
+        return false;
+    }
+
+    private String debugSectionLogName() {
+        /*
+         * Use prefix dwarf plus the section name (which already includes a dot separator) for the
+         * context key. For example messages for info section will be keyed using dwarf.debug_info.
+         * Other info formats use their own format-specific prefix.
+         */
+        assert getSectionName().startsWith(".debug");
+        return "dwarf" + getSectionName();
+    }
+
+    protected void enableLog(DebugContext context, int pos) {
+        /*
+         * Debug output is disabled during the first pass where we size the buffer. this is called
+         * to enable it during the second pass where the buffer gets written, but only if the scope
+         * is enabled.
+         */
+        assert contentByteArrayCreated();
+
+        if (context.areScopesEnabled()) {
+            debug = true;
+            debugBase = pos;
+            debugAddress = debugTextBase;
+        }
+    }
+
+    protected void log(DebugContext context, String format, Object... args) {
+        if (debug) {
+            context.logv(DebugContext.INFO_LEVEL, format, args);
+        }
+    }
+
+    protected void verboseLog(DebugContext context, String format, Object... args) {
+        if (debug) {
+            context.logv(DebugContext.VERBOSE_LEVEL, format, args);
+        }
+    }
+
+    protected boolean littleEndian() {
+        return dwarfSections.getByteOrder() == ByteOrder.LITTLE_ENDIAN;
+    }
+
+    /*
+     * Base level put methods that assume a non-null buffer.
+     */
+
+    protected int putByte(byte b, byte[] buffer, int p) {
+        int pos = p;
+        buffer[pos++] = b;
+        return pos;
+    }
+
+    protected int putShort(short s, byte[] buffer, int p) {
+        int pos = p;
+        if (littleEndian()) {
+            buffer[pos++] = (byte) (s & 0xff);
+            buffer[pos++] = (byte) ((s >> 8) & 0xff);
+        } else {
+            buffer[pos++] = (byte) ((s >> 8) & 0xff);
+            buffer[pos++] = (byte) (s & 0xff);
+        }
+        return pos;
+    }
+
+    protected int putInt(int i, byte[] buffer, int p) {
+        int pos = p;
+        if (littleEndian()) {
+            buffer[pos++] = (byte) (i & 0xff);
+            buffer[pos++] = (byte) ((i >> 8) & 0xff);
+            buffer[pos++] = (byte) ((i >> 16) & 0xff);
+            buffer[pos++] = (byte) ((i >> 24) & 0xff);
+        } else {
+            buffer[pos++] = (byte) ((i >> 24) & 0xff);
+            buffer[pos++] = (byte) ((i >> 16) & 0xff);
+            buffer[pos++] = (byte) ((i >> 8) & 0xff);
+            buffer[pos++] = (byte) (i & 0xff);
+        }
+        return pos;
+    }
+
+    protected int putLong(long l, byte[] buffer, int p) {
+        int pos = p;
+        if (littleEndian()) {
+            buffer[pos++] = (byte) (l & 0xff);
+            buffer[pos++] = (byte) ((l >> 8) & 0xff);
+            buffer[pos++] = (byte) ((l >> 16) & 0xff);
+            buffer[pos++] = (byte) ((l >> 24) & 0xff);
+            buffer[pos++] = (byte) ((l >> 32) & 0xff);
+            buffer[pos++] = (byte) ((l >> 40) & 0xff);
+            buffer[pos++] = (byte) ((l >> 48) & 0xff);
+            buffer[pos++] = (byte) ((l >> 56) & 0xff);
+        } else {
+            buffer[pos++] = (byte) ((l >> 56) & 0xff);
+            buffer[pos++] = (byte) ((l >> 48) & 0xff);
+            buffer[pos++] = (byte) ((l >> 40) & 0xff);
+            buffer[pos++] = (byte) ((l >> 32) & 0xff);
+            buffer[pos++] = (byte) ((l >> 16) & 0xff);
+            buffer[pos++] = (byte) ((l >> 24) & 0xff);
+            buffer[pos++] = (byte) ((l >> 8) & 0xff);
+            buffer[pos++] = (byte) (l & 0xff);
+        }
+        return pos;
+    }
+
+    protected int putRelocatableCodeOffset(long l, byte[] buffer, int p) {
+        int pos = p;
+        /*
+         * Mark address so it is relocated relative to the start of the text segment.
+         */
+        // markRelocationSite(pos, ObjectFile.RelocationKind.DIRECT_8, DwarfSectionName.TEXT_SECTION.value(), l);
+        pos = writeLong(l, buffer, pos);
+        return pos;
+    }
+
+    protected int putRelocatableDwarfSectionOffset(int offset, byte[] buffer, String referencedSectionName, int p) {
+        int pos = p;
+        /*
+         * Mark address so it is relocated relative to the start of the desired section.
+         */
+        // markRelocationSite(pos, ObjectFile.RelocationKind.DIRECT_4, referencedSectionName, offset);
+        pos = writeInt(offset, buffer, pos);
+        return pos;
+    }
+
+    protected int putULEB(long val, byte[] buffer, int p) {
+        int pos = p;
+        long l = val;
+        for (int i = 0; i < 9; i++) {
+            byte b = (byte) (l & 0x7f);
+            l = l >>> 7;
+            boolean done = (l == 0);
+            if (!done) {
+                b = (byte) (b | 0x80);
+            }
+            pos = writeByte(b, buffer, pos);
+            if (done) {
+                break;
+            }
+        }
+        return pos;
+    }
+
+    protected int putSLEB(long val, byte[] buffer, int p) {
+        int pos = p;
+        long l = val;
+        for (int i = 0; i < 9; i++) {
+            byte b = (byte) (l & 0x7f);
+            l = l >> 7;
+            boolean bIsSigned = (b & 0x40) != 0;
+            boolean done = ((bIsSigned && l == -1) || (!bIsSigned && l == 0));
+            if (!done) {
+                b = (byte) (b | 0x80);
+            }
+            pos = writeByte(b, buffer, pos);
+            if (done) {
+                break;
+            }
+        }
+        return pos;
+    }
+
+    protected static int countUTF8Bytes(String s) {
+        return countUTF8Bytes(s, 0);
+    }
+
+    protected static int countUTF8Bytes(String s, int startChar) {
+        byte[] bytes = s.substring(startChar).getBytes(StandardCharsets.UTF_8);
+        return bytes.length;
+    }
+
+    protected int putUTF8StringBytes(String s, int startChar, byte[] buffer, int p) {
+        int pos = p;
+        byte[] bytes = s.substring(startChar).getBytes(StandardCharsets.UTF_8);
+        System.arraycopy(bytes, 0, buffer, pos, bytes.length);
+        pos += bytes.length;
+        buffer[pos++] = '\0';
+        return pos;
+    }
+
+    /*
+     * Common write methods that check for a null buffer.
+     */
+
+    protected int writeByte(byte b, byte[] buffer, int p) {
+        if (buffer != null) {
+            return putByte(b, buffer, p);
+        } else {
+            return p + 1;
+        }
+    }
+
+    protected int writeShort(short s, byte[] buffer, int p) {
+        if (buffer != null) {
+            return putShort(s, buffer, p);
+        } else {
+            return p + 2;
+        }
+    }
+
+    protected int writeInt(int i, byte[] buffer, int p) {
+        if (buffer != null) {
+            return putInt(i, buffer, p);
+        } else {
+            return p + 4;
+        }
+    }
+
+    protected int writeLong(long l, byte[] buffer, int p) {
+        if (buffer != null) {
+            return putLong(l, buffer, p);
+        } else {
+            return p + 8;
+        }
+    }
+
+    protected int writeRelocatableCodeOffset(long l, byte[] buffer, int p) {
+        if (buffer != null) {
+            return putRelocatableCodeOffset(l, buffer, p);
+        } else {
+            return p + 8;
+        }
+    }
+
+    protected int writeULEB(long val, byte[] buffer, int p) {
+        if (buffer != null) {
+            // write to the buffer at the supplied position
+            return putULEB(val, buffer, p);
+        } else {
+            // write to a scratch buffer at position 0 then offset from initial pos
+            return p + putULEB(val, scratch, 0);
+        }
+    }
+
+    protected int writeSLEB(long val, byte[] buffer, int p) {
+        if (buffer != null) {
+            // write to the buffer at the supplied position
+            return putSLEB(val, buffer, p);
+        } else {
+            // write to a scratch buffer at position 0 then offset from initial pos
+            return p + putSLEB(val, scratch, 0);
+        }
+    }
+
+    protected int writeUTF8StringBytes(String s, byte[] buffer, int pos) {
+        return writeUTF8StringBytes(s, 0, buffer, pos);
+    }
+
+    protected int writeUTF8StringBytes(String s, int startChar, byte[] buffer, int p) {
+        if (buffer != null) {
+            return putUTF8StringBytes(s, startChar, buffer, p);
+        } else {
+            return s.substring(startChar).getBytes(StandardCharsets.UTF_8).length;
+        }
+    }
+
+    protected int writeExprOpcode(DwarfExpressionOpcode opcode, byte[] buffer, int p) {
+        return writeByte(opcode.value(), buffer, p);
+    }
+
+    protected int writeExprOpcodeLiteral(int offset, byte[] buffer, int p) {
+        byte value = DwarfExpressionOpcode.DW_OP_lit0.value();
+        assert offset >= 0 && offset < 0x20;
+        value = (byte) (value + offset);
+        return writeByte(value, buffer, p);
+    }
+
+    protected int writeExprOpcodeReg(byte reg, byte[] buffer, int p) {
+        byte value = DwarfExpressionOpcode.DW_OP_reg0.value();
+        assert reg >= 0 && reg < 0x20;
+        value += reg;
+        return writeByte(value, buffer, p);
+    }
+
+    protected int writeExprOpcodeBReg(byte reg, byte[] buffer, int p) {
+        byte value = DwarfExpressionOpcode.DW_OP_breg0.value();
+        assert reg >= 0 && reg < 0x20;
+        value += reg;
+        return writeByte(value, buffer, p);
+    }
+
+    /*
+     * Common write methods that rely on called methods to handle a null buffer
+     */
+
+    protected void patchLength(int lengthPos, byte[] buffer, int pos) {
+        int length = pos - (lengthPos + 4);
+        writeInt(length, buffer, lengthPos);
+    }
+
+    protected int writeAbbrevCode(AbbrevCode code, byte[] buffer, int pos) {
+        return writeSLEB(code.ordinal(), buffer, pos);
+    }
+
+    protected int writeTag(DwarfTag dwarfTag, byte[] buffer, int pos) {
+        int code = dwarfTag.value();
+        if (code == 0) {
+            return writeByte((byte) 0, buffer, pos);
+        } else {
+            return writeSLEB(code, buffer, pos);
+        }
+    }
+
+    protected int writeDwarfVersion(DwarfVersion dwarfVersion, byte[] buffer, int pos) {
+        return writeShort(dwarfVersion.value(), buffer, pos);
+    }
+
+    protected int writeFlag(DwarfFlag flag, byte[] buffer, int pos) {
+        return writeByte(flag.value(), buffer, pos);
+    }
+
+    protected int writeAttrAddress(long address, byte[] buffer, int pos) {
+        return writeRelocatableCodeOffset(address, buffer, pos);
+    }
+
+    @SuppressWarnings("unused")
+    protected int writeAttrData8(long value, byte[] buffer, int pos) {
+        return writeLong(value, buffer, pos);
+    }
+
+    protected int writeAttrData4(int value, byte[] buffer, int pos) {
+        return writeInt(value, buffer, pos);
+    }
+
+    protected int writeAttrData2(short value, byte[] buffer, int pos) {
+        return writeShort(value, buffer, pos);
+    }
+
+    protected int writeAttrData1(byte value, byte[] buffer, int pos) {
+        return writeByte(value, buffer, pos);
+    }
+
+    protected int writeInfoSectionOffset(int offset, byte[] buffer, int pos) {
+        return writeDwarfSectionOffset(offset, buffer, DwarfSectionName.DW_INFO_SECTION, pos);
+    }
+
+    protected int writeLineSectionOffset(int offset, byte[] buffer, int pos) {
+        return writeDwarfSectionOffset(offset, buffer, DwarfSectionName.DW_LINE_SECTION, pos);
+    }
+
+    protected int writeRangesSectionOffset(int offset, byte[] buffer, int pos) {
+        return writeDwarfSectionOffset(offset, buffer, DwarfSectionName.DW_RANGES_SECTION, pos);
+    }
+
+    protected int writeAbbrevSectionOffset(int offset, byte[] buffer, int pos) {
+        return writeDwarfSectionOffset(offset, buffer, DwarfSectionName.DW_ABBREV_SECTION, pos);
+    }
+
+    protected int writeStrSectionOffset(String value, byte[] buffer, int p) {
+        int pos = p;
+        int idx = debugStringIndex(value);
+        return writeStrSectionOffset(idx, buffer, pos);
+    }
+
+    private int writeStrSectionOffset(int offset, byte[] buffer, int pos) {
+        return writeDwarfSectionOffset(offset, buffer, DwarfSectionName.DW_STR_SECTION, pos);
+    }
+
+    protected int writeLocSectionOffset(int offset, byte[] buffer, int pos) {
+        return writeDwarfSectionOffset(offset, buffer, DwarfSectionName.DW_LOC_SECTION, pos);
+    }
+
+    protected int writeDwarfSectionOffset(int offset, byte[] buffer, DwarfSectionName referencedSectionName, int pos) {
+        // offsets to abbrev section DIEs need a relocation
+        // the linker uses this to update the offset when info sections are merged
+        if (buffer != null) {
+            return putRelocatableDwarfSectionOffset(offset, buffer, referencedSectionName.value(), pos);
+        } else {
+            return pos + 4;
+        }
+    }
+
+    protected int writeAttrNull(byte[] buffer, int pos) {
+        // A null attribute is just a zero tag.
+        return writeTag(DwarfTag.DW_TAG_null, buffer, pos);
+    }
+
+    protected static String formatValue(DebugLocalValueInfo value) {
+        switch (value.localKind()) {
+            case REGISTER:
+                return "REG:" + value.regIndex();
+            case STACKSLOT:
+                return "STACK:" + value.stackSlot();
+            case CONSTANT:
+                return "CONST:" + value.constantValue() + "[" + Long.toHexString(value.heapOffset()) + "]";
+            case UNDEFINED:
+            default:
+                return "-";
+        }
+    }
+
+    /**
+     * Identify the section after which this debug section needs to be ordered when sizing and
+     * creating content.
+     * 
+     * @return the name of the preceding section.
+     */
+    public final String targetName() {
+        return targetSectionName.value();
+    }
+
+    /**
+     * Identify this debug section by name.
+     * 
+     * @return the name of the debug section.
+     */
+    public final String getSectionName() {
+        return sectionName.value();
+    }
+
+    @Override
+    public int getOrDecideSize(Map<ObjectFile.Element, LayoutDecisionMap> alreadyDecided, int sizeHint) {
+
+        if (targetName().startsWith(".debug")) {
+            ObjectFile.Element previousElement = this.getElement().getOwner().elementForName(targetName());
+            RuntimeDwarfSectionImpl previousSection = (RuntimeDwarfSectionImpl) previousElement.getImpl();
+            assert previousSection.contentByteArrayCreated();
+        }
+        createContent();
+
+        return getContent().length;
+    }
+
+    @Override
+    public byte[] getOrDecideContent(Map<ObjectFile.Element, LayoutDecisionMap> alreadyDecided, byte[] contentHint) {
+        assert contentByteArrayCreated();
+        /*
+         * Ensure content byte[] has been written before calling super method.
+         *
+         * we do this in a nested debug scope derived from the one set up under the object file
+         * write
+         */
+        getOwner().debugContext(debugSectionLogName(), this::writeContent);
+
+        return super.getOrDecideContent(alreadyDecided, contentHint);
+    }
+
+    @Override
+    public Set<BuildDependency> getDependencies(Map<ObjectFile.Element, LayoutDecisionMap> decisions) {
+        Set<BuildDependency> deps = super.getDependencies(decisions);
+        String targetName = targetName();
+        ELFObjectFile.ELFSection targetSection = (ELFObjectFile.ELFSection) getElement().getOwner().elementForName(targetName);
+        LayoutDecision ourContent = decisions.get(getElement()).getDecision(LayoutDecision.Kind.CONTENT);
+        LayoutDecision ourSize = decisions.get(getElement()).getDecision(LayoutDecision.Kind.SIZE);
+
+        for (LayoutDecision.Kind targetKind : targetSectionKinds) {
+            if (targetKind == LayoutDecision.Kind.SIZE) {
+                /* Make our size depend on the target size so we compute sizes in order. */
+                LayoutDecision targetDecision = decisions.get(targetSection).getDecision(targetKind);
+                deps.add(BuildDependency.createOrGet(ourSize, targetDecision));
+            } else if (targetKind == LayoutDecision.Kind.CONTENT) {
+                /* Make our content depend on the target content so we compute contents in order. */
+                LayoutDecision targetDecision = decisions.get(targetSection).getDecision(targetKind);
+                deps.add(BuildDependency.createOrGet(ourContent, targetDecision));
+            } else {
+                /* Make our size depend on the relevant target's property. */
+                LayoutDecision targetDecision = decisions.get(targetSection).getDecision(targetKind);
+                deps.add(BuildDependency.createOrGet(ourSize, targetDecision));
+            }
+        }
+        return deps;
+    }
+
+    /**
+     * A scratch buffer used during computation of a section's size.
+     */
+    protected static final byte[] scratch = new byte[10];
+
+    /**
+     * Retrieve a stream of all types notified via the DebugTypeInfo API.
+     * 
+     * @return a stream of all types notified via the DebugTypeInfo API.
+     */
+    protected Iterable<TypeEntry> types() {
+        return dwarfSections.getTypes();
+    }
+
+    /**
+     * Retrieve a stream of all primitive types notified via the DebugTypeInfo API.
+     * 
+     * @return a stream of all primitive types notified via the DebugTypeInfo API.
+     */
+    protected Iterable<PrimitiveTypeEntry> primitiveTypes() {
+        return dwarfSections.getPrimitiveTypes();
+    }
+
+    /**
+     * Retrieve a stream of all instance classes, including interfaces and enums, notified via the
+     * DebugTypeInfo API.
+     *
+     * @return a stream of all instance classes notified via the DebugTypeInfo API.
+     */
+    protected Iterable<TypedefEntry> typedefs() {
+        return dwarfSections.getTypedefs();
+    }
+
+    /**
+     * Retrieve a stream of all compiled methods notified via the DebugTypeInfo API.
+     *
+     * @return a stream of all compiled methods notified via the DebugTypeInfo API.
+     */
+    protected CompiledMethodEntry compiledMethod() {
+        return dwarfSections.getCompiledMethod();
+    }
+
+    protected Iterable<FileEntry> files() {
+        return dwarfSections.getFiles();
+    }
+
+    protected int fileCount() {
+        return dwarfSections.getFiles().size();
+    }
+
+    protected Iterable<DirEntry> dirs() {
+        return dwarfSections.getDirs();
+    }
+
+    protected int dirCount() {
+        return dwarfSections.getDirs().size();
+    }
+
+    protected int debugStringIndex(String str) {
+        if (!contentByteArrayCreated()) {
+            return 0;
+        }
+        return dwarfSections.debugStringIndex(str);
+    }
+
+    protected String uniqueDebugString(String str) {
+        return dwarfSections.uniqueDebugString(str);
+    }
+
+    protected TypeEntry lookupType(ResolvedJavaType type) {
+        return dwarfSections.lookupTypeEntry(type);
+    }
+
+    protected int getTypeIndex(TypeEntry typeEntry) {
+        if (!contentByteArrayCreated()) {
+            return -1;
+        }
+        return dwarfSections.getTypeIndex(typeEntry);
+    }
+
+    protected void setTypeIndex(TypeEntry typeEntry, int pos) {
+        dwarfSections.setTypeIndex(typeEntry, pos);
+    }
+
+    protected void setMethodDeclarationIndex(MethodEntry methodEntry, int pos) {
+        dwarfSections.setMethodDeclarationIndex(methodEntry, pos);
+    }
+
+    protected int getMethodDeclarationIndex(MethodEntry methodEntry) {
+        if (!contentByteArrayCreated()) {
+            return 0;
+        }
+        return dwarfSections.getMethodDeclarationIndex(methodEntry);
+    }
+
+    protected void setAbstractInlineMethodIndex(CompiledMethodEntry compiledEntry, MethodEntry methodEntry, int pos) {
+        dwarfSections.setAbstractInlineMethodIndex(compiledEntry, methodEntry, pos);
+    }
+
+    protected int getAbstractInlineMethodIndex(CompiledMethodEntry compiledEntry, MethodEntry methodEntry) {
+        if (!contentByteArrayCreated()) {
+            return 0;
+        }
+        return dwarfSections.getAbstractInlineMethodIndex(compiledEntry, methodEntry);
+    }
+
+    /**
+     * Record the info section offset of a local (or parameter) declaration DIE appearing as a child
+     * of a standard method declaration or an abstract inline method declaration.
+     *
+     * @param methodEntry the method being declared or inlined.
+     * @param localInfo the local or param whose index is to be recorded.
+     * @param index the info section offset to be recorded.
+     */
+    protected void setMethodLocalIndex(CompiledMethodEntry compiledEntry, MethodEntry methodEntry, DebugLocalInfo localInfo, int index) {
+        dwarfSections.setMethodLocalIndex(compiledEntry, methodEntry, localInfo, index);
+    }
+
+    /**
+     * Retrieve the info section offset of a local (or parameter) declaration DIE appearing as a
+     * child of a standard method declaration or an abstract inline method declaration.
+     *
+     * @param methodEntry the method being declared or imported
+     * @param localInfo the local or param whose index is to be retrieved.
+     * @return the associated info section offset.
+     */
+    protected int getMethodLocalIndex(CompiledMethodEntry compiledEntry, MethodEntry methodEntry, DebugLocalInfo localInfo) {
+        if (!contentByteArrayCreated()) {
+            return 0;
+        }
+        return dwarfSections.getMethodLocalIndex(compiledEntry, methodEntry, localInfo);
+    }
+
+    /**
+     * Record the info section offset of a local (or parameter) location DIE associated with a top
+     * level (primary) or inline method range.
+     * 
+     * @param range the top level (primary) or inline range to which the local (or parameter)
+     *            belongs.
+     * @param localInfo the local or param whose index is to be recorded.
+     * @param index the info section offset to be recorded.
+     */
+    protected void setRangeLocalIndex(Range range, DebugLocalInfo localInfo, int index) {
+        dwarfSections.setRangeLocalIndex(range, localInfo, index);
+    }
+
+    /**
+     * Retrieve the info section offset of a local (or parameter) location DIE associated with a top
+     * level (primary) or inline method range.
+     * 
+     * @param range the top level (primary) or inline range to which the local (or parameter)
+     *            belongs.
+     * @param localInfo the local or param whose index is to be retrieved.
+     * @return the associated info section offset.
+     */
+    protected int getRangeLocalIndex(Range range, DebugLocalInfo localInfo) {
+        if (!contentByteArrayCreated()) {
+            return 0;
+        }
+        return dwarfSections.getRangeLocalIndex(range, localInfo);
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfStrSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfStrSectionImpl.java
new file mode 100644
index 000000000000..b64043997f67
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfStrSectionImpl.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.dwarf;
+
+import com.oracle.objectfile.runtime.debugentry.StringEntry;
+import com.oracle.objectfile.runtime.dwarf.constants.DwarfSectionName;
+import jdk.graal.compiler.debug.DebugContext;
+
+/**
+ * Generator for debug_str section.
+ */
+public class RuntimeDwarfStrSectionImpl extends RuntimeDwarfSectionImpl {
+    public RuntimeDwarfStrSectionImpl(RuntimeDwarfDebugInfo dwarfSections) {
+        // debug_str section depends on info section
+        super(dwarfSections, DwarfSectionName.DW_STR_SECTION, DwarfSectionName.DW_INFO_SECTION);
+    }
+
+    @Override
+    public void createContent() {
+        assert !contentByteArrayCreated();
+
+        int pos = 0;
+        for (StringEntry stringEntry : dwarfSections.getStringTable()) {
+            if (stringEntry.isAddToStrSection()) {
+                stringEntry.setOffset(pos);
+                String string = stringEntry.getString();
+                pos += countUTF8Bytes(string) + 1;
+            }
+        }
+        byte[] buffer = new byte[pos];
+        super.setContent(buffer);
+    }
+
+    @Override
+    public void writeContent(DebugContext context) {
+        assert contentByteArrayCreated();
+
+        byte[] buffer = getContent();
+        int size = buffer.length;
+        int pos = 0;
+
+        enableLog(context, pos);
+
+        verboseLog(context, " [0x%08x] DEBUG_STR", pos);
+        for (StringEntry stringEntry : dwarfSections.getStringTable()) {
+            if (stringEntry.isAddToStrSection()) {
+                assert stringEntry.getOffset() == pos;
+                String string = stringEntry.getString();
+                pos = writeUTF8StringBytes(string, buffer, pos);
+                verboseLog(context, " [0x%08x] string = %s", pos, string);
+            }
+        }
+        assert pos == size;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfAccess.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfAccess.java
new file mode 100644
index 000000000000..fb983dfa3be0
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfAccess.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.dwarf.constants;
+
+/**
+ * DW_AT_Accessibility attribute values.
+ */
+public enum DwarfAccess {
+    DW_ACCESS_public((byte) 1),
+    DW_ACCESS_protected((byte) 2),
+    DW_ACCESS_private((byte) 3);
+
+    private final byte value;
+
+    DwarfAccess(byte b) {
+        value = b;
+    }
+
+    public byte value() {
+        return value;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfAttribute.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfAttribute.java
new file mode 100644
index 000000000000..b0a3a55d5601
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfAttribute.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.dwarf.constants;
+
+/**
+ * All the Dwarf attributes needed to populate DIEs generated by GraalVM.
+ */
+public enum DwarfAttribute {
+    DW_AT_null(0x0),
+    DW_AT_location(0x02),
+    DW_AT_name(0x3),
+    DW_AT_byte_size(0x0b),
+    DW_AT_bit_size(0x0d),
+    DW_AT_stmt_list(0x10),
+    DW_AT_low_pc(0x11),
+    DW_AT_hi_pc(0x12),
+    DW_AT_language(0x13),
+    DW_AT_comp_dir(0x1b),
+    DW_AT_containing_type(0x1d),
+    DW_AT_inline(0x20),
+    DW_AT_producer(0x25),
+    DW_AT_abstract_origin(0x31),
+    DW_AT_accessibility(0x32),
+    DW_AT_artificial(0x34),
+    DW_AT_count(0x37),
+    DW_AT_data_member_location(0x38),
+    @SuppressWarnings("unused")
+    DW_AT_decl_column(0x39),
+    DW_AT_decl_file(0x3a),
+    DW_AT_decl_line(0x3b),
+    DW_AT_declaration(0x3c),
+    DW_AT_encoding(0x3e),
+    DW_AT_external(0x3f),
+    @SuppressWarnings("unused")
+    DW_AT_return_addr(0x2a),
+    @SuppressWarnings("unused")
+    DW_AT_frame_base(0x40),
+    DW_AT_specification(0x47),
+    DW_AT_type(0x49),
+    DW_AT_data_location(0x50),
+    DW_AT_use_UTF8(0x53),
+    DW_AT_ranges(0x55),
+    DW_AT_call_file(0x58),
+    DW_AT_call_line(0x59),
+    DW_AT_object_pointer(0x64),
+    DW_AT_linkage_name(0x6e);
+
+    private final int value;
+
+    DwarfAttribute(int v) {
+        value = v;
+    }
+
+    public int value() {
+        return value;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfEncoding.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfEncoding.java
new file mode 100644
index 000000000000..2a77d43545b2
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfEncoding.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.dwarf.constants;
+
+/**
+ * DW_AT_encoding attribute values.
+ */
+public enum DwarfEncoding {
+    @SuppressWarnings("unused")
+    DW_ATE_address((byte) 0x1),
+    DW_ATE_boolean((byte) 0x2),
+    DW_ATE_float((byte) 0x4),
+    DW_ATE_signed((byte) 0x5),
+    DW_ATE_signed_char((byte) 0x6),
+    DW_ATE_unsigned((byte) 0x7),
+    @SuppressWarnings("unused")
+    DW_ATE_unsigned_char((byte) 0x8);
+
+    private final byte value;
+
+    DwarfEncoding(byte b) {
+        value = b;
+    }
+
+    public byte value() {
+        return value;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfExpressionOpcode.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfExpressionOpcode.java
new file mode 100644
index 000000000000..37c3a7f5a05e
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfExpressionOpcode.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.dwarf.constants;
+
+/**
+ * Values used to build DWARF expressions and locations.
+ */
+public enum DwarfExpressionOpcode {
+    DW_OP_addr((byte) 0x03),
+    @SuppressWarnings("unused")
+    DW_OP_deref((byte) 0x06),
+    DW_OP_dup((byte) 0x12),
+    DW_OP_and((byte) 0x1a),
+    DW_OP_not((byte) 0x20),
+    DW_OP_plus((byte) 0x22),
+    DW_OP_shl((byte) 0x24),
+    DW_OP_shr((byte) 0x25),
+    DW_OP_bra((byte) 0x28),
+    DW_OP_eq((byte) 0x29),
+    DW_OP_lit0((byte) 0x30),
+    DW_OP_reg0((byte) 0x50),
+    DW_OP_breg0((byte) 0x70),
+    DW_OP_regx((byte) 0x90),
+    DW_OP_bregx((byte) 0x92),
+    DW_OP_push_object_address((byte) 0x97),
+    DW_OP_implicit_value((byte) 0x9e),
+    DW_OP_stack_value((byte) 0x9f);
+
+    private final byte value;
+
+    DwarfExpressionOpcode(byte b) {
+        value = b;
+    }
+
+    public byte value() {
+        return value;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfFlag.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfFlag.java
new file mode 100644
index 000000000000..2331185bf3a0
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfFlag.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.dwarf.constants;
+
+/**
+ * DW_FORM_flag only has two possible attribute values.
+ */
+public enum DwarfFlag {
+    @SuppressWarnings("unused")
+    DW_FLAG_false((byte) 0),
+    DW_FLAG_true((byte) 1);
+
+    private final byte value;
+
+    DwarfFlag(byte b) {
+        value = b;
+    }
+
+    public byte value() {
+        return value;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfForm.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfForm.java
new file mode 100644
index 000000000000..e272dc6c970c
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfForm.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.dwarf.constants;
+
+/**
+ * All the Dwarf attribute forms needed to type attribute values generated by GraalVM.
+ */
+public enum DwarfForm {
+    DW_FORM_null(0x0),
+    DW_FORM_addr(0x1),
+    DW_FORM_data2(0x05),
+    DW_FORM_data4(0x6),
+    @SuppressWarnings("unused")
+    DW_FORM_data8(0x7),
+    @SuppressWarnings("unused")
+    DW_FORM_string(0x8),
+    @SuppressWarnings("unused")
+    DW_FORM_block1(0x0a),
+    DW_FORM_ref_addr(0x10),
+    @SuppressWarnings("unused")
+    DW_FORM_ref1(0x11),
+    @SuppressWarnings("unused")
+    DW_FORM_ref2(0x12),
+    DW_FORM_ref4(0x13),
+    @SuppressWarnings("unused")
+    DW_FORM_ref8(0x14),
+    DW_FORM_sec_offset(0x17),
+    DW_FORM_data1(0x0b),
+    DW_FORM_flag(0xc),
+    DW_FORM_strp(0xe),
+    DW_FORM_expr_loc(0x18);
+
+    private final int value;
+
+    DwarfForm(int i) {
+        value = i;
+    }
+
+    public int value() {
+        return value;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfFrameValue.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfFrameValue.java
new file mode 100644
index 000000000000..4ea3ca2146f4
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfFrameValue.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.dwarf.constants;
+
+/**
+ * Constants that appear in CIE and FDE frame section entries.
+ */
+public enum DwarfFrameValue {
+    DW_CFA_CIE_version((byte) 1),
+    /* Values encoded in high 2 bits. */
+    DW_CFA_advance_loc((byte) 0x1),
+    DW_CFA_offset((byte) 0x2),
+    DW_CFA_restore((byte) 0x3),
+    /* Values encoded in low 6 bits. */
+    DW_CFA_nop((byte) 0x0),
+    @SuppressWarnings("unused")
+    DW_CFA_set_loc1((byte) 0x1),
+    DW_CFA_advance_loc1((byte) 0x2),
+    DW_CFA_advance_loc2((byte) 0x3),
+    DW_CFA_advance_loc4((byte) 0x4),
+    @SuppressWarnings("unused")
+    DW_CFA_offset_extended((byte) 0x5),
+    @SuppressWarnings("unused")
+    DW_CFA_restore_extended((byte) 0x6),
+    @SuppressWarnings("unused")
+    DW_CFA_undefined((byte) 0x7),
+    @SuppressWarnings("unused")
+    DW_CFA_same_value((byte) 0x8),
+    DW_CFA_register((byte) 0x9),
+    DW_CFA_def_cfa((byte) 0xc),
+    @SuppressWarnings("unused")
+    DW_CFA_def_cfa_register((byte) 0xd),
+    DW_CFA_def_cfa_offset((byte) 0xe);
+
+    private final byte value;
+
+    DwarfFrameValue(byte b) {
+        value = b;
+    }
+
+    public byte value() {
+        return value;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfHasChildren.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfHasChildren.java
new file mode 100644
index 000000000000..10b4584d06d7
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfHasChildren.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.dwarf.constants;
+
+/*
+ * Compile unit DIE header has_children attribute values.
+ */
+public enum DwarfHasChildren {
+    DW_CHILDREN_no((byte) 0),
+    DW_CHILDREN_yes((byte) 1);
+
+    private final byte value;
+
+    DwarfHasChildren(byte b) {
+        value = b;
+    }
+
+    public byte value() {
+        return value;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfInline.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfInline.java
new file mode 100644
index 000000000000..ae2b73bdf62a
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfInline.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.dwarf.constants;
+
+/*
+ * Values for DW_AT_inline attribute.
+ */
+public enum DwarfInline {
+    @SuppressWarnings("unused")
+    DW_INL_not_inlined((byte) 0),
+    DW_INL_inlined((byte) 1),
+    @SuppressWarnings("unused")
+    DW_INL_declared_not_inlined((byte) 2),
+    @SuppressWarnings("unused")
+    DW_INL_declared_inlined((byte) 3);
+
+    private final byte value;
+
+    DwarfInline(byte b) {
+        value = b;
+    }
+
+    public byte value() {
+        return value;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfLanguage.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfLanguage.java
new file mode 100644
index 000000000000..b0259904cb96
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfLanguage.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.dwarf.constants;
+
+/*
+ * DW_AT_language attribute has a range of pre-defined values but we
+ * are only interested in Java.
+ */
+public enum DwarfLanguage {
+    DW_LANG_Java((byte) 0xb);
+
+    private final byte value;
+
+    DwarfLanguage(byte b) {
+        value = b;
+    }
+
+    public byte value() {
+        return value;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfLineOpcode.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfLineOpcode.java
new file mode 100644
index 000000000000..d112261254fd
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfLineOpcode.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.dwarf.constants;
+
+/**
+ * Standard line section opcodes defined by Dwarf 4.
+ */
+public enum DwarfLineOpcode {
+    /*
+     * 0 can be inserted as a prefix for extended opcodes.
+     */
+    DW_LNS_extended_prefix((byte) 0),
+    /*
+     * Append current state as matrix row 0 args.
+     */
+    DW_LNS_copy((byte) 1),
+    /*
+     * Increment address 1 uleb arg.
+     */
+    DW_LNS_advance_pc((byte) 2),
+    /*
+     * Increment line 1 sleb arg.
+     */
+    DW_LNS_advance_line((byte) 3),
+    /*
+     * Set file 1 uleb arg.
+     */
+    DW_LNS_set_file((byte) 4),
+    /*
+     * sSet column 1 uleb arg.
+     */
+    DW_LNS_set_column((byte) 5),
+    /*
+     * Flip is_stmt 0 args.
+     */
+    DW_LNS_negate_stmt((byte) 6),
+    /*
+     * Set end sequence and copy row 0 args.
+     */
+    DW_LNS_set_basic_block((byte) 7),
+    /*
+     * Increment address as per opcode 255 0 args.
+     */
+    DW_LNS_const_add_pc((byte) 8),
+    /*
+     * Increment address 1 ushort arg.
+     */
+    DW_LNS_fixed_advance_pc((byte) 9),
+    /*
+     * Increment address 1 ushort arg.
+     */
+    @SuppressWarnings("unused")
+    DW_LNS_set_prologue_end((byte) 10),
+    /*
+     * Increment address 1 ushort arg.
+     */
+    @SuppressWarnings("unused")
+    DW_LNS_set_epilogue_begin((byte) 11),
+    /*
+     * Extended line section opcodes defined by DWARF 2.
+     */
+    /*
+     * There is no extended opcode 0.
+     */
+    @SuppressWarnings("unused")
+    DW_LNE_undefined((byte) 0),
+    /*
+     * End sequence of addresses.
+     */
+    DW_LNE_end_sequence((byte) 1),
+    /*
+     * Set address as explicit long argument.
+     */
+    DW_LNE_set_address((byte) 2),
+    /*
+     * Set file as explicit string argument.
+     */
+    DW_LNE_define_file((byte) 3);
+
+    private final byte value;
+
+    DwarfLineOpcode(byte b) {
+        value = b;
+    }
+
+    public byte value() {
+        return value;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfSectionName.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfSectionName.java
new file mode 100644
index 000000000000..a2b21e7c9d52
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfSectionName.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.dwarf.constants;
+
+/**
+ * Various ELF sections created by GraalVM including all debug info sections. The enum sequence
+ * starts with the text section (not defined in the DWARF spec and not created by debug info code).
+ */
+public enum DwarfSectionName {
+    TEXT_SECTION(".text"),
+    DW_STR_SECTION(".debug_str"),
+    DW_LINE_SECTION(".debug_line"),
+    DW_FRAME_SECTION(".debug_frame"),
+    DW_ABBREV_SECTION(".debug_abbrev"),
+    DW_INFO_SECTION(".debug_info"),
+    DW_LOC_SECTION(".debug_loc"),
+    DW_ARANGES_SECTION(".debug_aranges"),
+    DW_RANGES_SECTION(".debug_ranges");
+
+    private final String value;
+
+    DwarfSectionName(String s) {
+        value = s;
+    }
+
+    public String value() {
+        return value;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfTag.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfTag.java
new file mode 100644
index 000000000000..2d3ded731b57
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfTag.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.dwarf.constants;
+
+/**
+ * All the Dwarf tags needed to type DIEs generated by GraalVM.
+ */
+public enum DwarfTag {
+    DW_TAG_null(0),
+    DW_TAG_array_type(0x01),
+    DW_TAG_class_type(0x02),
+    DW_TAG_formal_parameter(0x05),
+    DW_TAG_member(0x0d),
+    DW_TAG_pointer_type(0x0f),
+    DW_TAG_compile_unit(0x11),
+    DW_TAG_structure_type(0x13),
+    DW_TAG_typedef(0x16),
+    DW_TAG_union_type(0x17),
+    DW_TAG_inheritance(0x1c),
+    DW_TAG_subrange_type(0x21),
+    DW_TAG_base_type(0x24),
+    DW_TAG_constant(0x27),
+    DW_TAG_subprogram(0x2e),
+    DW_TAG_variable(0x34),
+    DW_TAG_namespace(0x39),
+    DW_TAG_unspecified_type(0x3b),
+    DW_TAG_inlined_subroutine(0x1d);
+
+    private final int value;
+
+    DwarfTag(int i) {
+        value = i;
+    }
+
+    public int value() {
+        return value;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfVersion.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfVersion.java
new file mode 100644
index 000000000000..f49c4c1f50c2
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfVersion.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.runtime.dwarf.constants;
+
+/**
+ * The DWARF version values used by GraalVM.
+ */
+public enum DwarfVersion {
+
+    /**
+     * Currently generated debug info relies on DWARF spec version 4. However, some sections may
+     * still need to be generated as version 2.
+     */
+    DW_VERSION_2((short) 2),
+    DW_VERSION_4((short) 4);
+
+    private final short value;
+
+    DwarfVersion(short s) {
+        value = s;
+    }
+
+    public short value() {
+        return value;
+    }
+
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java
index 1eab692d7cfe..bb28247ef133 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java
@@ -76,6 +76,7 @@
 import jdk.graal.compiler.options.OptionValues;
 import jdk.graal.compiler.phases.common.DeadCodeEliminationPhase;
 import jdk.internal.misc.Unsafe;
+import org.graalvm.nativeimage.ProcessProperties;
 
 public class SubstrateOptions {
 
@@ -475,6 +476,21 @@ public static void setImageLayerCreateEnabledHandler(OptionEnabledHandler<Boolea
     @Option(help = "Track NodeSourcePositions during runtime-compilation")//
     public static final HostedOptionKey<Boolean> IncludeNodeSourcePositions = new HostedOptionKey<>(false);
 
+    @Option(help = "Directory where Java source-files will be placed for the debugger")//
+    public static final RuntimeOptionKey<String> RuntimeSourceDestDir = new RuntimeOptionKey<>(null, RelevantForCompilationIsolates);
+
+
+    public static Path getRuntimeSourceDestDir() {
+        String sourceDestDir = RuntimeSourceDestDir.getValue();
+        if (sourceDestDir != null) {
+            return Paths.get(sourceDestDir);
+        }
+        return Paths.get(ProcessProperties.getExecutableName()).getParent().resolve("sources");
+    }
+
+    @Option(help = "Provide debuginfo for runtime-compiled code.")//
+    public static final HostedOptionKey<Boolean> RuntimeDebugInfo = new HostedOptionKey<>(true);  // TODO: change to false
+
     @Option(help = "Search path for C libraries passed to the linker (list of comma-separated directories)", stability = OptionStability.STABLE)//
     @BundleMember(role = BundleMember.Role.Input)//
     public static final HostedOptionKey<AccumulatingLocatableMultiOptionValue.Paths> CLibraryPath = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Paths.buildWithCommaDelimiter());
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoFeature.java
new file mode 100644
index 000000000000..caff4a263d4e
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoFeature.java
@@ -0,0 +1,33 @@
+package com.oracle.svm.core.debug;
+
+import com.oracle.svm.core.SubstrateOptions;
+import com.oracle.svm.core.code.InstalledCodeObserverSupport;
+import com.oracle.svm.core.code.InstalledCodeObserverSupportFeature;
+import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
+import com.oracle.svm.core.feature.InternalFeature;
+import com.oracle.svm.core.graal.meta.RuntimeConfiguration;
+import org.graalvm.nativeimage.ImageSingletons;
+import org.graalvm.nativeimage.Platform;
+import org.graalvm.nativeimage.hosted.Feature;
+
+import java.util.List;
+
+@AutomaticallyRegisteredFeature
+public class SubstrateDebugInfoFeature implements InternalFeature {
+    @Override
+    public boolean isInConfiguration(IsInConfigurationAccess access) {
+        return Platform.includedIn(Platform.LINUX.class) && SubstrateOptions.useDebugInfoGeneration() && SubstrateOptions.RuntimeDebugInfo.getValue();
+    }
+
+    @Override
+    public List<Class<? extends Feature>> getRequiredFeatures() {
+        return List.of(InstalledCodeObserverSupportFeature.class);
+    }
+
+    @Override
+    public void registerCodeObserver(RuntimeConfiguration runtimeConfig) {
+        // This is called at image build-time -> the factory then creates a RuntimeDebugInfoProvider at runtime
+        ImageSingletons.lookup(InstalledCodeObserverSupport.class).addObserverFactory(new SubstrateDebugInfoInstaller.Factory(runtimeConfig.getProviders().getMetaAccess(), runtimeConfig));
+        ImageSingletons.add(SubstrateDebugInfoInstaller.Accessor.class, new SubstrateDebugInfoInstaller.Accessor());
+    }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
new file mode 100644
index 000000000000..228b5549ab8d
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
@@ -0,0 +1,164 @@
+package com.oracle.svm.core.debug;
+
+import com.oracle.svm.core.Uninterruptible;
+import com.oracle.svm.core.c.NonmovableArray;
+import com.oracle.svm.core.c.NonmovableArrays;
+import com.oracle.svm.core.code.InstalledCodeObserver;
+import com.oracle.svm.core.graal.meta.RuntimeConfiguration;
+import com.oracle.svm.core.meta.SharedMethod;
+import com.oracle.svm.core.thread.VMOperation;
+import com.oracle.svm.core.util.VMError;
+import jdk.graal.compiler.code.CompilationResult;
+import jdk.graal.compiler.debug.DebugContext;
+import jdk.vm.ci.meta.MetaAccessProvider;
+import org.graalvm.nativeimage.ImageSingletons;
+import org.graalvm.nativeimage.UnmanagedMemory;
+import org.graalvm.nativeimage.c.struct.RawField;
+import org.graalvm.nativeimage.c.struct.RawStructure;
+import org.graalvm.nativeimage.c.struct.SizeOf;
+import org.graalvm.nativeimage.c.type.CCharPointer;
+import org.graalvm.nativeimage.impl.UnmanagedMemorySupport;
+import org.graalvm.word.Pointer;
+import org.graalvm.word.WordFactory;
+
+public class SubstrateDebugInfoInstaller implements InstalledCodeObserver {
+
+    private final SubstrateDebugInfoProvider substrateDebugInfoProvider;
+
+    static final class Factory implements InstalledCodeObserver.Factory {
+
+        private final MetaAccessProvider metaAccess;
+        private final RuntimeConfiguration runtimeConfiguration;
+
+        Factory(MetaAccessProvider metaAccess, RuntimeConfiguration runtimeConfiguration) {
+            this.metaAccess = metaAccess;
+            this.runtimeConfiguration = runtimeConfiguration;
+        }
+
+        @Override
+        public InstalledCodeObserver create(DebugContext debugContext, SharedMethod method, CompilationResult compilation, Pointer code, int codeSize) {
+            try {
+                return new SubstrateDebugInfoInstaller(debugContext, method, compilation, metaAccess, runtimeConfiguration, code, codeSize);
+            } catch (Throwable t) {
+                throw VMError.shouldNotReachHere(t);
+            }
+        }
+    }
+
+    private SubstrateDebugInfoInstaller(DebugContext debugContext, SharedMethod method, CompilationResult compilation, MetaAccessProvider metaAccess, RuntimeConfiguration runtimeConfiguration, Pointer code, int codeSize) {
+        substrateDebugInfoProvider = new SubstrateDebugInfoProvider(debugContext, method, compilation, runtimeConfiguration, code.rawValue(), codeSize);
+    }
+
+    @RawStructure
+    private interface Handle extends InstalledCodeObserverHandle {
+        int INITIALIZED = 0;
+        int ACTIVATED = 1;
+        int RELEASED = 2;
+
+        @RawField
+        GDBJITInterface.JITCodeEntry getRawHandle();
+
+        @RawField
+        void setRawHandle(GDBJITInterface.JITCodeEntry value);
+
+        @RawField
+        NonmovableArray<Byte> getDebugInfoData();
+
+        @RawField
+        void setDebugInfoData(NonmovableArray<Byte> data);
+
+        @RawField
+        int getState();
+
+        @RawField
+        void setState(int value);
+    }
+
+    static final class Accessor implements InstalledCodeObserverHandleAccessor {
+
+        static Handle createHandle(NonmovableArray<Byte> debugInfoData) {
+            Handle handle = UnmanagedMemory.malloc(SizeOf.get(Handle.class));
+            handle.setAccessor(ImageSingletons.lookup(Accessor.class));
+            handle.setRawHandle(WordFactory.nullPointer());
+            handle.setDebugInfoData(debugInfoData);
+            handle.setState(Handle.INITIALIZED);
+            return handle;
+        }
+
+        @Override
+        public void activate(InstalledCodeObserverHandle installedCodeObserverHandle) {
+            Handle handle = (Handle) installedCodeObserverHandle;
+            VMOperation.guaranteeInProgress("SubstrateDebugInfoInstaller.Accessor.activate must run in a VMOperation");
+            VMError.guarantee(handle.getState() == Handle.INITIALIZED);
+
+            NonmovableArray<Byte> debugInfoData = handle.getDebugInfoData();
+            CCharPointer address = NonmovableArrays.addressOf(debugInfoData, 0);
+            int size = NonmovableArrays.lengthOf(debugInfoData);
+            GDBJITInterface.JITCodeEntry entry = GDBJITInterface.registerJITCode(address, size);
+            handle.setRawHandle(entry);
+
+            handle.setState(Handle.ACTIVATED);
+        }
+
+        @Override
+        public void release(InstalledCodeObserverHandle installedCodeObserverHandle) {
+            Handle handle = (Handle) installedCodeObserverHandle;
+            VMOperation.guaranteeInProgress("SubstrateDebugInfoInstaller.Accessor.release must run in a VMOperation");
+            VMError.guarantee(handle.getState() == Handle.ACTIVATED);
+
+            GDBJITInterface.JITCodeEntry entry = handle.getRawHandle();
+            GDBJITInterface.unregisterJITCode(entry);
+
+            handle.setState(Handle.RELEASED);
+            NonmovableArrays.releaseUnmanagedArray(handle.getDebugInfoData());
+            UnmanagedMemory.free(handle);
+        }
+
+        @Override
+        public void detachFromCurrentIsolate(InstalledCodeObserverHandle installedCodeObserverHandle) {
+            Handle handle = (Handle) installedCodeObserverHandle;
+            NonmovableArrays.untrackUnmanagedArray(handle.getDebugInfoData());
+        }
+
+        @Override
+        public void attachToCurrentIsolate(InstalledCodeObserverHandle installedCodeObserverHandle) {
+            Handle handle = (Handle) installedCodeObserverHandle;
+            NonmovableArrays.trackUnmanagedArray(handle.getDebugInfoData());
+        }
+
+        @Override
+        @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+        public void releaseOnTearDown(InstalledCodeObserverHandle installedCodeObserverHandle) {
+            Handle handle = (Handle) installedCodeObserverHandle;
+            if (handle.getState() == Handle.ACTIVATED) {
+                GDBJITInterface.JITCodeEntry entry = handle.getRawHandle();
+                GDBJITInterface.unregisterJITCode(entry);
+                handle.setState(Handle.RELEASED);
+            }
+            NonmovableArrays.releaseUnmanagedArray(handle.getDebugInfoData());
+            // UnmanagedMemory.free(handle); -> change because of Uninterruptible annotation
+            ImageSingletons.lookup(UnmanagedMemorySupport.class).free(handle);
+        }
+
+        static String toString(Handle handle) {
+            StringBuilder sb = new StringBuilder("DebugInfoHandle(handle = 0x");
+            sb.append(Long.toHexString(handle.getRawHandle().rawValue()));
+            sb.append(", address = 0x");
+            sb.append(Long.toHexString(NonmovableArrays.addressOf(handle.getDebugInfoData(), 0).rawValue()));
+            sb.append(", size = ");
+            sb.append(NonmovableArrays.lengthOf(handle.getDebugInfoData()));
+            sb.append(", handleState = ");
+            sb.append(handle.getState());
+            sb.append(")");
+            return sb.toString();
+        }
+    }
+
+    @Override
+    public InstalledCodeObserverHandle install() {
+        NonmovableArray<Byte> debugInfoData = substrateDebugInfoProvider.writeDebugInfoData();
+        Handle handle = Accessor.createHandle(debugInfoData);
+        System.out.println(Accessor.toString(handle));
+        return handle;
+    }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
new file mode 100644
index 000000000000..1af0574e51a7
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
@@ -0,0 +1,1444 @@
+package com.oracle.svm.core.debug;
+
+import com.oracle.objectfile.BasicNobitsSectionImpl;
+import com.oracle.objectfile.ObjectFile;
+import com.oracle.objectfile.SectionName;
+import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider;
+import com.oracle.svm.core.SubstrateOptions;
+import com.oracle.svm.core.c.CGlobalData;
+import com.oracle.svm.core.c.CGlobalDataFactory;
+import com.oracle.svm.core.c.NonmovableArray;
+import com.oracle.svm.core.c.NonmovableArrays;
+import com.oracle.svm.core.code.CompilationResultFrameTree;
+import com.oracle.svm.core.config.ConfigurationValues;
+import com.oracle.svm.core.graal.code.SubstrateBackend;
+import com.oracle.svm.core.graal.meta.RuntimeConfiguration;
+import com.oracle.svm.core.heap.Heap;
+import com.oracle.svm.core.heap.ObjectHeader;
+import com.oracle.svm.core.nmt.NmtCategory;
+import com.oracle.svm.core.os.VirtualMemoryProvider;
+import jdk.graal.compiler.code.CompilationResult;
+import jdk.graal.compiler.core.common.CompressEncoding;
+import jdk.graal.compiler.core.common.NumUtil;
+import jdk.graal.compiler.debug.DebugContext;
+import jdk.graal.compiler.graph.NodeSourcePosition;
+import jdk.graal.compiler.java.StableMethodNameFormatter;
+import jdk.graal.compiler.word.Word;
+import jdk.vm.ci.code.BytecodeFrame;
+import jdk.vm.ci.code.BytecodePosition;
+import jdk.vm.ci.code.RegisterValue;
+import jdk.vm.ci.code.StackSlot;
+import jdk.vm.ci.meta.JavaConstant;
+import jdk.vm.ci.meta.JavaKind;
+import jdk.vm.ci.meta.JavaValue;
+import jdk.vm.ci.meta.LineNumberTable;
+import jdk.vm.ci.meta.Local;
+import jdk.vm.ci.meta.LocalVariableTable;
+import jdk.vm.ci.meta.PrimitiveConstant;
+import jdk.vm.ci.meta.ResolvedJavaMethod;
+import jdk.vm.ci.meta.ResolvedJavaType;
+import jdk.vm.ci.meta.Signature;
+import jdk.vm.ci.meta.Value;
+import org.graalvm.nativeimage.ImageSingletons;
+
+import java.io.IOException;
+import java.lang.reflect.Modifier;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+import static com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugFrameSizeChange.Type.CONTRACT;
+import static com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugFrameSizeChange.Type.EXTEND;
+
+
+public class SubstrateDebugInfoProvider implements RuntimeDebugInfoProvider {
+
+    private final DebugContext debugContext;
+    private final ResolvedJavaMethod method;
+    private final CompilationResult compilation;
+    private final long codeAddress;
+    private final int codeSize;
+
+    private final RuntimeConfiguration runtimeConfiguration;
+    private final int pointerSize;
+    private final int tagsMask;
+    private final boolean useHeapBase;
+    private final int compressShift;
+    private final int referenceSize;
+    private final int referenceAlignment;
+
+    private final ObjectFile objectFile;
+    private final List<ObjectFile.Element> sortedObjectFileElements;
+    private final int debugInfoSize;
+
+    public static final CGlobalData<Word> TEST_DATA = CGlobalDataFactory.forSymbol("__svm_heap_begin");
+    //public static final CGlobalData<Word> TEST_DATA2 = CGlobalDataFactory.forSymbol("__svm_test_symbol");
+
+
+    public SubstrateDebugInfoProvider(DebugContext debugContext, ResolvedJavaMethod method, CompilationResult compilation, RuntimeConfiguration runtimeConfiguration, long codeAddress, int codeSize) {
+        this.debugContext = debugContext;
+        this.method = method;
+        this.compilation = compilation;
+        this.codeAddress = codeAddress;
+        this.codeSize = codeSize;
+
+        this.runtimeConfiguration = runtimeConfiguration;
+        this.pointerSize = ConfigurationValues.getTarget().wordSize;
+        ObjectHeader objectHeader = Heap.getHeap().getObjectHeader();
+        this.tagsMask = objectHeader.getReservedBitsMask();
+        CompressEncoding compressEncoding = ImageSingletons.lookup(CompressEncoding.class);
+        this.useHeapBase = compressEncoding.hasBase();
+        this.compressShift = (compressEncoding.hasShift() ? compressEncoding.getShift() : 0);
+        this.referenceSize = ConfigurationValues.getObjectLayout().getReferenceSize();
+        this.referenceAlignment = ConfigurationValues.getObjectLayout().getAlignment();
+
+        int pageSize = NumUtil.safeToInt(ImageSingletons.lookup(VirtualMemoryProvider.class).getGranularity().rawValue());
+        objectFile = ObjectFile.createRuntimeDebugInfo(pageSize);
+        objectFile.newNobitsSection(SectionName.TEXT.getFormatDependentName(objectFile.getFormat()), new BasicNobitsSectionImpl(codeSize));
+
+        objectFile.installRuntimeDebugInfo(this);
+
+        sortedObjectFileElements = new ArrayList<>();
+        debugInfoSize = objectFile.bake(sortedObjectFileElements);
+        dumpObjectFile();
+
+        //System.out.println("__svm_heap_begin: " + TEST_DATA.get().rawValue());
+        //System.out.println("__svm_test_symbol: " + TEST_DATA2.get().rawValue());
+    }
+
+    public NonmovableArray<Byte> writeDebugInfoData() {
+        NonmovableArray<Byte> array = NonmovableArrays.createByteArray(debugInfoSize, NmtCategory.Code);
+        objectFile.writeBuffer(sortedObjectFileElements, NonmovableArrays.asByteBuffer(array));
+        return array;
+    }
+
+    @Override
+    public String getCompilationUnitName() {
+        String name = (compilation != null) ? compilation.getName() : null;
+        if (name == null && method != null) {
+            name = method.format("%H.%n(%p)");
+        }
+        if (name == null || name.isEmpty()) {
+            name = "UnnamedCU";
+        }
+        name += " at 0x" + Long.toHexString(codeAddress);
+        return name;
+    }
+
+    private void dumpObjectFile() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(method.getName()).append('@').append(Long.toHexString(codeAddress)).append(".debug");
+        try (FileChannel dumpFile = FileChannel.open(Paths.get(sb.toString()),
+                StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING,
+                StandardOpenOption.CREATE)) {
+            ByteBuffer buffer = dumpFile.map(FileChannel.MapMode.READ_WRITE, 0, debugInfoSize);
+            objectFile.writeBuffer(sortedObjectFileElements, buffer);
+        } catch (IOException e) {
+            debugContext.log("Failed to dump %s", sb);
+        }
+    }
+
+    @Override
+    public boolean useHeapBase() {
+        return useHeapBase;
+    }
+
+    @Override
+    public int oopCompressShift() {
+        return compressShift;
+    }
+
+    @Override
+    public int oopTagsMask() {
+        return tagsMask;
+    }
+
+    @Override
+    public int oopReferenceSize() {
+        return referenceSize;
+    }
+
+    @Override
+    public int pointerSize() {
+        return pointerSize;
+    }
+
+    @Override
+    public int oopAlignment() {
+        return referenceAlignment;
+    }
+
+    @Override
+    public int compiledCodeMax() {
+        return 0;
+    }
+
+    @Override
+    public Iterable<DebugTypeInfo> typeInfoProvider() {
+        return List.of();
+    }
+
+    @Override
+    public DebugCodeInfo codeInfoProvider() {
+        return new SubstrateDebugCodeInfo(method, compilation);
+    }
+
+    @Override
+    public Path getCachePath() {
+        return SubstrateOptions.getRuntimeSourceDestDir();
+    }
+
+    @Override
+    public void recordActivity() {
+
+    }
+
+    private class SubstrateDebugFileInfo implements DebugFileInfo {
+        private final Path fullFilePath;
+        private final String fileName;
+
+        SubstrateDebugFileInfo(ResolvedJavaMethod method) {
+            this(method.getDeclaringClass());
+        }
+
+        SubstrateDebugFileInfo(ResolvedJavaType type) {
+            fullFilePath = fullFilePathFromClassName(type);
+            fileName = type.getSourceFileName();
+        }
+
+        private Path fullFilePathFromClassName(ResolvedJavaType type) {
+            String[] elements = type.toJavaName().split("\\.");
+            int count = elements.length;
+            String name = elements[count - 1];
+            while (name.startsWith("$")) {
+                name = name.substring(1);
+            }
+            if (name.contains("$")) {
+                name = name.substring(0, name.indexOf('$'));
+            }
+            if (name.isEmpty()) {
+                name = "_nofile_";
+            }
+            elements[count - 1] = name + ".java";
+            return FileSystems.getDefault().getPath("", elements);
+        }
+
+        @Override
+        public String fileName() {
+            return fileName;
+        }
+
+        @Override
+        public Path filePath() {
+            if (fullFilePath != null) {
+                return fullFilePath.getParent();
+            }
+            return null;
+        }
+    }
+
+
+    // actually unused currently
+    private abstract class SubstrateDebugTypeInfo extends SubstrateDebugFileInfo implements DebugTypeInfo {
+        protected final ResolvedJavaType type;
+
+        SubstrateDebugTypeInfo(ResolvedJavaType type) {
+            super(type);
+            this.type = type;
+        }
+
+        @Override
+        public ResolvedJavaType idType() {
+            return type;
+        }
+
+        @Override
+        public void debugContext(Consumer<DebugContext> action) {
+
+        }
+
+        @Override
+        public String typeName() {
+            return type.toJavaName();
+        }
+
+        @Override
+        public long classOffset() {
+            // we would need access to the heap/types on the heap
+            return -1;
+        }
+
+        @Override
+        public int size() {
+            if (type.isPrimitive()) {
+                JavaKind javaKind = type.getJavaKind();
+                return (javaKind == JavaKind.Void ? 0 : javaKind.getByteCount());
+            } else {
+                // typedef
+                return pointerSize();
+            }
+        }
+    }
+
+    private class SubstrateDebugTypedefInfo extends SubstrateDebugTypeInfo implements DebugTypedefInfo {
+        SubstrateDebugTypedefInfo(ResolvedJavaType type) {
+            super(type);
+        }
+
+        @Override
+        public DebugTypeKind typeKind() {
+            return DebugTypeKind.TYPEDEF;
+        }
+    }
+
+    private class SubstrateDebugPrimitiveTypeInfo extends SubstrateDebugTypeInfo implements DebugPrimitiveTypeInfo {
+
+        SubstrateDebugPrimitiveTypeInfo(ResolvedJavaType type) {
+            super(type);
+            assert type.isPrimitive();
+        }
+
+        @Override
+        public DebugTypeKind typeKind() {
+            return DebugTypeKind.PRIMITIVE;
+        }
+
+        @Override
+        public int bitCount() {
+            JavaKind javaKind = type.getJavaKind();
+            return (javaKind == JavaKind.Void ? 0 : javaKind.getBitCount());
+        }
+
+        @Override
+        public char typeChar() {
+            return type.getJavaKind().getTypeChar();
+        }
+
+        @Override
+        public int flags() {
+            char typeChar = typeChar();
+            return switch (typeChar) {
+                case 'B', 'S', 'I', 'J' -> FLAG_NUMERIC | FLAG_INTEGRAL | FLAG_SIGNED;
+                case 'C' -> FLAG_NUMERIC | FLAG_INTEGRAL;
+                case 'F', 'D' -> FLAG_NUMERIC;
+                default -> {
+                    assert typeChar == 'V' || typeChar == 'Z';
+                    yield 0;
+                }
+            };
+        }
+    }
+
+    private class SubstrateDebugMethodInfo extends SubstrateDebugFileInfo implements DebugMethodInfo {
+        protected final ResolvedJavaMethod method;
+        protected int line;
+        protected final List<DebugLocalInfo> paramInfo;
+        protected final DebugLocalInfo thisParamInfo;
+
+        SubstrateDebugMethodInfo(ResolvedJavaMethod method) {
+            super(method);
+            this.method = method;
+            LineNumberTable lineNumberTable = method.getLineNumberTable();
+            this.line = (lineNumberTable != null ? lineNumberTable.getLineNumber(0) : 0);
+            this.paramInfo = createParamInfo(method, line);
+
+            // We use the target modifiers to decide where to install any first param
+            // even though we may have added it according to whether method is static.
+            // That's because in a few special cases method is static but the original
+            // DebugFrameLocals
+            // from which it is derived is an instance method. This appears to happen
+            // when a C function pointer masquerades as a method. Whatever parameters
+            // we pass through need to match the definition of the original.
+            if (Modifier.isStatic(modifiers())) {
+                this.thisParamInfo = null;
+            } else {
+                this.thisParamInfo = paramInfo.removeFirst();
+            }
+        }
+
+        private List<DebugLocalInfo> createParamInfo(ResolvedJavaMethod method, int line) {
+            Signature signature = method.getSignature();
+            int parameterCount = signature.getParameterCount(false);
+            List<DebugLocalInfo> paramInfos = new ArrayList<>(parameterCount);
+            LocalVariableTable table = method.getLocalVariableTable();
+            int slot = 0;
+            ResolvedJavaType ownerType = method.getDeclaringClass();
+            if (!method.isStatic()) {
+                JavaKind kind = ownerType.getJavaKind();
+                assert kind == JavaKind.Object : "must be an object";
+                paramInfos.add(new SubstrateDebugLocalInfo("this", kind, ownerType, slot, line));
+                slot += kind.getSlotCount();
+            }
+            for (int i = 0; i < parameterCount; i++) {
+                Local local = (table == null ? null : table.getLocal(slot, 0));
+                String name = (local != null ? local.getName() : "__" + i);
+                ResolvedJavaType paramType = (ResolvedJavaType) signature.getParameterType(i, null);
+                JavaKind kind = paramType.getJavaKind();
+                paramInfos.add(new SubstrateDebugLocalInfo(name, kind, paramType, slot, line));
+                slot += kind.getSlotCount();
+            }
+            return paramInfos;
+        }
+
+        @Override
+        public String name() {
+            String name = method.getName();
+            if (name.equals("<init>")) {
+                name = method.format("%h");
+                if (name.indexOf('$') >= 0) {
+                    name = name.substring(name.lastIndexOf('$') + 1);
+                }
+            }
+            return name;
+        }
+
+        @Override
+        public ResolvedJavaType valueType() {
+            return (ResolvedJavaType) method.getSignature().getReturnType(null);
+        }
+
+        @Override
+        public int modifiers() {
+            return method.getModifiers();
+        }
+
+        @Override
+        public int line() {
+            return line;
+        }
+
+        @Override
+        public List<DebugLocalInfo> getParamInfo() {
+            return paramInfo;
+        }
+
+        @Override
+        public DebugLocalInfo getThisParamInfo() {
+            return thisParamInfo;
+        }
+
+        @Override
+        public String symbolNameForMethod() {
+            return method.getName(); //SubstrateUtil.uniqueShortName(method);
+        }
+
+        @Override
+        public boolean isDeoptTarget() {
+            return name().endsWith(StableMethodNameFormatter.MULTI_METHOD_KEY_SEPARATOR);
+        }
+
+        @Override
+        public boolean isConstructor() {
+            return method.isConstructor();
+        }
+
+        @Override
+        public boolean isVirtual() {
+            return false;
+        }
+
+        @Override
+        public int vtableOffset() {
+            // not virtual
+            return -1;
+        }
+
+        @Override
+        public boolean isOverride() {
+            return false;
+        }
+
+        @Override
+        public ResolvedJavaType ownerType() {
+            return method.getDeclaringClass();
+        }
+
+        @Override
+        public ResolvedJavaMethod idMethod() {
+            return method;
+        }
+    }
+
+    private class SubstrateDebugCodeInfo extends SubstrateDebugMethodInfo implements DebugCodeInfo {
+        private final CompilationResult compilation;
+
+        SubstrateDebugCodeInfo(ResolvedJavaMethod method, CompilationResult compilation) {
+            super(method);
+            this.compilation = compilation;
+        }
+
+        @SuppressWarnings("try")
+        @Override
+        public void debugContext(Consumer<DebugContext> action) {
+            try (DebugContext.Scope s = debugContext.scope("DebugCodeInfo", method)) {
+                action.accept(debugContext);
+            } catch (Throwable e) {
+                throw debugContext.handle(e);
+            }
+        }
+
+        @Override
+        public long addressLo() {
+            return codeAddress;
+        }
+
+        @Override
+        public long addressHi() {
+            return codeAddress + compilation.getTargetCodeSize();
+        }
+
+        @Override
+        public Iterable<DebugLocationInfo> locationInfoProvider() {
+            int maxDepth = Integer.MAX_VALUE; //SubstrateOptions.DebugCodeInfoMaxDepth.getValue();
+            boolean useSourceMappings = false; //SubstrateOptions.DebugCodeInfoUseSourceMappings.getValue();
+            final CompilationResultFrameTree.CallNode root = new CompilationResultFrameTree.Builder(debugContext, compilation.getTargetCodeSize(), maxDepth, useSourceMappings, true).build(compilation);
+            if (root == null) {
+                return List.of();
+            }
+            final List<DebugLocationInfo> locationInfos = new ArrayList<>();
+            int frameSize = getFrameSize();
+            final CompilationResultFrameTree.Visitor visitor = new MultiLevelVisitor(locationInfos, frameSize);
+            // arguments passed by visitor to apply are
+            // NativeImageDebugLocationInfo caller location info
+            // CallNode nodeToEmbed parent call node to convert to entry code leaf
+            // NativeImageDebugLocationInfo leaf into which current leaf may be merged
+            root.visitChildren(visitor, (Object) null, (Object) null, (Object) null);
+            return locationInfos;
+        }
+
+        // indices for arguments passed to SingleLevelVisitor::apply
+        protected static final int CALLER_INFO = 0;
+        protected static final int PARENT_NODE_TO_EMBED = 1;
+        protected static final int LAST_LEAF_INFO = 2;
+
+        private abstract class SingleLevelVisitor implements CompilationResultFrameTree.Visitor {
+
+            protected final List<DebugLocationInfo> locationInfos;
+            protected final int frameSize;
+
+            SingleLevelVisitor(List<DebugLocationInfo> locationInfos, int frameSize) {
+                this.locationInfos = locationInfos;
+                this.frameSize = frameSize;
+            }
+
+            public SubstrateDebugLocationInfo process(CompilationResultFrameTree.FrameNode node, SubstrateDebugLocationInfo callerInfo) {
+                SubstrateDebugLocationInfo locationInfo;
+                if (node instanceof CompilationResultFrameTree.CallNode) {
+                    // this node represents an inline call range so
+                    // add a locationinfo to cover the range of the call
+                    locationInfo = createCallLocationInfo((CompilationResultFrameTree.CallNode) node, callerInfo, frameSize);
+                } else if (isBadLeaf(node, callerInfo)) {
+                    locationInfo = createBadLeafLocationInfo(node, callerInfo, frameSize);
+                } else {
+                    // this is leaf method code so add details of its range
+                    locationInfo = createLeafLocationInfo(node, callerInfo, frameSize);
+                }
+                return locationInfo;
+            }
+        }
+
+        private class TopLevelVisitor extends SingleLevelVisitor {
+            TopLevelVisitor(List<DebugLocationInfo> locationInfos, int frameSize) {
+                super(locationInfos, frameSize);
+            }
+
+            @Override
+            public void apply(CompilationResultFrameTree.FrameNode node, Object... args) {
+                if (skipNode(node)) {
+                    // this is a bogus wrapper so skip it and transform the wrapped node instead
+                    node.visitChildren(this, args);
+                } else {
+                    SubstrateDebugLocationInfo locationInfo = process(node, null);
+                    if (node instanceof CompilationResultFrameTree.CallNode) {
+                        locationInfos.add(locationInfo);
+                        // erase last leaf (if present) since there is an intervening call range
+                        invalidateMerge(args);
+                    } else {
+                        locationInfo = tryMerge(locationInfo, args);
+                        if (locationInfo != null) {
+                            locationInfos.add(locationInfo);
+                        }
+                    }
+                }
+            }
+        }
+
+        public class MultiLevelVisitor extends SingleLevelVisitor {
+            MultiLevelVisitor(List<DebugLocationInfo> locationInfos, int frameSize) {
+                super(locationInfos, frameSize);
+            }
+
+            @Override
+            public void apply(CompilationResultFrameTree.FrameNode node, Object... args) {
+                if (skipNode(node)) {
+                    // this is a bogus wrapper so skip it and transform the wrapped node instead
+                    node.visitChildren(this, args);
+                } else {
+                    SubstrateDebugLocationInfo callerInfo = (SubstrateDebugLocationInfo) args[CALLER_INFO];
+                    CompilationResultFrameTree.CallNode nodeToEmbed = (CompilationResultFrameTree.CallNode) args[PARENT_NODE_TO_EMBED];
+                    if (nodeToEmbed != null) {
+                        if (embedWithChildren(nodeToEmbed, node)) {
+                            // embed a leaf range for the method start that was included in the
+                            // parent CallNode
+                            // its end range is determined by the start of the first node at this
+                            // level
+                            SubstrateDebugLocationInfo embeddedLocationInfo = createEmbeddedParentLocationInfo(nodeToEmbed, node, callerInfo, frameSize);
+                            locationInfos.add(embeddedLocationInfo);
+                            // since this is a leaf node we can merge later leafs into it
+                            initMerge(embeddedLocationInfo, args);
+                        }
+                        // reset args so we only embed the parent node before the first node at
+                        // this level
+                        args[PARENT_NODE_TO_EMBED] = nodeToEmbed = null;
+                    }
+                    SubstrateDebugLocationInfo locationInfo = process(node, callerInfo);
+                    if (node instanceof CompilationResultFrameTree.CallNode) {
+                        CompilationResultFrameTree.CallNode callNode = (CompilationResultFrameTree.CallNode) node;
+                        locationInfos.add(locationInfo);
+                        // erase last leaf (if present) since there is an intervening call range
+                        invalidateMerge(args);
+                        if (hasChildren(callNode)) {
+                            // a call node may include an initial leaf range for the call that must
+                            // be
+                            // embedded under the newly created location info so pass it as an
+                            // argument
+                            callNode.visitChildren(this, locationInfo, callNode, (Object) null);
+                        } else {
+                            // we need to embed a leaf node for the whole call range
+                            locationInfo = createEmbeddedParentLocationInfo(callNode, null, locationInfo, frameSize);
+                            locationInfos.add(locationInfo);
+                        }
+                    } else {
+                        locationInfo = tryMerge(locationInfo, args);
+                        if (locationInfo != null) {
+                            locationInfos.add(locationInfo);
+                        }
+                    }
+                }
+            }
+        }
+
+        /**
+         * Report whether a call node has any children.
+         *
+         * @param callNode the node to check
+         * @return true if it has any children otherwise false.
+         */
+        private boolean hasChildren(CompilationResultFrameTree.CallNode callNode) {
+            Object[] result = new Object[]{false};
+            callNode.visitChildren(new CompilationResultFrameTree.Visitor() {
+                @Override
+                public void apply(CompilationResultFrameTree.FrameNode node, Object... args) {
+                    args[0] = true;
+                }
+            }, result);
+            return (boolean) result[0];
+        }
+
+        /**
+         * Create a location info record for a leaf subrange.
+         *
+         * @param node is a simple FrameNode
+         * @return the newly created location info record
+         */
+        private SubstrateDebugLocationInfo createLeafLocationInfo(CompilationResultFrameTree.FrameNode node, SubstrateDebugLocationInfo callerInfo, int framesize) {
+            assert !(node instanceof CompilationResultFrameTree.CallNode);
+            SubstrateDebugLocationInfo locationInfo = new SubstrateDebugLocationInfo(node, callerInfo, framesize);
+            debugContext.log(DebugContext.DETAILED_LEVEL, "Create leaf Location Info : %s depth %d (%d, %d)", locationInfo.name(), locationInfo.depth(), locationInfo.addressLo(),
+                    locationInfo.addressHi() - 1);
+            return locationInfo;
+        }
+
+        /**
+         * Create a location info record for a subrange that encloses an inline call.
+         *
+         * @param callNode is the top level inlined call frame
+         * @return the newly created location info record
+         */
+        private SubstrateDebugLocationInfo createCallLocationInfo(CompilationResultFrameTree.CallNode callNode, SubstrateDebugLocationInfo callerInfo, int framesize) {
+            BytecodePosition callerPos = realCaller(callNode);
+            SubstrateDebugLocationInfo locationInfo = new SubstrateDebugLocationInfo(callerPos, callNode.getStartPos(), callNode.getEndPos() + 1, callerInfo, framesize);
+            debugContext.log(DebugContext.DETAILED_LEVEL, "Create call Location Info : %s depth %d (%d, %d)", locationInfo.name(), locationInfo.depth(), locationInfo.addressLo(),
+                    locationInfo.addressHi() - 1);
+            return locationInfo;
+        }
+
+        /**
+         * Create a location info record for the initial range associated with a parent call node
+         * whose position and start are defined by that call node and whose end is determined by the
+         * first child of the call node.
+         *
+         * @param parentToEmbed a parent call node which has already been processed to create the
+         *            caller location info
+         * @param firstChild the first child of the call node
+         * @param callerLocation the location info created to represent the range for the call
+         * @return a location info to be embedded as the first child range of the caller location.
+         */
+        private SubstrateDebugLocationInfo createEmbeddedParentLocationInfo(CompilationResultFrameTree.CallNode parentToEmbed, CompilationResultFrameTree.FrameNode firstChild, SubstrateDebugLocationInfo callerLocation, int framesize) {
+            BytecodePosition pos = parentToEmbed.frame;
+            int startPos = parentToEmbed.getStartPos();
+            int endPos = (firstChild != null ? firstChild.getStartPos() : parentToEmbed.getEndPos() + 1);
+            SubstrateDebugLocationInfo locationInfo = new SubstrateDebugLocationInfo(pos, startPos, endPos, callerLocation, framesize);
+            debugContext.log(DebugContext.DETAILED_LEVEL, "Embed leaf Location Info : %s depth %d (%d, %d)", locationInfo.name(), locationInfo.depth(), locationInfo.addressLo(),
+                    locationInfo.addressHi() - 1);
+            return locationInfo;
+        }
+
+        private SubstrateDebugLocationInfo createBadLeafLocationInfo(CompilationResultFrameTree.FrameNode node, SubstrateDebugLocationInfo callerLocation, int framesize) {
+            assert !(node instanceof CompilationResultFrameTree.CallNode) : "bad leaf location cannot be a call node!";
+            assert callerLocation == null : "should only see bad leaf at top level!";
+            BytecodePosition pos = node.frame;
+            BytecodePosition callerPos = pos.getCaller();
+            assert callerPos != null : "bad leaf must have a caller";
+            assert callerPos.getCaller() == null : "bad leaf caller must be root method";
+            int startPos = node.getStartPos();
+            int endPos = node.getEndPos() + 1;
+            SubstrateDebugLocationInfo locationInfo = new SubstrateDebugLocationInfo(callerPos, startPos, endPos, null, framesize);
+            debugContext.log(DebugContext.DETAILED_LEVEL, "Embed leaf Location Info : %s depth %d (%d, %d)", locationInfo.name(), locationInfo.depth(), locationInfo.addressLo(),
+                    locationInfo.addressHi() - 1);
+            return locationInfo;
+        }
+
+        private boolean isBadLeaf(CompilationResultFrameTree.FrameNode node, SubstrateDebugLocationInfo callerLocation) {
+            // Sometimes we see a leaf node marked as belonging to an inlined method
+            // that sits directly under the root method rather than under a call node.
+            // It needs replacing with a location info for the root method that covers
+            // the relevant code range.
+            if (callerLocation == null) {
+                BytecodePosition pos = node.frame;
+                BytecodePosition callerPos = pos.getCaller();
+                if (callerPos != null && !callerPos.getMethod().equals(pos.getMethod())) {
+                    if (callerPos.getCaller() == null) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+
+        /**
+         * Test whether a bytecode position represents a bogus frame added by the compiler when a
+         * substitution or snippet call is injected.
+         *
+         * @param pos the position to be tested
+         * @return true if the frame is bogus otherwise false
+         */
+        private boolean skipPos(BytecodePosition pos) {
+            return (pos.getBCI() == -1 && pos instanceof NodeSourcePosition && ((NodeSourcePosition) pos).isSubstitution());
+        }
+
+        /**
+         * Skip caller nodes with bogus positions, as determined by
+         * {@link #skipPos(BytecodePosition)}, returning first caller node position that is not
+         * bogus.
+         *
+         * @param node the node whose callers are to be traversed
+         * @return the first non-bogus position in the caller chain.
+         */
+        private BytecodePosition realCaller(CompilationResultFrameTree.CallNode node) {
+            BytecodePosition pos = node.frame.getCaller();
+            while (skipPos(pos)) {
+                pos = pos.getCaller();
+            }
+            return pos;
+        }
+
+        /**
+         * Test whether the position associated with a child node should result in an entry in the
+         * inline tree. The test is for a call node with a bogus position as determined by
+         * {@link #skipPos(BytecodePosition)}.
+         *
+         * @param node A node associated with a child frame in the compilation result frame tree.
+         * @return True an entry should be included or false if it should be omitted.
+         */
+        private boolean skipNode(CompilationResultFrameTree.FrameNode node) {
+            return node instanceof CompilationResultFrameTree.CallNode && skipPos(node.frame);
+        }
+
+        /**
+         * Test whether the position associated with a call node frame should be embedded along with
+         * the locations generated for the node's children. This is needed because call frames may
+         * include a valid source position that precedes the first child position.
+         *
+         * @param parent The call node whose children are currently being visited
+         * @param firstChild The first child of that call node
+         * @return true if the node should be embedded otherwise false
+         */
+        private boolean embedWithChildren(CompilationResultFrameTree.CallNode parent, CompilationResultFrameTree.FrameNode firstChild) {
+            // we only need to insert a range for the caller if it fills a gap
+            // at the start of the caller range before the first child
+            if (parent.getStartPos() < firstChild.getStartPos()) {
+                return true;
+            }
+            return false;
+        }
+
+        /**
+         * Try merging a new location info for a leaf range into the location info for the last leaf
+         * range added at this level.
+         *
+         * @param newLeaf the new leaf location info
+         * @param args the visitor argument vector used to pass parameters from one child visit to
+         *            the next possibly including the last leaf
+         * @return the new location info if it could not be merged or null to indicate that it was
+         *         merged
+         */
+        private SubstrateDebugLocationInfo tryMerge(SubstrateDebugLocationInfo newLeaf, Object[] args) {
+            // last leaf node added at this level is 3rd element of arg vector
+            SubstrateDebugLocationInfo lastLeaf = (SubstrateDebugLocationInfo) args[LAST_LEAF_INFO];
+
+            if (lastLeaf != null) {
+                // try merging new leaf into last one
+                lastLeaf = lastLeaf.merge(newLeaf);
+                if (lastLeaf != null) {
+                    // null return indicates new leaf has been merged into last leaf
+                    return null;
+                }
+            }
+            // update last leaf and return new leaf for addition to local info list
+            args[LAST_LEAF_INFO] = newLeaf;
+            return newLeaf;
+        }
+
+        /**
+         * Set the last leaf node at the current level to the supplied leaf node.
+         *
+         * @param lastLeaf the last leaf node created at this level
+         * @param args the visitor argument vector used to pass parameters from one child visit to
+         *            the next
+         */
+        private void initMerge(SubstrateDebugLocationInfo lastLeaf, Object[] args) {
+            args[LAST_LEAF_INFO] = lastLeaf;
+        }
+
+        /**
+         * Clear the last leaf node at the current level from the visitor arguments by setting the
+         * arg vector entry to null.
+         *
+         * @param args the visitor argument vector used to pass parameters from one child visit to
+         *            the next
+         */
+        private void invalidateMerge(Object[] args) {
+            args[LAST_LEAF_INFO] = null;
+        }
+
+        @Override
+        public int getFrameSize() {
+            return compilation.getTotalFrameSize();
+        }
+
+        @Override
+        public List<DebugFrameSizeChange> getFrameSizeChanges() {
+            List<DebugFrameSizeChange> frameSizeChanges = new LinkedList<>();
+            for (CompilationResult.CodeMark mark : compilation.getMarks()) {
+                /* We only need to observe stack increment or decrement points. */
+                if (mark.id.equals(SubstrateBackend.SubstrateMarkId.PROLOGUE_DECD_RSP)) {
+                    SubstrateDebugFrameSizeChange sizeChange = new SubstrateDebugFrameSizeChange(mark.pcOffset, EXTEND);
+                    frameSizeChanges.add(sizeChange);
+                    // } else if (mark.id.equals("PROLOGUE_END")) {
+                    // can ignore these
+                    // } else if (mark.id.equals("EPILOGUE_START")) {
+                    // can ignore these
+                } else if (mark.id.equals(SubstrateBackend.SubstrateMarkId.EPILOGUE_INCD_RSP)) {
+                    SubstrateDebugFrameSizeChange sizeChange = new SubstrateDebugFrameSizeChange(mark.pcOffset, CONTRACT);
+                    frameSizeChanges.add(sizeChange);
+                } else if (mark.id.equals(SubstrateBackend.SubstrateMarkId.EPILOGUE_END) && mark.pcOffset < compilation.getTargetCodeSize()) {
+                    /* There is code after this return point so notify a stack extend again. */
+                    SubstrateDebugFrameSizeChange sizeChange = new SubstrateDebugFrameSizeChange(mark.pcOffset, EXTEND);
+                    frameSizeChanges.add(sizeChange);
+                }
+            }
+            return frameSizeChanges;
+        }
+    }
+
+    private class SubstrateDebugLocationInfo extends SubstrateDebugMethodInfo implements DebugLocationInfo {
+        private final int bci;
+        private long lo;
+        private long hi;
+        private DebugLocationInfo callersLocationInfo;
+        private List<DebugLocalValueInfo> localInfoList;
+        private boolean isLeaf;
+
+        SubstrateDebugLocationInfo(CompilationResultFrameTree.FrameNode frameNode, SubstrateDebugLocationInfo callersLocationInfo, int framesize) {
+            this(frameNode.frame, frameNode.getStartPos(), frameNode.getEndPos() + 1, callersLocationInfo, framesize);
+        }
+
+        SubstrateDebugLocationInfo(BytecodePosition bcpos, long lo, long hi, SubstrateDebugLocationInfo callersLocationInfo, int framesize) {
+            super(bcpos.getMethod());
+            this.bci = bcpos.getBCI();
+            this.lo = lo;
+            this.hi = hi;
+            this.callersLocationInfo = callersLocationInfo;
+            this.localInfoList = initLocalInfoList(bcpos, framesize);
+            // assume this is a leaf until we find out otherwise
+            this.isLeaf = true;
+            // tag the caller as a non-leaf
+            if (callersLocationInfo != null) {
+                callersLocationInfo.isLeaf = false;
+            }
+        }
+
+        private List<DebugLocalValueInfo> initLocalInfoList(BytecodePosition bcpos, int framesize) {
+            if (!(bcpos instanceof BytecodeFrame)) {
+                return null;
+            }
+
+            BytecodeFrame frame = (BytecodeFrame) bcpos;
+            if (frame.numLocals == 0) {
+                return null;
+            }
+            // deal with any inconsistencies in the layout of the frame locals
+            // NativeImageDebugFrameInfo debugFrameInfo = new NativeImageDebugFrameInfo(frame);
+
+            LineNumberTable lineNumberTable = frame.getMethod().getLineNumberTable();
+            Local[] localsBySlot = getLocalsBySlot();
+            if (localsBySlot == null) {
+                return Collections.emptyList();
+            }
+            int count = Integer.min(localsBySlot.length, frame.numLocals);
+            ArrayList<DebugLocalValueInfo> localInfos = new ArrayList<>(count);
+            for (int i = 0; i < count; i++) {
+                Local l = localsBySlot[i];
+                if (l != null) {
+                    // we have a local with a known name, type and slot
+                    String name = l.getName();
+                    ResolvedJavaType ownerType = method.getDeclaringClass();
+                    ResolvedJavaType type = l.getType().resolve(ownerType);
+                    JavaKind kind = type.getJavaKind();
+                    int slot = l.getSlot();
+                    debugContext.log(DebugContext.DETAILED_LEVEL, "locals[%d] %s type %s slot %d", i, name, type.getName(), slot);
+                    JavaValue value = (slot < frame.numLocals ? frame.getLocalValue(slot) : Value.ILLEGAL);
+                    JavaKind storageKind = (slot < frame.numLocals ? frame.getLocalValueKind(slot) : JavaKind.Illegal);
+                    debugContext.log(DebugContext.DETAILED_LEVEL, "  =>  %s kind %s", value, storageKind);
+                    int bciStart = l.getStartBCI();
+                    int firstLine = (lineNumberTable != null ? lineNumberTable.getLineNumber(bciStart) : -1);
+                    // only add the local if the kinds match
+                    if ((storageKind == kind) ||
+                            isIntegralKindPromotion(storageKind, kind) ||
+                            (kind == JavaKind.Object && storageKind == JavaKind.Long)) {
+                        localInfos.add(new SubstrateDebugLocalValueInfo(name, value, framesize, storageKind, type, slot, firstLine));
+                    } else if (storageKind != JavaKind.Illegal) {
+                        debugContext.log(DebugContext.DETAILED_LEVEL, "  value kind incompatible with var kind %s!", type.getJavaKind());
+                    }
+                }
+            }
+            return localInfos;
+        }
+
+        private static boolean isIntegralKindPromotion(JavaKind promoted, JavaKind original) {
+            return (promoted == JavaKind.Int &&
+                    (original == JavaKind.Boolean || original == JavaKind.Byte || original == JavaKind.Short || original == JavaKind.Char));
+        }
+
+        private Local[] getLocalsBySlot() {
+            LocalVariableTable lvt = method.getLocalVariableTable();
+            Local[] nonEmptySortedLocals = null;
+            if (lvt != null) {
+                Local[] locals = lvt.getLocalsAt(bci);
+                if (locals != null && locals.length > 0) {
+                    nonEmptySortedLocals = Arrays.copyOf(locals, locals.length);
+                    Arrays.sort(nonEmptySortedLocals, (Local l1, Local l2) -> l1.getSlot() - l2.getSlot());
+                }
+            }
+            return nonEmptySortedLocals;
+        }
+
+        @Override
+        public long addressLo() {
+            return lo;
+        }
+
+        @Override
+        public long addressHi() {
+            return hi;
+        }
+
+        @Override
+        public int line() {
+            LineNumberTable lineNumberTable = method.getLineNumberTable();
+            if (lineNumberTable != null && bci >= 0) {
+                return lineNumberTable.getLineNumber(bci);
+            }
+            return -1;
+        }
+
+        @Override
+        public DebugLocationInfo getCaller() {
+            return callersLocationInfo;
+        }
+
+        @Override
+        public List<DebugLocalValueInfo> getLocalValueInfo() {
+            if (localInfoList != null) {
+                return localInfoList;
+            } else {
+                return Collections.emptyList();
+            }
+        }
+
+        @Override
+        public boolean isLeaf() {
+            return isLeaf;
+        }
+
+        public int depth() {
+            int depth = 1;
+            DebugLocationInfo caller = getCaller();
+            while (caller != null) {
+                depth++;
+                caller = caller.getCaller();
+            }
+            return depth;
+        }
+
+        private int localsSize() {
+            if (localInfoList != null) {
+                return localInfoList.size();
+            } else {
+                return 0;
+            }
+        }
+
+        /**
+         * Merge the supplied leaf location info into this leaf location info if they have
+         * contiguous ranges, the same method and line number and the same live local variables with
+         * the same values.
+         *
+         * @param that a leaf location info to be merged into this one
+         * @return this leaf location info if the merge was performed otherwise null
+         */
+        SubstrateDebugLocationInfo merge(SubstrateDebugLocationInfo that) {
+            assert callersLocationInfo == that.callersLocationInfo;
+            assert isLeaf == that.isLeaf;
+            assert depth() == that.depth() : "should only compare sibling ranges";
+            assert this.hi <= that.lo : "later nodes should not overlap earlier ones";
+            if (this.hi != that.lo) {
+                return null;
+            }
+            if (!method.equals(that.method)) {
+                return null;
+            }
+            if (line() != that.line()) {
+                return null;
+            }
+            int size = localsSize();
+            if (size != that.localsSize()) {
+                return null;
+            }
+            for (int i = 0; i < size; i++) {
+                SubstrateDebugLocalValueInfo thisLocal = (SubstrateDebugLocalValueInfo) localInfoList.get(i);
+                SubstrateDebugLocalValueInfo thatLocal = (SubstrateDebugLocalValueInfo) that.localInfoList.get(i);
+                if (!thisLocal.equals(thatLocal)) {
+                    return null;
+                }
+            }
+            debugContext.log(DebugContext.DETAILED_LEVEL, "Merge  leaf Location Info : %s depth %d (%d, %d) into (%d, %d)", that.name(), that.depth(), that.lo, that.hi - 1, this.lo, this.hi - 1);
+            // merging just requires updating lo and hi range as everything else is equal
+            this.hi = that.hi;
+
+            return this;
+        }
+    }
+
+    /**
+     * Size in bytes of the frame at call entry before any stack extend. Essentially this accounts
+     * for any automatically pushed return address whose presence depends upon the architecture.
+     */
+    static final int PRE_EXTEND_FRAME_SIZE = ConfigurationValues.getTarget().arch.getReturnAddressSize();
+
+    private class SubstrateDebugLocalInfo implements DebugLocalInfo {
+        protected final String name;
+        protected ResolvedJavaType type;
+        protected final JavaKind kind;
+        protected int slot;
+        protected int line;
+
+        SubstrateDebugLocalInfo(String name, JavaKind kind, ResolvedJavaType resolvedType, int slot, int line) {
+            this.name = name;
+            this.kind = kind;
+            this.slot = slot;
+            this.line = line;
+            // if we don't have a type default it for the JavaKind
+            // it may still end up null when kind is Undefined.
+            this.type = resolvedType;
+        }
+
+        @Override
+        public ResolvedJavaType valueType() {
+            return type;
+        }
+
+        @Override
+        public String name() {
+            return name;
+        }
+
+        @Override
+        public String typeName() {
+            ResolvedJavaType valueType = valueType();
+            return (valueType == null ? "" : valueType().toJavaName());
+        }
+
+        @Override
+        public int slot() {
+            return slot;
+        }
+
+        @Override
+        public int slotCount() {
+            return kind.getSlotCount();
+        }
+
+        @Override
+        public JavaKind javaKind() {
+            return kind;
+        }
+
+        @Override
+        public int line() {
+            return line;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof SubstrateDebugLocalInfo)) {
+                return false;
+            }
+            SubstrateDebugLocalInfo that = (SubstrateDebugLocalInfo) o;
+            // locals need to have the same name
+            if (!name.equals(that.name)) {
+                return false;
+            }
+            // locals need to have the same type
+            if (!type.equals(that.type)) {
+                return false;
+            }
+            // values need to be for the same line
+            if (line != that.line) {
+                return false;
+            }
+            // kinds must match
+            if (kind != that.kind) {
+                return false;
+            }
+            // slots must match
+            return slot == that.slot;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(name) * 31 + line;
+        }
+
+        @Override
+        public String toString() {
+            return typeName() + " " + name;
+        }
+    }
+
+    private class SubstrateDebugLocalValueInfo extends SubstrateDebugLocalInfo implements DebugLocalValueInfo {
+        private final SubstrateDebugLocalValue value;
+        private DebugLocalValueInfo.LocalKind localKind;
+
+        SubstrateDebugLocalValueInfo(String name, JavaValue value, int framesize, JavaKind kind, ResolvedJavaType resolvedType, int slot, int line) {
+            super(name, kind, resolvedType, slot, line);
+            if (value instanceof RegisterValue) {
+                this.localKind = DebugLocalValueInfo.LocalKind.REGISTER;
+                this.value = SubstrateDebugRegisterValue.create((RegisterValue) value);
+            } else if (value instanceof StackSlot) {
+                this.localKind = DebugLocalValueInfo.LocalKind.STACKSLOT;
+                this.value = SubstrateDebugStackValue.create((StackSlot) value, framesize);
+            } else if (value instanceof JavaConstant constant && (constant instanceof PrimitiveConstant || constant.isNull())) {
+                this.localKind = DebugLocalValueInfo.LocalKind.CONSTANT;
+                this.value = SubstrateDebugConstantValue.create(constant);
+            } else {
+                this.localKind = DebugLocalValueInfo.LocalKind.UNDEFINED;
+                this.value = null;
+            }
+        }
+
+        SubstrateDebugLocalValueInfo(String name, SubstrateDebugLocalValue value, JavaKind kind, ResolvedJavaType type, int slot, int line) {
+            super(name, kind, type, slot, line);
+            if (value == null) {
+                this.localKind = DebugLocalValueInfo.LocalKind.UNDEFINED;
+            } else if (value instanceof SubstrateDebugRegisterValue) {
+                this.localKind = DebugLocalValueInfo.LocalKind.REGISTER;
+            } else if (value instanceof SubstrateDebugStackValue) {
+                this.localKind = DebugLocalValueInfo.LocalKind.STACKSLOT;
+            } else if (value instanceof SubstrateDebugConstantValue) {
+                this.localKind = DebugLocalValueInfo.LocalKind.CONSTANT;
+            }
+            this.value = value;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof SubstrateDebugLocalValueInfo)) {
+                return false;
+            }
+            SubstrateDebugLocalValueInfo that = (SubstrateDebugLocalValueInfo) o;
+            // values need to have the same name
+            if (!name().equals(that.name())) {
+                return false;
+            }
+            // values need to be for the same line
+            if (line != that.line) {
+                return false;
+            }
+            // location kinds must match
+            if (localKind != that.localKind) {
+                return false;
+            }
+            // locations must match
+            switch (localKind) {
+                case REGISTER:
+                case STACKSLOT:
+                case CONSTANT:
+                    return value.equals(that.value);
+                default:
+                    return true;
+            }
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(name(), value) * 31 + line;
+        }
+
+        @Override
+        public String toString() {
+            switch (localKind) {
+                case REGISTER:
+                    return "reg[" + regIndex() + "]";
+                case STACKSLOT:
+                    return "stack[" + stackSlot() + "]";
+                case CONSTANT:
+                    return "constant[" + (constantValue() != null ? constantValue().toValueString() : "null") + "]";
+                default:
+                    return "-";
+            }
+        }
+
+        @Override
+        public DebugLocalValueInfo.LocalKind localKind() {
+            return localKind;
+        }
+
+        @Override
+        public int regIndex() {
+            return ((SubstrateDebugRegisterValue) value).getNumber();
+        }
+
+        @Override
+        public int stackSlot() {
+            return ((SubstrateDebugStackValue) value).getOffset();
+        }
+
+        @Override
+        public long heapOffset() {
+            return ((SubstrateDebugConstantValue) value).getHeapOffset();
+        }
+
+        @Override
+        public JavaConstant constantValue() {
+            return ((SubstrateDebugConstantValue) value).getConstant();
+        }
+    }
+
+    public abstract static class SubstrateDebugLocalValue {
+    }
+
+    public static final class SubstrateDebugRegisterValue extends SubstrateDebugLocalValue {
+        private static HashMap<Integer, SubstrateDebugRegisterValue> registerValues = new HashMap<>();
+        private int number;
+        private String name;
+
+        private SubstrateDebugRegisterValue(int number, String name) {
+            this.number = number;
+            this.name = "reg:" + name;
+        }
+
+        static SubstrateDebugRegisterValue create(RegisterValue value) {
+            int number = value.getRegister().number;
+            String name = value.getRegister().name;
+            return memoizedCreate(number, name);
+        }
+
+        static SubstrateDebugRegisterValue memoizedCreate(int number, String name) {
+            SubstrateDebugRegisterValue reg = registerValues.get(number);
+            if (reg == null) {
+                reg = new SubstrateDebugRegisterValue(number, name);
+                registerValues.put(number, reg);
+            }
+            return reg;
+        }
+
+        public int getNumber() {
+            return number;
+        }
+
+        @Override
+        public String toString() {
+            return name;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof SubstrateDebugRegisterValue)) {
+                return false;
+            }
+            SubstrateDebugRegisterValue that = (SubstrateDebugRegisterValue) o;
+            return number == that.number;
+        }
+
+        @Override
+        public int hashCode() {
+            return number * 31;
+        }
+    }
+
+    public static final class SubstrateDebugStackValue extends SubstrateDebugLocalValue {
+        private static HashMap<Integer, SubstrateDebugStackValue> stackValues = new HashMap<>();
+        private int offset;
+        private String name;
+
+        private SubstrateDebugStackValue(int offset) {
+            this.offset = offset;
+            this.name = "stack:" + offset;
+        }
+
+        static SubstrateDebugStackValue create(StackSlot value, int framesize) {
+            // Work around a problem on AArch64 where StackSlot asserts if it is
+            // passed a zero frame size, even though this is what is expected
+            // for stack slot offsets provided at the point of entry (because,
+            // unlike x86, lr has not been pushed).
+            int offset = (framesize == 0 ? value.getRawOffset() : value.getOffset(framesize));
+            return memoizedCreate(offset);
+        }
+
+        static SubstrateDebugStackValue create(DebugLocalValueInfo previous, int adjustment) {
+            assert previous.localKind() == DebugLocalValueInfo.LocalKind.STACKSLOT;
+            return memoizedCreate(previous.stackSlot() + adjustment);
+        }
+
+        private static SubstrateDebugStackValue memoizedCreate(int offset) {
+            SubstrateDebugStackValue value = stackValues.get(offset);
+            if (value == null) {
+                value = new SubstrateDebugStackValue(offset);
+                stackValues.put(offset, value);
+            }
+            return value;
+        }
+
+        public int getOffset() {
+            return offset;
+        }
+
+        @Override
+        public String toString() {
+            return name;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof SubstrateDebugStackValue)) {
+                return false;
+            }
+            SubstrateDebugStackValue that = (SubstrateDebugStackValue) o;
+            return offset == that.offset;
+        }
+
+        @Override
+        public int hashCode() {
+            return offset * 31;
+        }
+    }
+
+    public static final class SubstrateDebugConstantValue extends SubstrateDebugLocalValue {
+        private static HashMap<JavaConstant, SubstrateDebugConstantValue> constantValues = new HashMap<>();
+        private JavaConstant value;
+        private long heapoffset;
+
+        private SubstrateDebugConstantValue(JavaConstant value, long heapoffset) {
+            this.value = value;
+            this.heapoffset = heapoffset;
+        }
+
+        static SubstrateDebugConstantValue create(JavaConstant value) {
+            return create(value, -1);
+        }
+
+        static SubstrateDebugConstantValue create(JavaConstant value, long heapoffset) {
+            SubstrateDebugConstantValue c = constantValues.get(value);
+            if (c == null) {
+                c = new SubstrateDebugConstantValue(value, heapoffset);
+                constantValues.put(value, c);
+            }
+            assert c.heapoffset == heapoffset;
+            return c;
+        }
+
+        public JavaConstant getConstant() {
+            return value;
+        }
+
+        public long getHeapOffset() {
+            return heapoffset;
+        }
+
+        @Override
+        public String toString() {
+            return "constant:" + value.toString();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (!(o instanceof SubstrateDebugConstantValue)) {
+                return false;
+            }
+            SubstrateDebugConstantValue that = (SubstrateDebugConstantValue) o;
+            return heapoffset == that.heapoffset && value.equals(that.value);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(value) * 31 + (int) heapoffset;
+        }
+    }
+
+    /**
+     * Implementation of the DebugFrameSizeChange API interface that allows stack frame size change
+     * info to be passed to an ObjectFile when generation of debug info is enabled.
+     */
+    private class SubstrateDebugFrameSizeChange implements DebugFrameSizeChange {
+        private int offset;
+        private Type type;
+
+        SubstrateDebugFrameSizeChange(int offset, Type type) {
+            this.offset = offset;
+            this.type = type;
+        }
+
+        @Override
+        public int getOffset() {
+            return offset;
+        }
+
+        @Override
+        public Type getType() {
+            return type;
+        }
+    }
+}
diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/SubstrateGraalUtils.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/SubstrateGraalUtils.java
index d5112144f8a9..76a37f0e25c1 100644
--- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/SubstrateGraalUtils.java
+++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/SubstrateGraalUtils.java
@@ -31,6 +31,15 @@
 import java.util.EnumMap;
 import java.util.Map;
 
+import jdk.graal.compiler.phases.util.Providers;
+import com.oracle.svm.core.SubstrateOptions;
+import com.oracle.svm.core.log.Log;
+import com.oracle.svm.core.option.RuntimeOptionValues;
+import com.oracle.svm.graal.isolated.IsolatedGraalUtils;
+import com.oracle.svm.graal.meta.RuntimeCodeInstaller;
+import com.oracle.svm.graal.meta.SubstrateInstalledCodeImpl;
+import jdk.graal.compiler.printer.GraalDebugHandlersFactory;
+import jdk.vm.ci.code.InstalledCode;
 import org.graalvm.collections.EconomicMap;
 import org.graalvm.nativeimage.ImageSingletons;
 
@@ -81,6 +90,19 @@ public static CompilationResult compile(DebugContext debug, final SubstrateMetho
         return doCompile(debug, TruffleRuntimeCompilationSupport.getRuntimeConfig(), TruffleRuntimeCompilationSupport.getLIRSuites(), method);
     }
 
+    public static InstalledCode compileAndInstall(SubstrateMethod method) {
+        if (SubstrateOptions.shouldCompileInIsolates()) {
+            return IsolatedGraalUtils.compileInNewIsolateAndInstall(method);
+        }
+        RuntimeConfiguration runtimeConfiguration = TruffleRuntimeCompilationSupport.getRuntimeConfig();
+        DebugContext debug = new DebugContext.Builder(RuntimeOptionValues.singleton(), new GraalDebugHandlersFactory(runtimeConfiguration.getProviders().getSnippetReflection())).build();
+        SubstrateInstalledCodeImpl installedCode = new SubstrateInstalledCodeImpl(method);
+        CompilationResult compilationResult = SubstrateGraalUtils.doCompile(debug, TruffleRuntimeCompilationSupport.getRuntimeConfig(), TruffleRuntimeCompilationSupport.getLIRSuites(), method);
+        RuntimeCodeInstaller.install(method, compilationResult, installedCode);
+        Log.log().string("Code for " + method.format("%H.%n(%p)") + ": " + compilationResult.getTargetCodeSize() + " bytes").newline();
+        return installedCode;
+    }
+
     private static final Map<ExceptionAction, Integer> compilationProblemsPerAction = new EnumMap<>(ExceptionAction.class);
 
     private static final CompilationWatchDog.EventHandler COMPILATION_WATCH_DOG_EVENT_HANDLER = new CompilationWatchDog.EventHandler() {

From bf9dc642dd4ba1d60529d4640570815d8950c9f8 Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Tue, 17 Sep 2024 16:58:51 +0200
Subject: [PATCH 05/34] Rework Runtime Debug Info Generation

---
 substratevm/mx.substratevm/suite.py           |   2 +-
 .../objectfile/debugentry/ClassEntry.java     |   6 +-
 .../debugentry/StructureTypeEntry.java        |   4 +
 .../objectfile/debugentry/TypeEntry.java      |   1 +
 .../debugentry/range/CallRange.java           |   2 +-
 .../debugentry/range/LeafRange.java           |   2 +-
 .../debugentry/range/PrimaryRange.java        |   2 +-
 .../objectfile/debugentry/range/Range.java    |  14 +-
 .../objectfile/debugentry/range/SubRange.java |   2 +-
 .../debuginfo/DebugInfoProvider.java          |   4 +-
 .../elf/dwarf/DwarfAbbrevSectionImpl.java     |  21 -
 .../objectfile/elf/dwarf/DwarfDebugInfo.java  |   1 -
 .../elf/dwarf/DwarfRangesSectionImpl.java     |   6 +-
 .../pecoff/cv/CVLineRecordBuilder.java        |   2 +-
 .../pecoff/cv/CVSymbolSubsectionBuilder.java  |   2 +-
 .../runtime/RuntimeDebugInfoBase.java         | 647 ++++++-------
 .../runtime/RuntimeDebugInfoProvider.java     | 275 +-----
 .../debugentry/CompiledMethodEntry.java       | 164 ----
 .../runtime/debugentry/DirEntry.java          |  52 --
 .../runtime/debugentry/FileEntry.java         |  82 --
 .../runtime/debugentry/MemberEntry.java       | 112 ---
 .../runtime/debugentry/MethodEntry.java       | 380 --------
 .../debugentry/PrimitiveTypeEntry.java        | 102 --
 .../runtime/debugentry/StringEntry.java       |  89 --
 .../runtime/debugentry/StringTable.java       | 103 --
 .../debugentry/StructureTypeEntry.java        |  72 --
 .../runtime/debugentry/TypeEntry.java         | 105 ---
 .../runtime/debugentry/TypedefEntry.java      |  18 -
 .../runtime/debugentry/range/CallRange.java   |  73 --
 .../runtime/debugentry/range/LeafRange.java   |  50 -
 .../debugentry/range/PrimaryRange.java        |  77 --
 .../runtime/debugentry/range/Range.java       | 301 ------
 .../runtime/debugentry/range/SubRange.java    | 131 ---
 .../dwarf/RuntimeDwarfAbbrevSectionImpl.java  | 251 +++--
 .../runtime/dwarf/RuntimeDwarfDebugInfo.java  | 293 +++---
 .../dwarf/RuntimeDwarfFrameSectionImpl.java   |  14 +-
 .../RuntimeDwarfFrameSectionImplAArch64.java  |   8 +-
 .../RuntimeDwarfFrameSectionImplX86_64.java   |   8 +-
 .../dwarf/RuntimeDwarfInfoSectionImpl.java    | 610 +++++++-----
 .../dwarf/RuntimeDwarfLineSectionImpl.java    | 190 ++--
 .../dwarf/RuntimeDwarfLocSectionImpl.java     | 296 +++---
 .../dwarf/RuntimeDwarfSectionImpl.java        | 287 ++++--
 .../dwarf/RuntimeDwarfStrSectionImpl.java     |   4 +-
 .../runtime/dwarf/constants/DwarfAccess.java  |  46 -
 .../dwarf/constants/DwarfAttribute.java       |  81 --
 .../dwarf/constants/DwarfEncoding.java        |  52 --
 .../constants/DwarfExpressionOpcode.java      |  62 --
 .../runtime/dwarf/constants/DwarfFlag.java    |  46 -
 .../runtime/dwarf/constants/DwarfForm.java    |  66 --
 .../dwarf/constants/DwarfFrameValue.java      |  68 --
 .../dwarf/constants/DwarfHasChildren.java     |  45 -
 .../runtime/dwarf/constants/DwarfInline.java  |  50 -
 .../dwarf/constants/DwarfLanguage.java        |  45 -
 .../dwarf/constants/DwarfLineOpcode.java      | 113 ---
 .../dwarf/constants/DwarfSectionName.java     |  53 --
 .../runtime/dwarf/constants/DwarfTag.java     |  62 --
 .../runtime/dwarf/constants/DwarfVersion.java |  51 -
 .../core/debug/SubstrateBFDNameProvider.java  | 882 ++++++++++++++++++
 .../debug/SubstrateDebugInfoProvider.java     | 564 +++++++++--
 .../svm/graal/meta/SubstrateMethod.java       |   1 -
 .../image/NativeImageDebugInfoFeature.java    |   3 +
 .../image/NativeImageDebugInfoProvider.java   |  18 +-
 62 files changed, 2874 insertions(+), 4299 deletions(-)
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/CompiledMethodEntry.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/DirEntry.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/FileEntry.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/MemberEntry.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/MethodEntry.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/PrimitiveTypeEntry.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/StringEntry.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/StringTable.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/StructureTypeEntry.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/TypeEntry.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/TypedefEntry.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/CallRange.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/LeafRange.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/PrimaryRange.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/Range.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/SubRange.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfAccess.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfAttribute.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfEncoding.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfExpressionOpcode.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfFlag.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfForm.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfFrameValue.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfHasChildren.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfInline.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfLanguage.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfLineOpcode.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfSectionName.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfTag.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfVersion.java
 create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateBFDNameProvider.java

diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py
index ab1c8e0e55c0..b718d19d0ef3 100644
--- a/substratevm/mx.substratevm/suite.py
+++ b/substratevm/mx.substratevm/suite.py
@@ -999,7 +999,7 @@
                 },
                 "<others>": {
                     "<others>": {
-                        "cflags": ["-Wall", "-fPIC", "-O2", "-g", "-gdwarf-4"],
+                        "cflags": ["-Wall", "-fPIC", "-O2", "-g", "-gdwarf-5"],
                     },
                 },
             },
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
index b2d5f83d809a..1bfc0981a334 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
@@ -318,7 +318,7 @@ public boolean hasCompiledEntries() {
         return compiledEntryCount() != 0;
     }
 
-    public int compiledEntriesBase() {
+    public long compiledEntriesBase() {
         assert hasCompiledEntries();
         return compiledEntries.get(0).getPrimary().getLo();
     }
@@ -353,7 +353,7 @@ public List<MethodEntry> getMethods() {
      *
      * @return the lowest code section offset for compiled method code belonging to this class
      */
-    public int lowpc() {
+    public long lowpc() {
         assert hasCompiledEntries();
         return compiledEntries.get(0).getPrimary().getLo();
     }
@@ -365,7 +365,7 @@ public int lowpc() {
      *
      * @return the highest code section offset for compiled method code belonging to this class
      */
-    public int hipc() {
+    public long hipc() {
         assert hasCompiledEntries();
         return compiledEntries.get(compiledEntries.size() - 1).getPrimary().getHi();
     }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java
index 961692ee4c00..d56919481b15 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java
@@ -69,6 +69,10 @@ public Stream<FieldEntry> fields() {
         return fields.stream();
     }
 
+    public List<FieldEntry> getFields() {
+        return fields;
+    }
+
     public int fieldCount() {
         return fields.size();
     }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/TypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/TypeEntry.java
index 4bb5584a768b..723466e6c52f 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/TypeEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/TypeEntry.java
@@ -38,6 +38,7 @@
 import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind;
 import com.oracle.objectfile.elf.dwarf.DwarfDebugInfo;
 
+import com.oracle.objectfile.runtime.RuntimeDebugInfoBase;
 import jdk.graal.compiler.debug.DebugContext;
 
 public abstract class TypeEntry {
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/CallRange.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/CallRange.java
index 39e639306c41..c69270610d55 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/CallRange.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/CallRange.java
@@ -39,7 +39,7 @@ class CallRange extends SubRange {
      */
     protected SubRange lastCallee;
 
-    protected CallRange(MethodEntry methodEntry, int lo, int hi, int line, PrimaryRange primary, Range caller) {
+    protected CallRange(MethodEntry methodEntry, long lo, long hi, int line, PrimaryRange primary, Range caller) {
         super(methodEntry, lo, hi, line, primary, caller);
         this.firstCallee = null;
         this.lastCallee = null;
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/LeafRange.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/LeafRange.java
index 19d9a706cc16..2fc9f37fb45d 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/LeafRange.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/LeafRange.java
@@ -29,7 +29,7 @@
 import com.oracle.objectfile.debugentry.MethodEntry;
 
 class LeafRange extends SubRange {
-    protected LeafRange(MethodEntry methodEntry, int lo, int hi, int line, PrimaryRange primary, Range caller) {
+    protected LeafRange(MethodEntry methodEntry, long lo, long hi, int line, PrimaryRange primary, Range caller) {
         super(methodEntry, lo, hi, line, primary, caller);
     }
 
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/PrimaryRange.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/PrimaryRange.java
index 4e4c7fc6ae91..10f713f840b7 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/PrimaryRange.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/PrimaryRange.java
@@ -39,7 +39,7 @@ public class PrimaryRange extends Range {
      */
     protected SubRange lastCallee;
 
-    protected PrimaryRange(MethodEntry methodEntry, int lo, int hi, int line) {
+    protected PrimaryRange(MethodEntry methodEntry, long lo, long hi, int line) {
         super(methodEntry, lo, hi, line, -1);
         this.firstCallee = null;
         this.lastCallee = null;
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java
index e0a0360a6177..748d6c4eddd7 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java
@@ -72,8 +72,8 @@
 public abstract class Range {
     private static final String CLASS_DELIMITER = ".";
     protected final MethodEntry methodEntry;
-    protected final int lo;
-    protected int hi;
+    protected final long lo;
+    protected long hi;
     protected final int line;
     protected final int depth;
 
@@ -87,7 +87,7 @@ public abstract class Range {
      * @param line the line number associated with the range
      * @return a new primary range to serve as the root of the subrange tree.
      */
-    public static PrimaryRange createPrimary(MethodEntry methodEntry, int lo, int hi, int line) {
+    public static PrimaryRange createPrimary(MethodEntry methodEntry, long lo, long hi, int line) {
         return new PrimaryRange(methodEntry, lo, hi, line);
     }
 
@@ -105,7 +105,7 @@ public static PrimaryRange createPrimary(MethodEntry methodEntry, int lo, int hi
      * @param isLeaf true if this is a leaf range with no subranges
      * @return a new subrange to be linked into the range tree below the primary range.
      */
-    public static SubRange createSubrange(MethodEntry methodEntry, int lo, int hi, int line, PrimaryRange primary, Range caller, boolean isLeaf) {
+    public static SubRange createSubrange(MethodEntry methodEntry, long lo, long hi, int line, PrimaryRange primary, Range caller, boolean isLeaf) {
         assert primary != null;
         assert primary.isPrimary();
         if (isLeaf) {
@@ -115,7 +115,7 @@ public static SubRange createSubrange(MethodEntry methodEntry, int lo, int hi, i
         }
     }
 
-    protected Range(MethodEntry methodEntry, int lo, int hi, int line, int depth) {
+    protected Range(MethodEntry methodEntry, long lo, long hi, int line, int depth) {
         assert methodEntry != null;
         this.methodEntry = methodEntry;
         this.lo = lo;
@@ -144,11 +144,11 @@ public String getSymbolName() {
         return methodEntry.getSymbolName();
     }
 
-    public int getHi() {
+    public long getHi() {
         return hi;
     }
 
-    public int getLo() {
+    public long getLo() {
         return lo;
     }
 
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/SubRange.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/SubRange.java
index 6c8f9a1fc669..58b2178b42e2 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/SubRange.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/SubRange.java
@@ -60,7 +60,7 @@ public abstract class SubRange extends Range {
     private DebugInfoProvider.DebugLocalInfo[] localInfos;
 
     @SuppressWarnings("this-escape")
-    protected SubRange(MethodEntry methodEntry, int lo, int hi, int line, PrimaryRange primary, Range caller) {
+    protected SubRange(MethodEntry methodEntry, long lo, long hi, int line, PrimaryRange primary, Range caller) {
         super(methodEntry, lo, hi, line, (caller == null ? 0 : caller.depth + 1));
         this.caller = caller;
         if (caller != null) {
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java
index e8488c87b4f0..a4ffa8bb144c 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java
@@ -314,13 +314,13 @@ interface DebugRangeInfo extends DebugMethodInfo {
          * @return the lowest address containing code generated for an outer or inlined code segment
          *         reported at this line represented as an offset into the code segment.
          */
-        int addressLo();
+        long addressLo();
 
         /**
          * @return the first address above the code generated for an outer or inlined code segment
          *         reported at this line represented as an offset into the code segment.
          */
-        int addressHi();
+        long addressHi();
     }
 
     /**
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java
index 3ceb9a36988f..ead4598414a3 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java
@@ -943,7 +943,6 @@ public int writeAbbrevs(DebugContext context, byte[] buffer, int p) {
         pos = writeMethodLocationAbbrev(context, buffer, pos);
         pos = writeAbstractInlineMethodAbbrev(context, buffer, pos);
         pos = writeStaticFieldLocationAbbrev(context, buffer, pos);
-        pos = writeStaticFieldLocationTypeUnitRefAbbrev(context, buffer, pos);
         pos = writeSuperReferenceAbbrev(context, buffer, pos);
         pos = writeInterfaceImplementorAbbrev(context, buffer, pos);
 
@@ -1559,26 +1558,6 @@ private int writeStaticFieldLocationAbbrev(@SuppressWarnings("unused") DebugCont
         return pos;
     }
 
-    private int writeStaticFieldLocationTypeUnitRefAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
-        int pos = p;
-
-        pos = writeAbbrevCode(AbbrevCode.STATIC_FIELD_LOCATION_TYPE_UNIT_REF, buffer, pos);
-        pos = writeTag(DwarfTag.DW_TAG_variable, buffer, pos);
-        pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_no, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_specification, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_ref_addr, buffer, pos);
-        // pos = writeAttrType(DwarfDebugInfo.DW_AT_linkage_name, buffer, pos);
-        // pos = writeAttrForm(DwarfDebugInfo.DW_FORM_strp, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_location, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_expr_loc, buffer, pos);
-        /*
-         * Now terminate.
-         */
-        pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos);
-        return pos;
-    }
-
     private int writeSuperReferenceAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
         int pos = p;
         pos = writeAbbrevCode(AbbrevCode.SUPER_REFERENCE, buffer, pos);
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java
index 4166c81def1c..e3634115c5b3 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java
@@ -76,7 +76,6 @@ enum AbbrevCode {
         FOREIGN_STRUCT,
         METHOD_LOCATION,
         STATIC_FIELD_LOCATION,
-        STATIC_FIELD_LOCATION_TYPE_UNIT_REF,
         ARRAY_LAYOUT,
         INTERFACE_LAYOUT,
         COMPRESSED_LAYOUT,
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfRangesSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfRangesSectionImpl.java
index 1d289c6307c8..e80f64692c85 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfRangesSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfRangesSectionImpl.java
@@ -131,7 +131,7 @@ private int writeRangeLists(DebugContext context, byte[] buffer, int p) {
     private int writeRangeList(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) {
         int pos = p;
         log(context, "  [0x%08x] ranges start for class %s", pos, classEntry.getTypeName());
-        int base = classEntry.compiledEntriesBase();
+        long base = classEntry.compiledEntriesBase();
         log(context, "  [0x%08x] base 0x%x", pos, base);
         pos = writeRangeListEntry(DwarfRangeListEntry.DW_RLE_base_address, buffer, pos);
         pos = writeRelocatableCodeOffset(base, buffer, pos);
@@ -140,8 +140,8 @@ private int writeRangeList(DebugContext context, ClassEntry classEntry, byte[] b
         classEntry.compiledEntries().forEach(compiledMethodEntry -> {
             cursor.set(writeRangeListEntry(DwarfRangeListEntry.DW_RLE_offset_pair, buffer, cursor.get()));
 
-            int loOffset = compiledMethodEntry.getPrimary().getLo() - base;
-            int hiOffset = compiledMethodEntry.getPrimary().getHi() - base;
+            long loOffset = compiledMethodEntry.getPrimary().getLo() - base;
+            long hiOffset = compiledMethodEntry.getPrimary().getHi() - base;
             log(context, "  [0x%08x] lo 0x%x (%s)", cursor.get(), loOffset, compiledMethodEntry.getPrimary().getFullMethodNameWithParams());
             cursor.set(writeULEB(loOffset, buffer, cursor.get()));
             log(context, "  [0x%08x] hi 0x%x", cursor.get(), hiOffset);
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVLineRecordBuilder.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVLineRecordBuilder.java
index dd7f777b3bbe..4b766bca5b50 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVLineRecordBuilder.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVLineRecordBuilder.java
@@ -102,7 +102,7 @@ private void processRange(Range range) {
         }
 
         /* Add line record. */
-        int lineLoAddr = range.getLo() - compiledEntry.getPrimary().getLo();
+        int lineLoAddr = (int)(range.getLo() - compiledEntry.getPrimary().getLo());
         int line = Math.max(range.getLine(), 1);
         debug("  processRange:   addNewLine: 0x%05x-0x%05x %s", lineLoAddr, range.getHi() - compiledEntry.getPrimary().getLo(), line);
         lineRecord.addNewLine(lineLoAddr, line);
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubsectionBuilder.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubsectionBuilder.java
index 3d4722d384ac..3eb4d220c45a 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubsectionBuilder.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubsectionBuilder.java
@@ -129,7 +129,7 @@ private void buildFunction(CompiledMethodEntry compiledEntry) {
         /* S_PROC32 add function definition. */
         int functionTypeIndex = addTypeRecords(compiledEntry);
         byte funcFlags = 0;
-        CVSymbolSubrecord.CVSymbolGProc32Record proc32 = new CVSymbolSubrecord.CVSymbolGProc32Record(cvDebugInfo, externalName, debuggerName, 0, 0, 0, primaryRange.getHi() - primaryRange.getLo(), 0,
+        CVSymbolSubrecord.CVSymbolGProc32Record proc32 = new CVSymbolSubrecord.CVSymbolGProc32Record(cvDebugInfo, externalName, debuggerName, 0, 0, 0, (int)(primaryRange.getHi() - primaryRange.getLo()), 0,
                         0, functionTypeIndex, (short) 0, funcFlags);
         addSymbolRecord(proc32);
 
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/RuntimeDebugInfoBase.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/RuntimeDebugInfoBase.java
index 6dc86f836589..cad772ad679d 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/RuntimeDebugInfoBase.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/RuntimeDebugInfoBase.java
@@ -1,128 +1,119 @@
-package com.oracle.objectfile.runtime;
+/*
+ * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
 
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugCodeInfo;
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugFileInfo;
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocalInfo;
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocalValueInfo;
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocationInfo;
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugMethodInfo;
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugTypeInfo.DebugTypeKind;
-import com.oracle.objectfile.runtime.debugentry.CompiledMethodEntry;
-import com.oracle.objectfile.runtime.debugentry.DirEntry;
-import com.oracle.objectfile.runtime.debugentry.FileEntry;
-import com.oracle.objectfile.runtime.debugentry.MethodEntry;
-import com.oracle.objectfile.runtime.debugentry.PrimitiveTypeEntry;
-import com.oracle.objectfile.runtime.debugentry.StringTable;
-import com.oracle.objectfile.runtime.debugentry.TypeEntry;
-import com.oracle.objectfile.runtime.debugentry.TypedefEntry;
-import com.oracle.objectfile.runtime.debugentry.range.PrimaryRange;
-import com.oracle.objectfile.runtime.debugentry.range.Range;
-import com.oracle.objectfile.runtime.debugentry.range.SubRange;
-import jdk.vm.ci.meta.JavaKind;
-import jdk.vm.ci.meta.ResolvedJavaType;
+package com.oracle.objectfile.runtime;
 
 import java.nio.ByteOrder;
-import java.nio.file.FileSystems;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+import com.oracle.objectfile.debugentry.ArrayTypeEntry;
+import com.oracle.objectfile.debugentry.ClassEntry;
+import com.oracle.objectfile.debugentry.CompiledMethodEntry;
+import com.oracle.objectfile.debugentry.DebugInfoBase;
+import com.oracle.objectfile.debugentry.DirEntry;
+import com.oracle.objectfile.debugentry.EnumClassEntry;
+import com.oracle.objectfile.debugentry.FieldEntry;
+import com.oracle.objectfile.debugentry.FileEntry;
+import com.oracle.objectfile.debugentry.ForeignTypeEntry;
+import com.oracle.objectfile.debugentry.HeaderTypeEntry;
+import com.oracle.objectfile.debugentry.InterfaceClassEntry;
+import com.oracle.objectfile.debugentry.LoaderEntry;
+import com.oracle.objectfile.debugentry.MethodEntry;
+import com.oracle.objectfile.debugentry.PrimitiveTypeEntry;
+import com.oracle.objectfile.debugentry.StringTable;
+import com.oracle.objectfile.debugentry.StructureTypeEntry;
+import com.oracle.objectfile.debugentry.TypeEntry;
+import com.oracle.objectfile.debuginfo.DebugInfoProvider;
+import org.graalvm.collections.EconomicMap;
+import org.graalvm.collections.EconomicSet;
+
+import com.oracle.objectfile.debugentry.range.PrimaryRange;
+import com.oracle.objectfile.debugentry.range.Range;
+import com.oracle.objectfile.debugentry.range.SubRange;
+import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugCodeInfo;
+import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFileInfo;
+import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalValueInfo;
+import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocationInfo;
+import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind;
+import com.oracle.objectfile.elf.dwarf.DwarfDebugInfo;
+
+import jdk.graal.compiler.debug.DebugContext;
+import jdk.vm.ci.meta.ResolvedJavaType;
 
-public class RuntimeDebugInfoBase {
-
-    protected ByteOrder byteOrder;
-    /**
-     * A table listing all known strings, some of which may be marked for insertion into the
-     * debug_str section.
-     */
-    private final StringTable stringTable = new StringTable();
-    /**
-     * List of dirs in which files are found to reside.
-     */
-    private final List<DirEntry> dirs = new ArrayList<>();
-    /**
-     * Index of all dirs in which files are found to reside either as part of substrate/compiler or
-     * user code.
-     */
-    private final HashMap<Path, DirEntry> dirsIndex = new HashMap<>();
-
-    /**
-     * List of all types present in the native image including instance classes, array classes,
-     * primitive types and the one-off Java header struct.
-     */
-    private final List<TypeEntry> types = new ArrayList<>();
-    /**
-     * Index of already seen types keyed by the unique, associated, identifying ResolvedJavaType or,
-     * in the single special case of the TypeEntry for the Java header structure, by key null.
-     */
-    private final HashMap<ResolvedJavaType, TypeEntry> typesIndex = new HashMap<>();
-    /**
-     * List of all types present in the native image including instance classes, array classes,
-     * primitive types and the one-off Java header struct.
-     */
-    private final List<TypedefEntry> typedefs = new ArrayList<>();
-    /**
-     * Index of already seen types keyed by the unique, associated, identifying ResolvedJavaType or,
-     * in the single special case of the TypeEntry for the Java header structure, by key null.
-     */
-    private final HashMap<ResolvedJavaType, TypedefEntry> typedefsIndex = new HashMap<>();
-    /**
-     * Handle on runtime compiled method found in debug info.
-     */
-    private CompiledMethodEntry compiledMethod;
-    /**
-     * List of files which contain primary or secondary ranges.
-     */
-    private final List<FileEntry> files = new ArrayList<>();
-    /**
-     * Index of files which contain primary or secondary ranges keyed by path.
-     */
-    private final HashMap<Path, FileEntry> filesIndex = new HashMap<>();
-
-    /**
-     * Flag set to true if heap references are stored as addresses relative to a heap base register
-     * otherwise false.
-     */
-    private boolean useHeapBase;
+/**
+ * An abstract class which indexes the information presented by the DebugInfoProvider in an
+ * organization suitable for use by subclasses targeting a specific binary format.
+ *
+ * This class provides support for iterating over records detailing all the types and compiled
+ * methods presented via the DebugInfoProvider interface. The obvious hierarchical traversal order
+ * when generating debug info output is:
+ *
+ * 1) by top level compiled method (and associated primary Range) n.b. these are always presented to
+ * the generator in order of ascending address
+ *
+ * 2) by inlined method (sub range) within top level method, also ordered by ascending address
+ *
+ * This traversal ensures that debug records are generated in increasing address order
+ *
+ * An alternative hierarchical traversal order is
+ *
+ * 1) by top level class (unique ResolvedJavaType id) n.b. types are not guaranteed to be presented
+ * to the generator in increasing address order of their method code ranges. In particular many
+ * classes do not have top-level compiled methods and may not even have inlined methods.
+ *
+ * 2) by top level compiled method (and associated primary Range) within a class, which are ordered
+ * by ascending address
+ *
+ * 3) by inlined method (sub range) within top level method, also ordered by ascending address
+ *
+ * Since clients may need to generate records for classes with no compiled methods, the second
+ * traversal order is often employed.
+ *
+ * n.b. methods of a given class do not always appear in a single continuous address range. The
+ * compiler choose to interleave intervening code from other classes or data values in order to get
+ * better cache locality. It may also choose to generate deoptimized variants of methods in a
+ * separate range from normal, optimized compiled code. This out of (code addess) order sorting may
+ * make it difficult to use a class by class traversal to generate debug info in separate per-class
+ * units.
+ */
+public abstract class RuntimeDebugInfoBase extends DebugInfoBase {
+    protected RuntimeDebugInfoProvider debugInfoProvider;
 
     private String cuName;
-    /**
-     * Number of bits oops are left shifted by when using compressed oops.
-     */
-    private int oopCompressShift;
-    /**
-     * Number of low order bits used for tagging oops.
-     */
-    private int oopTagsCount;
-    /**
-     * Number of bytes used to store an oop reference.
-     */
-    private int oopReferenceSize;
-    /**
-     * Number of bytes used to store a raw pointer.
-     */
-    private int pointerSize;
-    /**
-     * Alignment of object memory area (and, therefore, of any oop) in bytes.
-     */
-    private int oopAlignment;
-    /**
-     * Number of bits in oop which are guaranteed 0 by virtue of alignment.
-     */
-    private int oopAlignShift;
-    /**
-     * The compilation directory in which to look for source files as a {@link String}.
-     */
-    private String cachePath;
-
-    /**
-     * The offset of the first byte beyond the end of the Java compiled code address range.
-     */
-    private int compiledCodeMax;
 
     @SuppressWarnings("this-escape")
     public RuntimeDebugInfoBase(ByteOrder byteOrder) {
+        super(byteOrder);
         this.byteOrder = byteOrder;
         this.useHeapBase = true;
         this.oopTagsCount = 0;
@@ -131,15 +122,12 @@ public RuntimeDebugInfoBase(ByteOrder byteOrder) {
         this.pointerSize = 0;
         this.oopAlignment = 0;
         this.oopAlignShift = 0;
+        this.hubClassEntry = null;
         this.compiledCodeMax = 0;
         // create and index an empty dir with index 0.
         ensureDirEntry(EMPTY_PATH);
     }
 
-    public int compiledCodeMax() {
-        return compiledCodeMax;
-    }
-
     /**
      * Entry point allowing ELFObjectFile to pass on information about types, code and heap data.
      *
@@ -147,6 +135,8 @@ public int compiledCodeMax() {
      */
     @SuppressWarnings("try")
     public void installDebugInfo(RuntimeDebugInfoProvider debugInfoProvider) {
+        this.debugInfoProvider = debugInfoProvider;
+
         /*
          * Track whether we need to use a heap base register.
          */
@@ -198,60 +188,106 @@ public void installDebugInfo(RuntimeDebugInfoProvider debugInfoProvider) {
             cachePath = uniqueNullString; // fall back to null string
         }
 
-        // TODO: handle method and required typedefs / primitives
         DebugCodeInfo debugCodeInfo = debugInfoProvider.codeInfoProvider();
-        String fileName = debugCodeInfo.fileName();
-        Path filePath = debugCodeInfo.filePath();
-        ResolvedJavaType ownerType = debugCodeInfo.ownerType();
-        String methodName = debugCodeInfo.name();
-        long lo = debugCodeInfo.addressLo();
-        long hi = debugCodeInfo.addressHi();
-        int primaryLine = debugCodeInfo.line();
-
-        TypedefEntry typedefEntry = lookupTypedefEntry(ownerType);
-        MethodEntry methodEntry = processMethod(debugCodeInfo, typedefEntry);
-        PrimaryRange primaryRange = Range.createPrimary(methodEntry, lo, hi, primaryLine);
-        compiledMethod = new CompiledMethodEntry(primaryRange, debugCodeInfo.getFrameSizeChanges(), debugCodeInfo.getFrameSize(), typedefEntry);
-        HashMap<DebugLocationInfo, SubRange> subRangeIndex = new HashMap<>();
-        for(DebugLocationInfo debugLocationInfo : debugCodeInfo.locationInfoProvider()) {
-            addSubrange(debugLocationInfo, primaryRange, subRangeIndex);
-        }
-    }
+        debugCodeInfo.debugContext((debugContext) -> {
+            /*
+             * Primary file name and full method name need to be written to the debug_str section.
+             */
+            ResolvedJavaType ownerType = debugCodeInfo.ownerType();
+
+            DebugInfoProvider.DebugTypeInfo debugTypeInfo = debugInfoProvider.createDebugTypeInfo(ownerType);
+            String typeName = debugTypeInfo.typeName();
+            typeName = stringTable.uniqueDebugString(typeName);
+            DebugTypeKind typeKind = debugTypeInfo.typeKind();
+            int byteSize = debugTypeInfo.size();
+
+            if (debugContext.isLogEnabled(DebugContext.INFO_LEVEL)) {
+                debugContext.log(DebugContext.INFO_LEVEL, "Register %s type %s ", typeKind.toString(), typeName);
+            }
+            String fileName = debugTypeInfo.fileName();
+            Path filePath = debugTypeInfo.filePath();
+            TypeEntry typeEntry = addTypeEntry(ownerType, typeName, fileName, filePath, byteSize, typeKind);
+            typeEntry.addDebugInfo(this, debugTypeInfo, debugContext);
+
+            fileName = debugCodeInfo.fileName();
+            filePath = debugCodeInfo.filePath();
+            String methodName = debugCodeInfo.name();
+            long lo = debugCodeInfo.addressLo();
+            long hi = debugCodeInfo.addressHi();
+            int primaryLine = debugCodeInfo.line();
+
+            /* Search for a method defining this primary range. */
+            ClassEntry classEntry = lookupClassEntry(ownerType);
+            MethodEntry methodEntry = classEntry.ensureMethodEntryForDebugRangeInfo(debugCodeInfo, this, debugContext);
+            PrimaryRange primaryRange = Range.createPrimary(methodEntry, lo, hi, primaryLine);
+            if (debugContext.isLogEnabled(DebugContext.INFO_LEVEL)) {
+                debugContext.log(DebugContext.INFO_LEVEL, "PrimaryRange %s.%s %s %s:%d [0x%x, 0x%x]", ownerType.toJavaName(), methodName, filePath, fileName, primaryLine, lo, hi);
+            }
+            addPrimaryRange(primaryRange, debugCodeInfo, classEntry);
+            /*
+             * Record all subranges even if they have no line or file so we at least get a symbol
+             * for them and don't see a break in the address range.
+             */
+            EconomicMap<DebugLocationInfo, SubRange> subRangeIndex = EconomicMap.create();
+            debugCodeInfo.locationInfoProvider().forEach(debugLocationInfo -> addSubrange(debugLocationInfo, primaryRange, classEntry, subRangeIndex, debugContext));
 
-    private MethodEntry processMethod(DebugMethodInfo debugMethodInfo, TypedefEntry ownerType) {
-        String methodName = debugMethodInfo.name();
-        int line = debugMethodInfo.line();
-        ResolvedJavaType resultType = debugMethodInfo.valueType();
-        List<DebugLocalInfo> paramInfos = debugMethodInfo.getParamInfo();
-        DebugLocalInfo thisParam = debugMethodInfo.getThisParamInfo();
-        int paramCount = paramInfos.size();
-        TypeEntry resultTypeEntry = lookupTypeEntry(resultType);
-        TypeEntry[] typeEntries = new TypeEntry[paramCount];
-        for (int i = 0; i < paramCount; i++) {
-            typeEntries[i] = lookupTypeEntry(paramInfos.get(i).valueType());
-        }
-        FileEntry methodFileEntry = ensureFileEntry(debugMethodInfo);
-        MethodEntry methodEntry = new MethodEntry(this, debugMethodInfo, methodFileEntry, line, methodName, ownerType, resultTypeEntry, typeEntries, paramInfos, thisParam);
-        // indexMethodEntry(methodEntry, debugMethodInfo.idMethod());
-        return methodEntry;
+            collectFilesAndDirs(classEntry);
+
+            debugInfoProvider.recordActivity();
+        });
+
+        // populate a file and dir list and associated index for each class entry
+        // getInstanceClasses().forEach(DebugInfoBase::collectFilesAndDirs);
     }
 
-    private TypeEntry createTypeEntry(String typeName, String fileName, Path filePath, int size, DebugTypeKind typeKind) {
+    @Override
+    protected TypeEntry createTypeEntry(String typeName, String fileName, Path filePath, int size, DebugTypeKind typeKind) {
         TypeEntry typeEntry = null;
         switch (typeKind) {
-            case TYPEDEF:
-                typeEntry = new TypedefEntry(typeName, size);
+            case INSTANCE: {
+                FileEntry fileEntry = addFileEntry(fileName, filePath);
+                typeEntry = new ClassEntry(typeName, fileEntry, size);
+                if (typeEntry.getTypeName().equals(DwarfDebugInfo.HUB_TYPE_NAME)) {
+                    hubClassEntry = (ClassEntry) typeEntry;
+                }
+                break;
+            }
+            case INTERFACE: {
+                FileEntry fileEntry = addFileEntry(fileName, filePath);
+                typeEntry = new InterfaceClassEntry(typeName, fileEntry, size);
                 break;
+            }
+            case ENUM: {
+                FileEntry fileEntry = addFileEntry(fileName, filePath);
+                typeEntry = new EnumClassEntry(typeName, fileEntry, size);
+                break;
+            }
             case PRIMITIVE:
                 assert fileName.length() == 0;
                 assert filePath == null;
                 typeEntry = new PrimitiveTypeEntry(typeName, size);
                 break;
+            case ARRAY:
+                assert fileName.length() == 0;
+                assert filePath == null;
+                typeEntry = new ArrayTypeEntry(typeName, size);
+                break;
+            case HEADER:
+                assert fileName.length() == 0;
+                assert filePath == null;
+                typeEntry = new HeaderTypeEntry(typeName, size);
+                break;
+            case FOREIGN: {
+                FileEntry fileEntry = addFileEntry(fileName, filePath);
+                typeEntry = new ForeignTypeEntry(typeName, fileEntry, size);
+                break;
+            }
         }
         return typeEntry;
     }
 
-    private TypeEntry addTypeEntry(ResolvedJavaType idType, String typeName, String fileName, Path filePath, int size, DebugTypeKind typeKind) {
+    @Override
+    protected TypeEntry addTypeEntry(ResolvedJavaType idType, String typeName, String fileName, Path filePath, int size, DebugTypeKind typeKind) {
         TypeEntry typeEntry = (idType != null ? typesIndex.get(idType) : null);
         if (typeEntry == null) {
             typeEntry = createTypeEntry(typeName, fileName, filePath, size, typeKind);
@@ -259,52 +295,88 @@ private TypeEntry addTypeEntry(ResolvedJavaType idType, String typeName, String
             if (idType != null) {
                 typesIndex.put(idType, typeEntry);
             }
-            if (typeEntry instanceof TypedefEntry) {
-                indexTypedef(idType, (TypedefEntry) typeEntry);
+            // track object type and header struct
+            if (idType == null) {
+                headerType = (HeaderTypeEntry) typeEntry;
+            }
+            if (typeName.equals("java.lang.Object")) {
+                objectClass = (ClassEntry) typeEntry;
+            }
+            if (typeName.equals("void")) {
+                voidType = typeEntry;
+            }
+            if (typeEntry instanceof ClassEntry) {
+                indexInstanceClass(idType, (ClassEntry) typeEntry);
+            }
+        } else {
+            if (!(typeEntry.isClass())) {
+                assert ((ClassEntry) typeEntry).getFileName().equals(fileName);
             }
         }
         return typeEntry;
     }
 
+    @Override
     public TypeEntry lookupTypeEntry(ResolvedJavaType type) {
         TypeEntry typeEntry = typesIndex.get(type);
         if (typeEntry == null) {
-            if (type.isPrimitive()) {
-                typeEntry = addTypeEntry(type, type.toJavaName(), "", null, (type.getJavaKind() == JavaKind.Void ? 0 : type.getJavaKind().getBitCount()), DebugTypeKind.PRIMITIVE);
-            } else {
-                String fileName = type.getSourceFileName();
-                Path filePath = fullFilePathFromClassName(type);
-                typeEntry = addTypeEntry(type, type.toJavaName(),fileName, filePath, pointerSize, DebugTypeKind.TYPEDEF);
-            }
+            DebugInfoProvider.DebugTypeInfo debugTypeInfo = debugInfoProvider.createDebugTypeInfo(type);
+            debugTypeInfo.debugContext((debugContext) -> {
+                String typeName = debugTypeInfo.typeName();
+                typeName = stringTable.uniqueDebugString(typeName);
+                DebugTypeKind typeKind = debugTypeInfo.typeKind();
+                int byteSize = debugTypeInfo.size();
+
+                if (debugContext.isLogEnabled(DebugContext.INFO_LEVEL)) {
+                    debugContext.log(DebugContext.INFO_LEVEL, "Register %s type %s ", typeKind.toString(), typeName);
+                }
+                String fileName = debugTypeInfo.fileName();
+                Path filePath = debugTypeInfo.filePath();
+                TypeEntry newTypeEntry = addTypeEntry(type, typeName, fileName, filePath, byteSize, typeKind);
+                newTypeEntry.addDebugInfo(this, debugTypeInfo, debugContext);
+            });
+
+            typeEntry = typesIndex.get(type);
         }
-        return typeEntry;
-    }
 
-    TypedefEntry lookupTypedefEntry(ResolvedJavaType type) {
-        TypedefEntry typedefEntry = typedefsIndex.get(type);
-        if (typedefEntry == null) {
-            String fileName = type.getSourceFileName();
-            Path filePath = fullFilePathFromClassName(type);
-            typedefEntry = (TypedefEntry) addTypeEntry(type, type.toJavaName(), fileName, filePath, pointerSize, DebugTypeKind.TYPEDEF);
+        if (typeEntry == null) {
+            throw new RuntimeException("Type entry not found " + type.getName());
         }
-        return typedefEntry;
+        return typeEntry;
     }
 
-    protected static Path fullFilePathFromClassName(ResolvedJavaType type) {
-        String[] elements = type.toJavaName().split("\\.");
-        int count = elements.length;
-        String name = elements[count - 1];
-        while (name.startsWith("$")) {
-            name = name.substring(1);
-        }
-        if (name.contains("$")) {
-            name = name.substring(0, name.indexOf('$'));
+    @Override
+    public ClassEntry lookupClassEntry(ResolvedJavaType type) {
+        // lookup key should advertise itself as a resolved instance class or interface
+        assert type.isInstanceClass() || type.isInterface();
+        // lookup target should already be included in the index
+        ClassEntry classEntry = instanceClassesIndex.get(type);
+        if (classEntry == null) {
+            DebugInfoProvider.DebugTypeInfo debugTypeInfo = debugInfoProvider.createDebugTypeInfo(type);
+            debugTypeInfo.debugContext((debugContext) -> {
+                String typeName = debugTypeInfo.typeName();
+                typeName = stringTable.uniqueDebugString(typeName);
+                DebugTypeKind typeKind = debugTypeInfo.typeKind();
+                int byteSize = debugTypeInfo.size();
+
+                if (debugContext.isLogEnabled(DebugContext.INFO_LEVEL)) {
+                    debugContext.log(DebugContext.INFO_LEVEL, "Register %s type %s ", typeKind.toString(), typeName);
+                }
+                String fileName = debugTypeInfo.fileName();
+                Path filePath = debugTypeInfo.filePath();
+                TypeEntry newTypeEntry = addTypeEntry(type, typeName, fileName, filePath, byteSize, typeKind);
+                newTypeEntry.addDebugInfo(this, debugTypeInfo, debugContext);
+            });
+
+            classEntry = instanceClassesIndex.get(type);
         }
-        if (name.equals("")) {
-            name = "_nofile_";
+
+        if (classEntry == null || !(classEntry.isClass())) {
+            throw new RuntimeException("Class entry not found " + type.getName());
         }
-        elements[count - 1] = name + ".java";
-        return FileSystems.getDefault().getPath("", elements);
+        // lookup target should also be indexed in the types index
+        assert typesIndex.get(type) != null;
+        return classEntry;
     }
 
     /**
@@ -313,12 +385,15 @@ protected static Path fullFilePathFromClassName(ResolvedJavaType type) {
      *
      * @param locationInfo
      * @param primaryRange
+     * @param classEntry
      * @param subRangeIndex
+     * @param debugContext
      * @return the subrange for {@code locationInfo} linked with all its caller subranges up to the
      *         primaryRange
      */
     @SuppressWarnings("try")
-    private Range addSubrange(DebugLocationInfo locationInfo, PrimaryRange primaryRange, HashMap<DebugLocationInfo, SubRange> subRangeIndex) {
+    @Override
+    protected Range addSubrange(DebugLocationInfo locationInfo, PrimaryRange primaryRange, ClassEntry classEntry, EconomicMap<DebugLocationInfo, SubRange> subRangeIndex, DebugContext debugContext) {
         /*
          * We still insert subranges for the primary method but they don't actually count as inline.
          * we only need a range so that subranges for inline code can refer to the top level line
@@ -342,35 +417,31 @@ private Range addSubrange(DebugLocationInfo locationInfo, PrimaryRange primaryRa
         final long lo = primaryRange.getLo() + locationInfo.addressLo();
         final long hi = primaryRange.getLo() + locationInfo.addressHi();
         final int line = locationInfo.line();
-        TypedefEntry subRangeTypedefEntry = lookupTypedefEntry(ownerType);
-        MethodEntry subRangeMethodEntry = processMethod(locationInfo, subRangeTypedefEntry);
+        ClassEntry subRangeClassEntry = lookupClassEntry(ownerType);
+        MethodEntry subRangeMethodEntry = subRangeClassEntry.ensureMethodEntryForDebugRangeInfo(locationInfo, this, debugContext);
         SubRange subRange = Range.createSubrange(subRangeMethodEntry, lo, hi, line, primaryRange, caller, locationInfo.isLeaf());
-        //classEntry.indexSubRange(subRange);
+        classEntry.indexSubRange(subRange);
         subRangeIndex.put(locationInfo, subRange);
-        /*if (debugContext.isLogEnabled(DebugContext.DETAILED_LEVEL)) {
+        if (debugContext.isLogEnabled(DebugContext.DETAILED_LEVEL)) {
             debugContext.log(DebugContext.DETAILED_LEVEL, "SubRange %s.%s %d %s:%d [0x%x, 0x%x] (%d, %d)",
                     ownerType.toJavaName(), methodName, subRange.getDepth(), fullPath, line, lo, hi, loOff, hiOff);
-        }*/
+        }
         assert (callerLocationInfo == null || (callerLocationInfo.addressLo() <= loOff && callerLocationInfo.addressHi() >= hiOff)) : "parent range should enclose subrange!";
-        List<DebugLocalValueInfo> localValueInfos = locationInfo.getLocalValueInfo();
-        /*for (int i = 0; i < localValueInfos.length; i++) {
+        DebugLocalValueInfo[] localValueInfos = locationInfo.getLocalValueInfo();
+        for (int i = 0; i < localValueInfos.length; i++) {
             DebugLocalValueInfo localValueInfo = localValueInfos[i];
             if (debugContext.isLogEnabled(DebugContext.DETAILED_LEVEL)) {
                 debugContext.log(DebugContext.DETAILED_LEVEL, "  locals[%d] %s:%s = %s", localValueInfo.slot(), localValueInfo.name(), localValueInfo.typeName(), localValueInfo);
             }
-        }*/
+        }
         subRange.setLocalValueInfo(localValueInfos);
         return subRange;
     }
 
-    private void indexTypedef(ResolvedJavaType idType, TypedefEntry typedefEntry) {
-        typedefs.add(typedefEntry);
-        typedefsIndex.put(idType, typedefEntry);
-    }
-
     static final Path EMPTY_PATH = Paths.get("");
 
-    private FileEntry addFileEntry(String fileName, Path filePath) {
+    @Override
+    protected FileEntry addFileEntry(String fileName, Path filePath) {
         assert fileName != null;
         Path dirPath = filePath;
         Path fileAsPath;
@@ -396,7 +467,8 @@ private FileEntry addFileEntry(String fileName, Path filePath) {
         return fileEntry;
     }
 
-    public FileEntry ensureFileEntry(DebugFileInfo debugFileInfo) {
+    @Override
+    protected FileEntry ensureFileEntry(DebugFileInfo debugFileInfo) {
         String fileName = debugFileInfo.fileName();
         if (fileName == null || fileName.length() == 0) {
             return null;
@@ -416,180 +488,7 @@ public FileEntry ensureFileEntry(DebugFileInfo debugFileInfo) {
         return fileEntry;
     }
 
-    private DirEntry ensureDirEntry(Path filePath) {
-        if (filePath == null) {
-            return null;
-        }
-        DirEntry dirEntry = dirsIndex.get(filePath);
-        if (dirEntry == null) {
-            /* Ensure dir path is entered into the debug_str section. */
-            uniqueDebugString(filePath.toString());
-            dirEntry = new DirEntry(filePath);
-            dirsIndex.put(filePath, dirEntry);
-            dirs.add(dirEntry);
-        }
-        return dirEntry;
-    }
-
-    /* Accessors to query the debug info model. */
-    public ByteOrder getByteOrder() {
-        return byteOrder;
-    }
-
-    public List<TypeEntry> getTypes() {
-        return types;
-    }
-    public List<TypedefEntry> getTypedefs() {
-        return typedefs;
-    }
-
-    public List<FileEntry> getFiles() {
-        return files;
-    }
-
-    public List<DirEntry> getDirs() {
-        return dirs;
-    }
-
-    @SuppressWarnings("unused")
-    public FileEntry findFile(Path fullFileName) {
-        return filesIndex.get(fullFileName);
-    }
-
-    public StringTable getStringTable() {
-        return stringTable;
-    }
-
-    /**
-     * Indirects this call to the string table.
-     *
-     * @param string the string whose index is required.
-     */
-    public String uniqueDebugString(String string) {
-        return stringTable.uniqueDebugString(string);
-    }
-
-    /**
-     * Indirects this call to the string table.
-     *
-     * @param string the string whose index is required.
-     * @return the offset of the string in the .debug_str section.
-     */
-    public int debugStringIndex(String string) {
-        return stringTable.debugStringIndex(string);
-    }
-
-    public boolean useHeapBase() {
-        return useHeapBase;
-    }
-
-    public String cuName() {
-        return cuName;
-    }
-
-    public byte oopTagsMask() {
-        return (byte) ((1 << oopTagsCount) - 1);
-    }
-
-    public byte oopTagsShift() {
-        return (byte) oopTagsCount;
-    }
-
-    public int oopCompressShift() {
-        return oopCompressShift;
-    }
-
-    public int oopReferenceSize() {
-        return oopReferenceSize;
-    }
-
-    public int pointerSize() {
-        return pointerSize;
-    }
-
-    public int oopAlignment() {
-        return oopAlignment;
-    }
-
-    public int oopAlignShift() {
-        return oopAlignShift;
-    }
-
-    public String getCachePath() {
-        return cachePath;
-    }
-
     public CompiledMethodEntry getCompiledMethod() {
-        return compiledMethod;
-    }
-
-    public Iterable<PrimitiveTypeEntry> getPrimitiveTypes() {
-        List<PrimitiveTypeEntry> primitiveTypes = new ArrayList<>();
-        for (TypeEntry typeEntry : types) {
-            if (typeEntry instanceof PrimitiveTypeEntry primitiveTypeEntry) {
-                primitiveTypes.add(primitiveTypeEntry);
-            }
-        }
-        return primitiveTypes;
+        return compiledMethods.getFirst();
     }
-
-
-    /*
-    private static void collectFilesAndDirs(TypedefEntry classEntry) {
-        // track files and dirs we have already seen so that we only add them once
-        EconomicSet<FileEntry> visitedFiles = EconomicSet.create();
-        EconomicSet<DirEntry> visitedDirs = EconomicSet.create();
-        // add the class's file and dir
-        includeOnce(classEntry, classEntry.getFileEntry(), visitedFiles, visitedDirs);
-        // add files for fields (may differ from class file if we have a substitution)
-        for (FieldEntry fieldEntry : classEntry.fields) {
-            includeOnce(classEntry, fieldEntry.getFileEntry(), visitedFiles, visitedDirs);
-        }
-        // add files for declared methods (may differ from class file if we have a substitution)
-        for (MethodEntry methodEntry : classEntry.getMethods()) {
-            includeOnce(classEntry, methodEntry.getFileEntry(), visitedFiles, visitedDirs);
-        }
-        // add files for top level compiled and inline methods
-        classEntry.compiledEntries().forEachOrdered(compiledMethodEntry -> {
-            includeOnce(classEntry, compiledMethodEntry.getPrimary().getFileEntry(), visitedFiles, visitedDirs);
-            // we need files for leaf ranges and for inline caller ranges
-            //
-            // add leaf range files first because they get searched for linearly
-            // during line info processing
-            compiledMethodEntry.leafRangeIterator().forEachRemaining(subRange -> {
-                includeOnce(classEntry, subRange.getFileEntry(), visitedFiles, visitedDirs);
-            });
-            // now the non-leaf range files
-            compiledMethodEntry.topDownRangeIterator().forEachRemaining(subRange -> {
-                if (!subRange.isLeaf()) {
-                    includeOnce(classEntry, subRange.getFileEntry(), visitedFiles, visitedDirs);
-                }
-            });
-        });
-        // now all files and dirs are known build an index for them
-        classEntry.buildFileAndDirIndexes();
-    }*/
-
-    /**
-     * Ensure the supplied file entry and associated directory entry are included, but only once, in
-     * a class entry's file and dir list.
-     *
-     * @param classEntry the class entry whose file and dir list may need to be updated
-     * @param fileEntry a file entry which may need to be added to the class entry's file list or
-     *            whose dir may need adding to the class entry's dir list
-     * @param visitedFiles a set tracking current file list entries, updated if a file is added
-     * @param visitedDirs a set tracking current dir list entries, updated if a dir is added
-     */
-    /*
-    private static void includeOnce(ClassEntry classEntry, FileEntry fileEntry, EconomicSet<FileEntry> visitedFiles, EconomicSet<DirEntry> visitedDirs) {
-        if (fileEntry != null && !visitedFiles.contains(fileEntry)) {
-            visitedFiles.add(fileEntry);
-            classEntry.includeFile(fileEntry);
-            DirEntry dirEntry = fileEntry.getDirEntry();
-            if (dirEntry != null && !dirEntry.getPathString().isEmpty() && !visitedDirs.contains(dirEntry)) {
-                visitedDirs.add(dirEntry);
-                classEntry.includeDir(dirEntry);
-            }
-        }
-    }*/
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/RuntimeDebugInfoProvider.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/RuntimeDebugInfoProvider.java
index 73b670a89b3c..effb4a2e5256 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/RuntimeDebugInfoProvider.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/RuntimeDebugInfoProvider.java
@@ -26,6 +26,7 @@
 
 package com.oracle.objectfile.runtime;
 
+import com.oracle.objectfile.debuginfo.DebugInfoProvider;
 import jdk.graal.compiler.debug.DebugContext;
 import jdk.vm.ci.meta.JavaConstant;
 import jdk.vm.ci.meta.JavaKind;
@@ -73,279 +74,9 @@ public interface RuntimeDebugInfoProvider {
 
     int compiledCodeMax();
 
-    /**
-     * An interface implemented by items that can be located in a file.
-     */
-    interface DebugFileInfo {
-        /**
-         * @return the name of the file containing a file element excluding any path.
-         */
-        String fileName();
-
-        /**
-         * @return a relative path to the file containing a file element derived from its package
-         * name or {@code null} if the element is in the empty package.
-         */
-        Path filePath();
-    }
-
-    interface DebugTypeInfo extends DebugFileInfo {
-        ResolvedJavaType idType();
-
-        enum DebugTypeKind {
-            PRIMITIVE,
-            TYPEDEF;
-
-            @Override
-            public String toString() {
-                return switch (this) {
-                    case PRIMITIVE -> "primitive";
-                    case TYPEDEF -> "typedef";
-                    default -> "???";
-                };
-            }
-        }
-
-        void debugContext(Consumer<DebugContext> action);
-
-        /**
-         * @return the fully qualified name of the debug type.
-         */
-        String typeName();
-
-        DebugTypeKind typeKind();
-
-        /**
-         * returns the offset in the heap at which the java.lang.Class instance which models this
-         * class is located or -1 if no such instance exists for this class.
-         *
-         * @return the offset of the java.lang.Class instance which models this class or -1.
-         */
-        long classOffset();
-
-        int size();
-    }
-
-    interface DebugTypedefInfo extends DebugTypeInfo {
-    }
-
-    interface DebugPrimitiveTypeInfo extends DebugTypeInfo {
-        /*
-         * NUMERIC excludes LOGICAL types boolean and void
-         */
-        int FLAG_NUMERIC = 1 << 0;
-        /*
-         * INTEGRAL excludes FLOATING types float and double
-         */
-        int FLAG_INTEGRAL = 1 << 1;
-        /*
-         * SIGNED excludes UNSIGNED type char
-         */
-        int FLAG_SIGNED = 1 << 2;
-
-        int bitCount();
-
-        char typeChar();
-
-        int flags();
-    }
-
-    interface DebugMemberInfo extends DebugFileInfo {
-
-        String name();
-
-        ResolvedJavaType valueType();
-
-        int modifiers();
-    }
-
-    interface DebugMethodInfo extends DebugMemberInfo {
-        /**
-         * @return the line number for the outer or inlined segment.
-         */
-        int line();
-
-        /**
-         * @return an array of DebugLocalInfo objects holding details of this method's parameters
-         */
-        List<DebugLocalInfo> getParamInfo();
-
-        /**
-         * @return a DebugLocalInfo objects holding details of the target instance parameter this if
-         * the method is an instance method or null if it is a static method.
-         */
-        DebugLocalInfo getThisParamInfo();
-
-        /**
-         * @return the symbolNameForMethod string
-         */
-        String symbolNameForMethod();
-
-        /**
-         * @return true if this method has been compiled in as a deoptimization target
-         */
-        boolean isDeoptTarget();
-
-        /**
-         * @return true if this method is a constructor.
-         */
-        boolean isConstructor();
-
-        /**
-         * @return true if this is a virtual method. In Graal a virtual method can become
-         * non-virtual if all other implementations are non-reachable.
-         */
-        boolean isVirtual();
-
-        /**
-         * @return the offset into the virtual function table for this method if virtual
-         */
-        int vtableOffset();
-
-        /**
-         * @return true if this method is an override of another method.
-         */
-        boolean isOverride();
-
-        /*
-         * Return the unique type that owns this method. <p/>
-         *
-         * @return the unique type that owns this method
-         */
-        ResolvedJavaType ownerType();
-
-        /*
-         * Return the unique identifier for this method. The result can be used to unify details of
-         * methods presented via interface DebugTypeInfo with related details of compiled methods
-         * presented via interface DebugRangeInfo and of call frame methods presented via interface
-         * DebugLocationInfo. <p/>
-         *
-         * @return the unique identifier for this method
-         */
-        ResolvedJavaMethod idMethod();
-    }
-
-    /**
-     * Access details of a compiled top level or inline method producing the code in a specific
-     * {@link com.oracle.objectfile.debugentry.range.Range}.
-     */
-    interface DebugRangeInfo extends DebugMethodInfo {
-
-        /**
-         * @return the lowest address containing code generated for an outer or inlined code segment
-         * reported at this line represented as an offset into the code segment.
-         */
-        long addressLo();
-
-        /**
-         * @return the first address above the code generated for an outer or inlined code segment
-         * reported at this line represented as an offset into the code segment.
-         */
-        long addressHi();
-    }
-
-    /**
-     * Access details of a specific compiled method.
-     */
-    interface DebugCodeInfo extends DebugRangeInfo {
-        void debugContext(Consumer<DebugContext> action);
-
-        /**
-         * @return a stream of records detailing source local var and line locations within the
-         * compiled method.
-         */
-        Iterable<DebugLocationInfo> locationInfoProvider();
-
-        /**
-         * @return the size of the method frame between prologue and epilogue.
-         */
-        int getFrameSize();
-
-        /**
-         * @return a list of positions at which the stack is extended to a full frame or torn down
-         * to an empty frame
-         */
-        List<DebugFrameSizeChange> getFrameSizeChanges();
-    }
-
-    /**
-     * Access details of code generated for a specific outer or inlined method at a given line
-     * number.
-     */
-    interface DebugLocationInfo extends DebugRangeInfo {
-        /**
-         * @return the {@link DebugLocationInfo} of the nested inline caller-line
-         */
-        DebugLocationInfo getCaller();
-
-        /**
-         * @return a stream of {@link DebugLocalValueInfo} objects identifying local or parameter
-         * variables present in the frame of the current range.
-         */
-        List<DebugLocalValueInfo> getLocalValueInfo();
-
-        boolean isLeaf();
-    }
-
-    /**
-     * A DebugLocalInfo details a local or parameter variable recording its name and type, the
-     * (abstract machine) local slot index it resides in and the number of slots it occupies.
-     */
-    interface DebugLocalInfo {
-        ResolvedJavaType valueType();
-
-        String name();
-
-        String typeName();
-
-        int slot();
-
-        int slotCount();
-
-        JavaKind javaKind();
-
-        int line();
-    }
-
-    /**
-     * A DebugLocalValueInfo details the value a local or parameter variable present in a specific
-     * frame. The value may be undefined. If not then the instance records its type and either its
-     * (constant) value or the register or stack location in which the value resides.
-     */
-    interface DebugLocalValueInfo extends DebugLocalInfo {
-        enum LocalKind {
-            UNDEFINED,
-            REGISTER,
-            STACKSLOT,
-            CONSTANT
-        }
-
-        LocalKind localKind();
-
-        int regIndex();
-
-        int stackSlot();
-
-        long heapOffset();
-
-        JavaConstant constantValue();
-    }
-
-    interface DebugFrameSizeChange {
-        enum Type {
-            EXTEND,
-            CONTRACT
-        }
-
-        int getOffset();
-
-        Type getType();
-    }
-
-    @SuppressWarnings("unused")
-    Iterable<DebugTypeInfo> typeInfoProvider();
+    DebugInfoProvider.DebugCodeInfo codeInfoProvider();
 
-    DebugCodeInfo codeInfoProvider();
+    DebugInfoProvider.DebugTypeInfo createDebugTypeInfo(ResolvedJavaType javaType);
 
     Path getCachePath();
 
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/CompiledMethodEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/CompiledMethodEntry.java
deleted file mode 100644
index a09ea7652f57..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/CompiledMethodEntry.java
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.debugentry;
-
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugFrameSizeChange;
-import com.oracle.objectfile.runtime.debugentry.range.PrimaryRange;
-import com.oracle.objectfile.runtime.debugentry.range.SubRange;
-
-import java.util.ArrayDeque;
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * Tracks debug info associated with a top level compiled method.
- */
-public class CompiledMethodEntry {
-    /**
-     * The primary range detailed by this object.
-     */
-    private final PrimaryRange primary;
-    /**
-     * Details of the class owning this range.
-     */
-    private final TypedefEntry typedefEntry;
-    /**
-     * Details of of compiled method frame size changes.
-     */
-    private final List<DebugFrameSizeChange> frameSizeInfos;
-    /**
-     * Size of compiled method frame.
-     */
-    private final int frameSize;
-
-    public CompiledMethodEntry(PrimaryRange primary, List<DebugFrameSizeChange> frameSizeInfos, int frameSize, TypedefEntry typedefEntry) {
-        this.primary = primary;
-        this.typedefEntry = typedefEntry;
-        this.frameSizeInfos = frameSizeInfos;
-        this.frameSize = frameSize;
-    }
-
-    public PrimaryRange getPrimary() {
-        return primary;
-    }
-
-    public TypedefEntry getTypedefEntry() {
-        return typedefEntry;
-    }
-
-    /**
-     * Returns an iterator that traverses all the callees of the method associated with this entry.
-     * The iterator performs a depth-first pre-order traversal of the call tree.
-     *
-     * @return the iterator
-     */
-    public Iterator<SubRange> topDownRangeIterator() {
-        return new Iterator<>() {
-            final ArrayDeque<SubRange> workStack = new ArrayDeque<>();
-            SubRange current = primary.getFirstCallee();
-
-            @Override
-            public boolean hasNext() {
-                return current != null;
-            }
-
-            @Override
-            public SubRange next() {
-                assert hasNext();
-                SubRange result = current;
-                forward();
-                return result;
-            }
-
-            private void forward() {
-                SubRange sibling = current.getSiblingCallee();
-                assert sibling == null || (current.getHi() <= sibling.getLo()) : current.getHi() + " > " + sibling.getLo();
-                if (!current.isLeaf()) {
-                    /* save next sibling while we process the children */
-                    if (sibling != null) {
-                        workStack.push(sibling);
-                    }
-                    current = current.getFirstCallee();
-                } else if (sibling != null) {
-                    current = sibling;
-                } else {
-                    /*
-                     * Return back up to parents' siblings, use pollFirst instead of pop to return
-                     * null in case the work stack is empty
-                     */
-                    current = workStack.pollFirst();
-                }
-            }
-        };
-    }
-
-    /**
-     * Returns an iterator that traverses the callees of the method associated with this entry and
-     * returns only the leafs. The iterator performs a depth-first pre-order traversal of the call
-     * tree returning only ranges with no callees.
-     *
-     * @return the iterator
-     */
-    public Iterator<SubRange> leafRangeIterator() {
-        final Iterator<SubRange> iter = topDownRangeIterator();
-        return new Iterator<>() {
-            SubRange current = forwardLeaf(iter);
-
-            @Override
-            public boolean hasNext() {
-                return current != null;
-            }
-
-            @Override
-            public SubRange next() {
-                assert hasNext();
-                SubRange result = current;
-                current = forwardLeaf(iter);
-                return result;
-            }
-
-            private SubRange forwardLeaf(Iterator<SubRange> t) {
-                if (t.hasNext()) {
-                    SubRange next = t.next();
-                    while (next != null && !next.isLeaf()) {
-                        next = t.next();
-                    }
-                    return next;
-                }
-                return null;
-            }
-        };
-    }
-
-    public List<DebugFrameSizeChange> getFrameSizeInfos() {
-        return frameSizeInfos;
-    }
-
-    public int getFrameSize() {
-        return frameSize;
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/DirEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/DirEntry.java
deleted file mode 100644
index dbdc79566d1b..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/DirEntry.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.debugentry;
-
-import java.nio.file.Path;
-
-/**
- * Tracks the directory associated with one or more source files.
- *
- * This is identified separately from each FileEntry identifying files that reside in the directory.
- * That is necessary because the line info generator needs to collect and write out directory names
- * into directory tables once only rather than once per file.
- */
-public class DirEntry {
-    private final Path path;
-
-    public DirEntry(Path path) {
-        this.path = path;
-    }
-
-    public Path getPath() {
-        return path;
-    }
-
-    public String getPathString() {
-        return path.toString();
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/FileEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/FileEntry.java
deleted file mode 100644
index 65e948ea9988..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/FileEntry.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.debugentry;
-
-/**
- * Tracks debug info associated with a Java source file.
- */
-public class FileEntry {
-    private final String fileName;
-    private final DirEntry dirEntry;
-
-    public FileEntry(String fileName, DirEntry dirEntry) {
-        this.fileName = fileName;
-        this.dirEntry = dirEntry;
-    }
-
-    /**
-     * The name of the associated file excluding path elements.
-     */
-    public String getFileName() {
-        return fileName;
-    }
-
-    public String getPathName() {
-        @SuppressWarnings("hiding")
-        DirEntry dirEntry = getDirEntry();
-        if (dirEntry == null) {
-            return "";
-        } else {
-            return dirEntry.getPathString();
-        }
-    }
-
-    public String getFullName() {
-        if (dirEntry == null) {
-            return fileName;
-        } else {
-            return dirEntry.getPath().resolve(getFileName()).toString();
-        }
-    }
-
-    /**
-     * The directory entry associated with this file entry.
-     */
-    public DirEntry getDirEntry() {
-        return dirEntry;
-    }
-
-    @Override
-    public String toString() {
-        if (getDirEntry() == null) {
-            return getFileName() == null ? "-" : getFileName();
-        } else if (getFileName() == null) {
-            return "--";
-        }
-        return String.format("FileEntry(%s)", getFullName());
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/MemberEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/MemberEntry.java
deleted file mode 100644
index cc3083bafcc0..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/MemberEntry.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.debugentry;
-
-/**
- * An abstract class providing modelling a generic class member which includes behaviour and data
- * shared by both field and method entries.
- */
-public abstract class MemberEntry {
-    protected FileEntry fileEntry;
-    protected final int line;
-    protected final String memberName;
-    protected final StructureTypeEntry ownerType;
-    protected final TypeEntry valueType;
-    protected final int modifiers;
-
-    public MemberEntry(FileEntry fileEntry, String memberName, StructureTypeEntry ownerType, TypeEntry valueType, int modifiers) {
-        this(fileEntry, 0, memberName, ownerType, valueType, modifiers);
-    }
-
-    public MemberEntry(FileEntry fileEntry, int line, String memberName, StructureTypeEntry ownerType, TypeEntry valueType, int modifiers) {
-        assert line >= 0;
-        this.fileEntry = fileEntry;
-        this.line = line;
-        this.memberName = memberName;
-        this.ownerType = ownerType;
-        this.valueType = valueType;
-        this.modifiers = modifiers;
-    }
-
-    public String getFileName() {
-        if (fileEntry != null) {
-            return fileEntry.getFileName();
-        } else {
-            return "";
-        }
-    }
-
-    public String getFullFileName() {
-        if (fileEntry != null) {
-            return fileEntry.getFullName();
-        } else {
-            return null;
-        }
-    }
-
-    @SuppressWarnings("unused")
-    String getDirName() {
-        if (fileEntry != null) {
-            return fileEntry.getPathName();
-        } else {
-            return "";
-        }
-    }
-
-    public FileEntry getFileEntry() {
-        return fileEntry;
-    }
-
-    public int getFileIdx() {
-        if (ownerType instanceof TypedefEntry) {
-            return ((TypedefEntry) ownerType).getFileIdx(fileEntry);
-        }
-        // should not be asking for a file for header fields
-        assert false : "not expecting a file lookup for header fields";
-        return 1;
-    }
-
-    public int getLine() {
-        return line;
-    }
-
-    public StructureTypeEntry ownerType() {
-        return ownerType;
-    }
-
-    public TypeEntry getValueType() {
-        return valueType;
-    }
-
-    public int getModifiers() {
-        return modifiers;
-    }
-
-    public String getModifiersString() {
-        return ownerType.memberModifiers(modifiers);
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/MethodEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/MethodEntry.java
deleted file mode 100644
index 8f76da24a142..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/MethodEntry.java
+++ /dev/null
@@ -1,380 +0,0 @@
-/*
- * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.debugentry;
-
-import com.oracle.objectfile.runtime.RuntimeDebugInfoBase;
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugCodeInfo;
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocalInfo;
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocalValueInfo;
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocationInfo;
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugMethodInfo;
-import jdk.vm.ci.meta.JavaKind;
-import jdk.vm.ci.meta.ResolvedJavaType;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.ListIterator;
-
-public class MethodEntry extends MemberEntry {
-    private final TypeEntry[] paramTypes;
-    private final DebugLocalInfo thisParam;
-    private final List<DebugLocalInfo> paramInfos;
-    private final int firstLocalSlot;
-    // local vars are accumulated as they are referenced sorted by slot, then name, then
-    // type name. we don't currently deal handle references to locals with no slot.
-    private final ArrayList<DebugLocalInfo> locals;
-    static final int DEOPT = 1 << 0;
-    static final int IN_RANGE = 1 << 1;
-    static final int INLINED = 1 << 2;
-    static final int IS_OVERRIDE = 1 << 3;
-    static final int IS_CONSTRUCTOR = 1 << 4;
-    private int flags;
-    private final int vtableOffset;
-    private final String symbolName;
-
-    @SuppressWarnings("this-escape")
-    public MethodEntry(RuntimeDebugInfoBase debugInfoBase, DebugMethodInfo debugMethodInfo,
-                       FileEntry fileEntry, int line, String methodName, TypedefEntry ownerType,
-                       TypeEntry valueType, TypeEntry[] paramTypes, List<DebugLocalInfo> paramInfos, DebugLocalInfo thisParam) {
-        super(fileEntry, line, methodName, ownerType, valueType, debugMethodInfo.modifiers());
-        this.paramTypes = paramTypes;
-        this.paramInfos = paramInfos;
-        this.thisParam = thisParam;
-        this.symbolName = debugMethodInfo.symbolNameForMethod();
-        this.flags = 0;
-        if (debugMethodInfo.isDeoptTarget()) {
-            setIsDeopt();
-        }
-        if (debugMethodInfo.isConstructor()) {
-            setIsConstructor();
-        }
-        if (debugMethodInfo.isOverride()) {
-            setIsOverride();
-        }
-        vtableOffset = debugMethodInfo.vtableOffset();
-        int paramCount = paramInfos.size();
-        if (paramCount > 0) {
-            DebugLocalInfo lastParam = paramInfos.get(paramCount - 1);
-            firstLocalSlot = lastParam.slot() + lastParam.slotCount();
-        } else {
-            firstLocalSlot = (thisParam == null ? 0 : thisParam.slotCount());
-        }
-        locals = new ArrayList<>();
-        updateRangeInfo(debugInfoBase, debugMethodInfo);
-    }
-
-    public String methodName() {
-        return memberName;
-    }
-
-    @Override
-    public TypedefEntry ownerType() {
-        assert ownerType instanceof TypedefEntry;
-        return (TypedefEntry) ownerType;
-    }
-
-    public int getParamCount() {
-        return paramInfos.size();
-    }
-
-    public TypeEntry getParamType(int idx) {
-        assert idx < paramInfos.size();
-        return paramTypes[idx];
-    }
-
-    public TypeEntry[] getParamTypes() {
-        return paramTypes;
-    }
-
-    public String getParamTypeName(int idx) {
-        assert idx < paramTypes.length;
-        return paramTypes[idx].getTypeName();
-    }
-
-    public String getParamName(int idx) {
-        assert idx < paramInfos.size();
-        /* N.b. param names may be null. */
-        return paramInfos.get(idx).name();
-    }
-
-    public int getParamLine(int idx) {
-        assert idx < paramInfos.size();
-        /* N.b. param names may be null. */
-        return paramInfos.get(idx).line();
-    }
-
-    public DebugLocalInfo getParam(int i) {
-        assert i >= 0 && i < paramInfos.size() : "bad param index";
-        return paramInfos.get(i);
-    }
-
-    public DebugLocalInfo getThisParam() {
-        return thisParam;
-    }
-
-    public int getLocalCount() {
-        return locals.size();
-    }
-
-    public DebugLocalInfo getLocal(int i) {
-        assert i >= 0 && i < locals.size() : "bad param index";
-        return locals.get(i);
-    }
-
-    private void setIsDeopt() {
-        flags |= DEOPT;
-    }
-
-    public boolean isDeopt() {
-        return (flags & DEOPT) != 0;
-    }
-
-    private void setIsInRange() {
-        flags |= IN_RANGE;
-    }
-
-    public boolean isInRange() {
-        return (flags & IN_RANGE) != 0;
-    }
-
-    private void setIsInlined() {
-        flags |= INLINED;
-    }
-
-    public boolean isInlined() {
-        return (flags & INLINED) != 0;
-    }
-
-    private void setIsOverride() {
-        flags |= IS_OVERRIDE;
-    }
-
-    public boolean isOverride() {
-        return (flags & IS_OVERRIDE) != 0;
-    }
-
-    private void setIsConstructor() {
-        flags |= IS_CONSTRUCTOR;
-    }
-
-    public boolean isConstructor() {
-        return (flags & IS_CONSTRUCTOR) != 0;
-    }
-
-    /**
-     * Sets {@code isInRange} and ensures that the {@code fileEntry} is up to date. If the
-     * MethodEntry was added by traversing the DeclaredMethods of a Class its fileEntry will point
-     * to the original source file, thus it will be wrong for substituted methods. As a result when
-     * setting a MethodEntry as isInRange we also make sure that its fileEntry reflects the file
-     * info associated with the corresponding Range.
-     *
-     * @param debugInfoBase
-     * @param debugMethodInfo
-     */
-    public void updateRangeInfo(RuntimeDebugInfoBase debugInfoBase, DebugMethodInfo debugMethodInfo) {
-        if (debugMethodInfo instanceof DebugLocationInfo) {
-            DebugLocationInfo locationInfo = (DebugLocationInfo) debugMethodInfo;
-            if (locationInfo.getCaller() != null) {
-                /* this is a real inlined method */
-                setIsInlined();
-            }
-        } else if (debugMethodInfo instanceof DebugCodeInfo) {
-            /* this method is being notified as a top level compiled method */
-            if (isInRange()) {
-                /* it has already been seen -- just check for consistency */
-                assert fileEntry == debugInfoBase.ensureFileEntry(debugMethodInfo);
-            } else {
-                /*
-                 * If the MethodEntry was added by traversing the DeclaredMethods of a Class its
-                 * fileEntry may point to the original source file, which will be wrong for
-                 * substituted methods. As a result when setting a MethodEntry as isInRange we also
-                 * make sure that its fileEntry reflects the file info associated with the
-                 * corresponding Range.
-                 */
-                setIsInRange();
-                fileEntry = debugInfoBase.ensureFileEntry(debugMethodInfo);
-            }
-        }
-    }
-
-    public boolean isVirtual() {
-        return vtableOffset >= 0;
-    }
-
-    public int getVtableOffset() {
-        return vtableOffset;
-    }
-
-    public String getSymbolName() {
-        return symbolName;
-    }
-
-    /**
-     * Return a unique local or parameter variable associated with the value, optionally recording
-     * it as a new local variable or fail, returning null, when the local value does not conform
-     * with existing recorded parameter or local variables. Values with invalid (negative) slots
-     * always fail. Values whose slot is associated with a parameter only conform if their name and
-     * type equal those of the parameter. Values whose slot is in the local range will always
-     * succeed,. either by matchign the slot and name of an existing local or by being recorded as a
-     * new local variable.
-     * 
-     * @param localValueInfo
-     * @return the unique local variable with which this local value can be legitimately associated
-     *         otherwise null.
-     */
-    public DebugLocalInfo recordLocal(DebugLocalValueInfo localValueInfo) {
-        int slot = localValueInfo.slot();
-        if (slot < 0) {
-            return null;
-        } else {
-            if (slot < firstLocalSlot) {
-                return matchParam(localValueInfo);
-            } else {
-                return matchLocal(localValueInfo);
-            }
-        }
-    }
-
-    private DebugLocalInfo matchParam(DebugLocalValueInfo localValueInfo) {
-        if (thisParam != null) {
-            if (checkMatch(thisParam, localValueInfo)) {
-                return thisParam;
-            }
-        }
-        for (int i = 0; i < paramInfos.size(); i++) {
-            DebugLocalInfo paramInfo = paramInfos.get(i);
-            if (checkMatch(paramInfo, localValueInfo)) {
-                return paramInfo;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * wrapper class for a local value that stands in as a unique identifier for the associated
-     * local variable while allowing its line to be adjusted when earlier occurrences of the same
-     * local are identified.
-     */
-    private static class DebugLocalInfoWrapper implements DebugLocalInfo {
-        DebugLocalValueInfo value;
-        int line;
-
-        DebugLocalInfoWrapper(DebugLocalValueInfo value) {
-            this.value = value;
-            this.line = value.line();
-        }
-
-        @Override
-        public ResolvedJavaType valueType() {
-            return value.valueType();
-        }
-
-        @Override
-        public String name() {
-            return value.name();
-        }
-
-        @Override
-        public String typeName() {
-            return value.typeName();
-        }
-
-        @Override
-        public int slot() {
-            return value.slot();
-        }
-
-        @Override
-        public int slotCount() {
-            return value.slotCount();
-        }
-
-        @Override
-        public JavaKind javaKind() {
-            return value.javaKind();
-        }
-
-        @Override
-        public int line() {
-            return line;
-        }
-
-        public void setLine(int line) {
-            this.line = line;
-        }
-    }
-
-    private DebugLocalInfo matchLocal(DebugLocalValueInfo localValueInfo) {
-        ListIterator<DebugLocalInfo> listIterator = locals.listIterator();
-        while (listIterator.hasNext()) {
-            DebugLocalInfoWrapper next = (DebugLocalInfoWrapper) listIterator.next();
-            if (checkMatch(next, localValueInfo)) {
-                int currentLine = next.line();
-                int newLine = localValueInfo.line();
-                if ((currentLine < 0 && newLine >= 0) ||
-                                (newLine >= 0 && newLine < currentLine)) {
-                    next.setLine(newLine);
-                }
-                return next;
-            } else if (next.slot() > localValueInfo.slot()) {
-                // we have iterated just beyond the insertion point
-                // so wind cursor back one element
-                listIterator.previous();
-                break;
-            }
-        }
-        DebugLocalInfoWrapper newLocal = new DebugLocalInfoWrapper(localValueInfo);
-        // add at the current cursor position
-        listIterator.add(newLocal);
-        return newLocal;
-    }
-
-    boolean checkMatch(DebugLocalInfo local, DebugLocalValueInfo value) {
-        boolean isMatch = (local.slot() == value.slot() &&
-                        local.name().equals(value.name()) &&
-                        local.typeName().equals(value.typeName()));
-        assert !isMatch || verifyMatch(local, value) : "failed to verify matched var and value";
-        return isMatch;
-    }
-
-    private static boolean verifyMatch(DebugLocalInfo local, DebugLocalValueInfo value) {
-        // slot counts are normally expected to match
-        if (local.slotCount() == value.slotCount()) {
-            return true;
-        }
-        // we can have a zero count for the local or value if it is undefined
-        if (local.slotCount() == 0 || value.slotCount() == 0) {
-            return true;
-        }
-        // pseudo-object locals can appear as longs
-        if (local.javaKind() == JavaKind.Object && value.javaKind() == JavaKind.Long) {
-            return true;
-        }
-        // something is wrong
-        return false;
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/PrimitiveTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/PrimitiveTypeEntry.java
deleted file mode 100644
index a630e5b0bdac..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/PrimitiveTypeEntry.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.debugentry;
-
-import com.oracle.objectfile.runtime.RuntimeDebugInfoBase;
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugPrimitiveTypeInfo;
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugTypeInfo;
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugTypeInfo.DebugTypeKind;
-import jdk.graal.compiler.debug.DebugContext;
-
-import static com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugPrimitiveTypeInfo.FLAG_INTEGRAL;
-import static com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugPrimitiveTypeInfo.FLAG_NUMERIC;
-import static com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugPrimitiveTypeInfo.FLAG_SIGNED;
-
-public class PrimitiveTypeEntry extends TypeEntry {
-    private char typeChar;
-    private int flags;
-    private int bitCount;
-
-    public PrimitiveTypeEntry(String typeName, int size) {
-        super(typeName, size);
-        typeChar = '#';
-        flags = 0;
-        bitCount = 0;
-    }
-
-    @Override
-    public DebugTypeKind typeKind() {
-        return DebugTypeKind.PRIMITIVE;
-    }
-
-    @Override
-    public void addDebugInfo(RuntimeDebugInfoBase debugInfoBase, DebugTypeInfo debugTypeInfo, DebugContext debugContext) {
-        super.addDebugInfo(debugInfoBase, debugTypeInfo, debugContext);
-        DebugPrimitiveTypeInfo debugPrimitiveTypeInfo = (DebugPrimitiveTypeInfo) debugTypeInfo;
-        flags = debugPrimitiveTypeInfo.flags();
-        typeChar = debugPrimitiveTypeInfo.typeChar();
-        bitCount = debugPrimitiveTypeInfo.bitCount();
-        if (debugContext.isLogEnabled()) {
-            debugContext.log("typename %s %s (%d bits)%n", typeName, decodeFlags(), bitCount);
-        }
-    }
-
-    private String decodeFlags() {
-        StringBuilder builder = new StringBuilder();
-        if ((flags & FLAG_NUMERIC) != 0) {
-            if ((flags & FLAG_INTEGRAL) != 0) {
-                if ((flags & FLAG_SIGNED) != 0) {
-                    builder.append("SIGNED ");
-                } else {
-                    builder.append("UNSIGNED ");
-                }
-                builder.append("INTEGRAL");
-            } else {
-                builder.append("FLOATING");
-            }
-        } else {
-            if (bitCount > 0) {
-                builder.append("LOGICAL");
-            } else {
-                builder.append("VOID");
-            }
-        }
-        return builder.toString();
-    }
-
-    public char getTypeChar() {
-        return typeChar;
-    }
-
-    public int getBitCount() {
-        return bitCount;
-    }
-
-    public int getFlags() {
-        return flags;
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/StringEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/StringEntry.java
deleted file mode 100644
index 1aa804a54536..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/StringEntry.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.debugentry;
-
-/**
- * Used to retain a unique (up to equals) copy of a String. Also flags whether the String needs to
- * be located in the debug_string section and, if so, tracks the offset at which it gets written.
- */
-public class StringEntry {
-    private final String string;
-    private int offset;
-    private boolean addToStrSection;
-
-    StringEntry(String string) {
-        this.string = string;
-        this.offset = -1;
-    }
-
-    public String getString() {
-        return string;
-    }
-
-    public int getOffset() {
-        assert offset >= -1;
-        return offset;
-    }
-
-    public void setOffset(int offset) {
-        assert this.offset < 0;
-        assert offset >= 0;
-        this.offset = offset;
-    }
-
-    public boolean isAddToStrSection() {
-        return addToStrSection;
-    }
-
-    public void setAddToStrSection() {
-        this.addToStrSection = true;
-    }
-
-    @Override
-    public boolean equals(Object object) {
-        if (object == null || !(object instanceof StringEntry)) {
-            return false;
-        } else {
-            StringEntry other = (StringEntry) object;
-            return this == other || string.equals(other.string);
-        }
-    }
-
-    @Override
-    public int hashCode() {
-        return string.hashCode() + 37;
-    }
-
-    @Override
-    public String toString() {
-        return string;
-    }
-
-    public boolean isEmpty() {
-        return string.length() == 0;
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/StringTable.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/StringTable.java
deleted file mode 100644
index 975b9f4565ae..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/StringTable.java
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.debugentry;
-
-import java.util.HashMap;
-import java.util.Iterator;
-
-/**
- * Allows incoming strings to be reduced to unique (up to equals) instances and supports marking of
- * strings which need to be written to the debug_str section and retrieval of the location offset
- * after writing.
- */
-public class StringTable implements Iterable<StringEntry> {
-
-    private final HashMap<String, StringEntry> table;
-
-    public StringTable() {
-        this.table = new HashMap<>();
-    }
-
-    /**
-     * Ensures a unique instance of a string exists in the table, inserting the supplied String if
-     * no equivalent String is already present. This should only be called before the string section
-     * has been written.
-     *
-     * @param string the string to be included in the table
-     * @return the unique instance of the String
-     */
-    public String uniqueString(String string) {
-        return ensureString(string, false);
-    }
-
-    /**
-     * Ensures a unique instance of a string exists in the table and is marked for inclusion in the
-     * debug_str section, inserting the supplied String if no equivalent String is already present.
-     * This should only be called before the string section has been written.
-     *
-     * @param string the string to be included in the table and marked for inclusion in the
-     *            debug_str section
-     * @return the unique instance of the String
-     */
-    public String uniqueDebugString(String string) {
-        return ensureString(string, true);
-    }
-
-    private String ensureString(String string, boolean addToStrSection) {
-        StringEntry stringEntry = table.get(string);
-        if (stringEntry == null) {
-            stringEntry = new StringEntry(string);
-            table.put(string, stringEntry);
-        }
-        if (addToStrSection && !stringEntry.isAddToStrSection()) {
-            stringEntry.setAddToStrSection();
-        }
-        return stringEntry.getString();
-    }
-
-    /**
-     * Retrieves the offset at which a given string was written into the debug_str section. This
-     * should only be called after the string section has been written.
-     *
-     * @param string the string whose offset is to be retrieved
-     * @return the offset or -1 if the string does not define an entry or the entry has not been
-     *         written to the debug_str section
-     */
-    public int debugStringIndex(String string) {
-        StringEntry stringEntry = table.get(string);
-        assert stringEntry != null : "\"" + string + "\" not in string table";
-        if (stringEntry == null) {
-            return -1;
-        }
-        return stringEntry.getOffset();
-    }
-
-    @Override
-    public Iterator<StringEntry> iterator() {
-        return table.values().iterator();
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/StructureTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/StructureTypeEntry.java
deleted file mode 100644
index a2d8a7785a73..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/StructureTypeEntry.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.debugentry;
-
-import java.lang.reflect.Modifier;
-
-/**
- * An intermediate type that provides behaviour for managing fields. This unifies code for handling
- * header structures and Java instance and array classes that both support data members.
- */
-public abstract class StructureTypeEntry extends TypeEntry {
-    public StructureTypeEntry(String typeName, int size) {
-        super(typeName, size);
-    }
-
-    String memberModifiers(int modifiers) {
-        StringBuilder builder = new StringBuilder();
-        if (Modifier.isPublic(modifiers)) {
-            builder.append("public ");
-        } else if (Modifier.isProtected(modifiers)) {
-            builder.append("protected ");
-        } else if (Modifier.isPrivate(modifiers)) {
-            builder.append("private ");
-        }
-        if (Modifier.isFinal(modifiers)) {
-            builder.append("final ");
-        }
-        if (Modifier.isAbstract(modifiers)) {
-            builder.append("abstract ");
-        } else if (Modifier.isVolatile(modifiers)) {
-            builder.append("volatile ");
-        } else if (Modifier.isTransient(modifiers)) {
-            builder.append("transient ");
-        } else if (Modifier.isSynchronized(modifiers)) {
-            builder.append("synchronized ");
-        }
-        if (Modifier.isNative(modifiers)) {
-            builder.append("native ");
-        }
-        if (Modifier.isStatic(modifiers)) {
-            builder.append("static");
-        } else {
-            builder.append("instance");
-        }
-
-        return builder.toString();
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/TypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/TypeEntry.java
deleted file mode 100644
index d06def728554..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/TypeEntry.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.debugentry;
-
-import com.oracle.objectfile.runtime.RuntimeDebugInfoBase;
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugTypeInfo;
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugTypeInfo.DebugTypeKind;
-import jdk.graal.compiler.debug.DebugContext;
-
-import static com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugTypeInfo.DebugTypeKind.PRIMITIVE;
-import static com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugTypeInfo.DebugTypeKind.TYPEDEF;
-
-public abstract class TypeEntry {
-    /**
-     * The name of this type.
-     */
-    protected final String typeName;
-
-    /**
-     * The offset of the java.lang.Class instance for this class in the image heap or -1 if no such
-     * object exists.
-     */
-    private long classOffset;
-
-    /**
-     * The size of an occurrence of this type in bytes.
-     */
-    protected final int size;
-
-    protected TypeEntry(String typeName, int size) {
-        this.typeName = typeName;
-        this.size = size;
-        this.classOffset = -1;
-    }
-
-    public long getClassOffset() {
-        return classOffset;
-    }
-
-    public int getSize() {
-        return size;
-    }
-
-    public String getTypeName() {
-        return typeName;
-    }
-
-    public abstract DebugTypeKind typeKind();
-
-    public boolean isPrimitive() {
-        return typeKind() == PRIMITIVE;
-    }
-
-    public boolean isTypedef() {
-        return typeKind() == TYPEDEF;
-    }
-
-    /**
-     * Test whether this entry is a class type, either an instance class, an interface type, an enum
-     * type or a foreign type. The test excludes primitive and array types and the header type.
-     *
-     * n.b. Foreign types are considered to be class types because they appear like interfaces or
-     * classes in the Java source and hence need to be modeled by a ClassEntry which can track
-     * properties of the java type. This also allows them to be decorated with properties that
-     * record details of the generated debug info.
-     *
-     * @return true if this entry is a class type otherwise false.
-     */
-    public boolean isClass() {
-        return isTypedef();
-    }
-
-    public boolean isStructure() {
-        return isTypedef();
-    }
-
-    public void addDebugInfo(@SuppressWarnings("unused") RuntimeDebugInfoBase debugInfoBase, DebugTypeInfo debugTypeInfo, @SuppressWarnings("unused") DebugContext debugContext) {
-        /* Record the location of the Class instance in the heap if there is one */
-        this.classOffset = debugTypeInfo.classOffset();
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/TypedefEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/TypedefEntry.java
deleted file mode 100644
index 8ea0da2ab5c5..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/TypedefEntry.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.oracle.objectfile.runtime.debugentry;
-
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugTypeInfo.DebugTypeKind;
-
-public class TypedefEntry extends StructureTypeEntry {
-    public TypedefEntry(String typeName, int size) {
-        super(typeName, size);
-    }
-
-    @Override
-    public DebugTypeKind typeKind() {
-        return DebugTypeKind.TYPEDEF;
-    }
-
-    public int getFileIdx(FileEntry fileEntry) {
-        return 0;
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/CallRange.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/CallRange.java
deleted file mode 100644
index 3c94523b41f1..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/CallRange.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2022, 2022, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.debugentry.range;
-
-import com.oracle.objectfile.runtime.debugentry.MethodEntry;
-
-class CallRange extends SubRange {
-    /**
-     * The first direct callee whose range is wholly contained in this range or null if this is a
-     * leaf range.
-     */
-    protected SubRange firstCallee;
-    /**
-     * The last direct callee whose range is wholly contained in this range.
-     */
-    protected SubRange lastCallee;
-
-    protected CallRange(MethodEntry methodEntry, long lo, long hi, int line, PrimaryRange primary, Range caller) {
-        super(methodEntry, lo, hi, line, primary, caller);
-        this.firstCallee = null;
-        this.lastCallee = null;
-    }
-
-    @Override
-    protected void addCallee(SubRange callee) {
-        assert this.lo <= callee.lo;
-        assert this.hi >= callee.hi;
-        assert callee.caller == this;
-        assert callee.siblingCallee == null;
-        if (this.firstCallee == null) {
-            assert this.lastCallee == null;
-            this.firstCallee = this.lastCallee = callee;
-        } else {
-            this.lastCallee.siblingCallee = callee;
-            this.lastCallee = callee;
-        }
-    }
-
-    @Override
-    public SubRange getFirstCallee() {
-        return firstCallee;
-    }
-
-    @Override
-    public boolean isLeaf() {
-        assert firstCallee != null;
-        return false;
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/LeafRange.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/LeafRange.java
deleted file mode 100644
index 863d4b8e5541..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/LeafRange.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2022, 2022, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.debugentry.range;
-
-import com.oracle.objectfile.runtime.debugentry.MethodEntry;
-
-class LeafRange extends SubRange {
-    protected LeafRange(MethodEntry methodEntry, long lo, long hi, int line, PrimaryRange primary, Range caller) {
-        super(methodEntry, lo, hi, line, primary, caller);
-    }
-
-    @Override
-    protected void addCallee(SubRange callee) {
-        assert false : "should never be adding callees to a leaf range!";
-    }
-
-    @Override
-    public SubRange getFirstCallee() {
-        return null;
-    }
-
-    @Override
-    public boolean isLeaf() {
-        return true;
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/PrimaryRange.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/PrimaryRange.java
deleted file mode 100644
index ff847d8a0f89..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/PrimaryRange.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2022, 2022, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.debugentry.range;
-
-import com.oracle.objectfile.runtime.debugentry.MethodEntry;
-
-public class PrimaryRange extends Range {
-    /**
-     * The first subrange in the range covered by this primary or null if this primary as no
-     * subranges.
-     */
-    protected SubRange firstCallee;
-    /**
-     * The last subrange in the range covered by this primary.
-     */
-    protected SubRange lastCallee;
-
-    protected PrimaryRange(MethodEntry methodEntry, long lo, long hi, int line) {
-        super(methodEntry, lo, hi, line, -1);
-        this.firstCallee = null;
-        this.lastCallee = null;
-    }
-
-    @Override
-    public boolean isPrimary() {
-        return true;
-    }
-
-    @Override
-    protected void addCallee(SubRange callee) {
-        assert this.lo <= callee.lo;
-        assert this.hi >= callee.hi;
-        assert callee.caller == this;
-        assert callee.siblingCallee == null;
-        if (this.firstCallee == null) {
-            assert this.lastCallee == null;
-            this.firstCallee = this.lastCallee = callee;
-        } else {
-            this.lastCallee.siblingCallee = callee;
-            this.lastCallee = callee;
-        }
-    }
-
-    @Override
-    public SubRange getFirstCallee() {
-        return firstCallee;
-    }
-
-    @Override
-    public boolean isLeaf() {
-        return firstCallee == null;
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/Range.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/Range.java
deleted file mode 100644
index cd02ea163312..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/Range.java
+++ /dev/null
@@ -1,301 +0,0 @@
-/*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.debugentry.range;
-
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocalInfo;
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocalValueInfo;
-import com.oracle.objectfile.runtime.debugentry.FileEntry;
-import com.oracle.objectfile.runtime.debugentry.MethodEntry;
-import com.oracle.objectfile.runtime.debugentry.TypeEntry;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-
-/**
- * Details of a specific address range in a compiled method either a primary range identifying a
- * whole compiled method or a sub-range identifying a sub-sequence of the compiled instructions that
- * may derive from top level or inlined code. Each sub-range is linked with its caller, (which may
- * be the primary range) and its callees, forming a call tree. Subranges are either leaf nodes with
- * no children or call nodes which have children.
- *
- * <ul>
- * <li>A leaf node at the top level (depth 0) records the start and extent of a sequence of compiled
- * code derived from the top level method. The leaf node reports itself as belonging to the top
- * level method.
- * <li>A leaf node at depth N records the start and extent of a sequence of compiled code derived
- * from a leaf inlined method at a call depth of N. The leaf node reports itself as belonging to the
- * leaf method.
- * <li>A call node at level 0 records the start and extent of a sequence of compiled code that
- * includes all compiled code derived from a top level call that has been inlined. All child nodes
- * of the call node (direct or indirect) should model ranges that lie within the parent range. The
- * call node reports itself as belonging to the top level method and its file and line information
- * identify the location of the call.
- * <li>A call node at level N records the start and extent of a sequence of compiled code that
- * includes all compiled code derived from an inline call at depth N. All child nodes of the call
- * node (direct or indirect) should model ranges that lie within the parent range. The call node
- * reports itself as belonging to the caller method at depth N and its file and line information
- * identify the location of the call.
- * <ul>
- *
- * Ranges also record the location of local and parameter values that are valid for the range's
- * extent. Each value maps to a corresponding parameter or local variable attached to the range's
- * method. So, a leaf or call node at level 0 records local and parameter values for separate
- * sub-extents of the top level method while a leaf or call node at level N+1 records local and
- * parameter values for separate sub-extents of an inline called method whose full extent is
- * represented by the parent call range at level N.
- */
-public abstract class Range {
-    private static final String CLASS_DELIMITER = ".";
-    protected final MethodEntry methodEntry;
-    protected final long lo;
-    protected long hi;
-    protected final int line;
-    protected final int depth;
-
-    /**
-     * Create a primary range representing the root of the subrange tree for a top level compiled
-     * method.
-     * 
-     * @param methodEntry the top level compiled method for this primary range.
-     * @param lo the lowest address included in the range.
-     * @param hi the first address above the highest address in the range.
-     * @param line the line number associated with the range
-     * @return a new primary range to serve as the root of the subrange tree.
-     */
-    public static PrimaryRange createPrimary(MethodEntry methodEntry, long lo, long hi, int line) {
-        return new PrimaryRange(methodEntry, lo, hi, line);
-    }
-
-    /**
-     * Create a subrange representing a segment of the address range for code of a top level or
-     * inlined compiled method. The result will either be a call or a leaf range.
-     * 
-     * @param methodEntry the method from which code in the subrange is derived.
-     * @param lo the lowest address included in the range.
-     * @param hi the first address above the highest address in the range.
-     * @param line the line number associated with the range
-     * @param primary the primary range to which this subrange belongs
-     * @param caller the range for which this is a subrange, either an inlined call range or the
-     *            primary range.
-     * @param isLeaf true if this is a leaf range with no subranges
-     * @return a new subrange to be linked into the range tree below the primary range.
-     */
-    public static SubRange createSubrange(MethodEntry methodEntry, long lo, long hi, int line, PrimaryRange primary, Range caller, boolean isLeaf) {
-        assert primary != null;
-        assert primary.isPrimary();
-        if (isLeaf) {
-            return new LeafRange(methodEntry, lo, hi, line, primary, caller);
-        } else {
-            return new CallRange(methodEntry, lo, hi, line, primary, caller);
-        }
-    }
-
-    protected Range(MethodEntry methodEntry, long lo, long hi, int line, int depth) {
-        assert methodEntry != null;
-        this.methodEntry = methodEntry;
-        this.lo = lo;
-        this.hi = hi;
-        this.line = line;
-        this.depth = depth;
-    }
-
-    protected abstract void addCallee(SubRange callee);
-
-    public boolean contains(Range other) {
-        return (lo <= other.lo && hi >= other.hi);
-    }
-
-    public abstract boolean isPrimary();
-
-    public String getClassName() {
-        return methodEntry.ownerType().getTypeName();
-    }
-
-    public String getMethodName() {
-        return methodEntry.methodName();
-    }
-
-    public String getSymbolName() {
-        return methodEntry.getSymbolName();
-    }
-
-    public long getHi() {
-        return hi;
-    }
-
-    public long getLo() {
-        return lo;
-    }
-
-    public int getLine() {
-        return line;
-    }
-
-    public String getFullMethodName() {
-        return constructClassAndMethodName();
-    }
-
-    public String getFullMethodNameWithParams() {
-        return constructClassAndMethodNameWithParams();
-    }
-
-    public boolean isDeoptTarget() {
-        return methodEntry.isDeopt();
-    }
-
-    private String getExtendedMethodName(boolean includeClass, boolean includeParams, boolean includeReturnType) {
-        StringBuilder builder = new StringBuilder();
-        if (includeReturnType && methodEntry.getValueType().getTypeName().length() > 0) {
-            builder.append(methodEntry.getValueType().getTypeName());
-            builder.append(' ');
-        }
-        if (includeClass && getClassName() != null) {
-            builder.append(getClassName());
-            builder.append(CLASS_DELIMITER);
-        }
-        builder.append(getMethodName());
-        if (includeParams) {
-            builder.append("(");
-            TypeEntry[] paramTypes = methodEntry.getParamTypes();
-            if (paramTypes != null) {
-                String prefix = "";
-                for (TypeEntry t : paramTypes) {
-                    builder.append(prefix);
-                    builder.append(t.getTypeName());
-                    prefix = ", ";
-                }
-            }
-            builder.append(')');
-        }
-        if (includeReturnType) {
-            builder.append(" ");
-            builder.append(methodEntry.getValueType().getTypeName());
-        }
-        return builder.toString();
-    }
-
-    private String constructClassAndMethodName() {
-        return getExtendedMethodName(true, false, false);
-    }
-
-    private String constructClassAndMethodNameWithParams() {
-        return getExtendedMethodName(true, true, false);
-    }
-
-    public FileEntry getFileEntry() {
-        return methodEntry.getFileEntry();
-    }
-
-    public int getModifiers() {
-        return methodEntry.getModifiers();
-    }
-
-    @Override
-    public String toString() {
-        return String.format("Range(lo=0x%05x hi=0x%05x %s %s:%d)", lo, hi, constructClassAndMethodNameWithParams(), methodEntry.getFullFileName(), line);
-    }
-
-    public String getFileName() {
-        return methodEntry.getFileName();
-    }
-
-    public MethodEntry getMethodEntry() {
-        return methodEntry;
-    }
-
-    public abstract SubRange getFirstCallee();
-
-    public abstract boolean isLeaf();
-
-    public boolean includesInlineRanges() {
-        SubRange child = getFirstCallee();
-        while (child != null && child.isLeaf()) {
-            child = child.getSiblingCallee();
-        }
-        return child != null;
-    }
-
-    public int getDepth() {
-        return depth;
-    }
-
-    public HashMap<DebugLocalInfo, List<SubRange>> getVarRangeMap() {
-        MethodEntry calleeMethod;
-        if (isPrimary()) {
-            calleeMethod = getMethodEntry();
-        } else {
-            assert !isLeaf() : "should only be looking up var ranges for inlined calls";
-            calleeMethod = getFirstCallee().getMethodEntry();
-        }
-        HashMap<DebugLocalInfo, List<SubRange>> varRangeMap = new HashMap<>();
-        if (calleeMethod.getThisParam() != null) {
-            varRangeMap.put(calleeMethod.getThisParam(), new ArrayList<>());
-        }
-        for (int i = 0; i < calleeMethod.getParamCount(); i++) {
-            varRangeMap.put(calleeMethod.getParam(i), new ArrayList<>());
-        }
-        for (int i = 0; i < calleeMethod.getLocalCount(); i++) {
-            varRangeMap.put(calleeMethod.getLocal(i), new ArrayList<>());
-        }
-        return updateVarRangeMap(varRangeMap);
-    }
-
-    public HashMap<DebugLocalInfo, List<SubRange>> updateVarRangeMap(HashMap<DebugLocalInfo, List<SubRange>> varRangeMap) {
-        // leaf subranges of the current range may provide values for param or local vars
-        // of this range's method. find them and index the range so that we can identify
-        // both the local/param and the associated range.
-        SubRange subRange = this.getFirstCallee();
-        while (subRange != null) {
-            addVarRanges(subRange, varRangeMap);
-            subRange = subRange.siblingCallee;
-        }
-        return varRangeMap;
-    }
-
-    public void addVarRanges(SubRange subRange, HashMap<DebugLocalInfo, List<SubRange>> varRangeMap) {
-        int localValueCount = subRange.getLocalValueCount();
-        for (int i = 0; i < localValueCount; i++) {
-            DebugLocalValueInfo localValueInfo = subRange.getLocalValue(i);
-            DebugLocalInfo local = subRange.getLocal(i);
-            if (local != null) {
-                switch (localValueInfo.localKind()) {
-                    case REGISTER:
-                    case STACKSLOT:
-                    case CONSTANT:
-                        List<SubRange> varRanges = varRangeMap.get(local);
-                        assert varRanges != null : "local not present in var to ranges map!";
-                        varRanges.add(subRange);
-                        break;
-                    case UNDEFINED:
-                        break;
-                }
-            }
-        }
-    }
-
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/SubRange.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/SubRange.java
deleted file mode 100644
index 12e6458abbd6..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/debugentry/range/SubRange.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2022, 2022, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.debugentry.range;
-
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider;
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocalInfo;
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocalValueInfo;
-import com.oracle.objectfile.runtime.debugentry.MethodEntry;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public abstract class SubRange extends Range {
-    /**
-     * The root of the call tree the subrange belongs to.
-     */
-    private final PrimaryRange primary;
-    /**
-     * The range for the caller or the primary range when this range is for top level method code.
-     */
-    protected Range caller;
-    /**
-     * A link to a sibling callee, i.e., a range sharing the same caller with this range.
-     */
-    protected SubRange siblingCallee;
-    /**
-     * Values for the associated method's local and parameter variables that are available or,
-     * alternatively, marked as invalid in this range.
-     */
-    private List<DebugLocalValueInfo> localValueInfos;
-    /**
-     * The set of local or parameter variables with which each corresponding local value in field
-     * localValueInfos is associated. Local values which are associated with the same local or
-     * parameter variable will share the same reference in the corresponding array entries. Local
-     * values with which no local variable can be associated will have a null reference in the
-     * corresponding array. The latter case can happen when a local value has an invalid slot or
-     * when a local value that maps to a parameter slot has a different name or type to the
-     * parameter.
-     */
-    private List<DebugLocalInfo> localInfos;
-
-    @SuppressWarnings("this-escape")
-    protected SubRange(MethodEntry methodEntry, long lo, long hi, int line, PrimaryRange primary, Range caller) {
-        super(methodEntry, lo, hi, line, (caller == null ? 0 : caller.depth + 1));
-        this.caller = caller;
-        if (caller != null) {
-            caller.addCallee(this);
-        }
-        assert primary != null;
-        this.primary = primary;
-    }
-
-    public Range getPrimary() {
-        return primary;
-    }
-
-    @Override
-    public boolean isPrimary() {
-        return false;
-    }
-
-    public Range getCaller() {
-        return caller;
-    }
-
-    @Override
-    public abstract SubRange getFirstCallee();
-
-    @Override
-    public abstract boolean isLeaf();
-
-    public int getLocalValueCount() {
-        return localValueInfos.size();
-    }
-
-    public DebugLocalValueInfo getLocalValue(int i) {
-        assert i >= 0 && i < localValueInfos.size() : "bad index";
-        return localValueInfos.get(i);
-    }
-
-    public DebugLocalInfo getLocal(int i) {
-        assert i >= 0 && i < localInfos.size() : "bad index";
-        return localInfos.get(i);
-    }
-
-    public void setLocalValueInfo(List<DebugLocalValueInfo> localValueInfos) {
-        this.localValueInfos = localValueInfos;
-        this.localInfos = new ArrayList<>();
-        // set up mapping from local values to local variables
-        for (DebugLocalValueInfo localValueInfo : localValueInfos) {
-            localInfos.add(methodEntry.recordLocal(localValueInfo));
-        }
-    }
-
-    public DebugLocalValueInfo lookupValue(DebugLocalInfo local) {
-        for (int i = 0; i < localValueInfos.size(); i++) {
-            if (getLocal(i).equals(local)) {
-                return getLocalValue(i);
-            }
-        }
-        return null;
-    }
-
-    public SubRange getSiblingCallee() {
-        return siblingCallee;
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfAbbrevSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfAbbrevSectionImpl.java
index 400346fa6724..5024fb8ed738 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfAbbrevSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfAbbrevSectionImpl.java
@@ -27,11 +27,12 @@
 package com.oracle.objectfile.runtime.dwarf;
 
 import com.oracle.objectfile.runtime.dwarf.RuntimeDwarfDebugInfo.AbbrevCode;
-import com.oracle.objectfile.runtime.dwarf.constants.DwarfAttribute;
-import com.oracle.objectfile.runtime.dwarf.constants.DwarfForm;
-import com.oracle.objectfile.runtime.dwarf.constants.DwarfHasChildren;
-import com.oracle.objectfile.runtime.dwarf.constants.DwarfSectionName;
-import com.oracle.objectfile.runtime.dwarf.constants.DwarfTag;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfAttribute;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfForm;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfHasChildren;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfTag;
+
 import jdk.graal.compiler.debug.DebugContext;
 
 public class RuntimeDwarfAbbrevSectionImpl extends RuntimeDwarfSectionImpl {
@@ -44,6 +45,8 @@ public RuntimeDwarfAbbrevSectionImpl(RuntimeDwarfDebugInfo dwarfSections) {
     @Override
     public void createContent() {
         assert !contentByteArrayCreated();
+        /*
+         */
 
         int pos = 0;
         pos = writeAbbrevs(null, null, pos);
@@ -69,18 +72,21 @@ public void writeContent(DebugContext context) {
 
     public int writeAbbrevs(DebugContext context, byte[] buffer, int p) {
         int pos = p;
-        // Level 0
-        pos = writeMethodCompileUnitAbbrev(context, buffer, pos);
+        pos = writeCompileUnitAbbrev(context, buffer, pos);
+
+        pos = writeNamespaceAbbrev(context, buffer, pos);
 
-        // Level 1
-        pos = writePrimitiveTypeAbbrev(context, buffer, pos);
-        pos = writeTypedefAbbrev(context, buffer, pos);
-        pos = writeTypedefPointerAbbrev(context, buffer, pos);
-        pos = writeMethodLocationAbbrevs(context, buffer, pos);
-        pos = writeAbstractInlineMethodAbbrev(context, buffer, pos);
+        pos = writeClassLayoutAbbrev(context, buffer, pos);
+
+        pos = writeMethodDeclarationAbbrevs(context, buffer, pos);
+
+        pos = writeMethodLocationAbbrev(context, buffer, pos);
 
-        // Level 2 + inline depth
         pos = writeInlinedSubroutineAbbrev(buffer, pos);
+
+        pos = writeParameterDeclarationAbbrevs(context, buffer, pos);
+        pos = writeLocalDeclarationAbbrevs(context, buffer, pos);
+
         pos = writeParameterLocationAbbrevs(context, buffer, pos);
         pos = writeLocalLocationAbbrevs(context, buffer, pos);
 
@@ -101,13 +107,11 @@ private int writeHasChildren(DwarfHasChildren hasChildren, byte[] buffer, int po
         return writeByte(hasChildren.value(), buffer, pos);
     }
 
-    private int writeMethodCompileUnitAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
+    private int writeCompileUnitAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
         int pos = p;
-        pos = writeAbbrevCode(AbbrevCode.METHOD_UNIT, buffer, pos);
+        pos = writeAbbrevCode(AbbrevCode.CLASS_UNIT, buffer, pos);
         pos = writeTag(DwarfTag.DW_TAG_compile_unit, buffer, pos);
         pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_yes, buffer, pos);
-        // pos = writeAttrType(DwarfAttribute.DW_AT_producer, buffer, pos);
-        // pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos);
         pos = writeAttrType(DwarfAttribute.DW_AT_language, buffer, pos);
         pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos);
         pos = writeAttrType(DwarfAttribute.DW_AT_use_UTF8, buffer, pos);
@@ -122,6 +126,8 @@ private int writeMethodCompileUnitAbbrev(@SuppressWarnings("unused") DebugContex
         pos = writeAttrForm(DwarfForm.DW_FORM_addr, buffer, pos);
         pos = writeAttrType(DwarfAttribute.DW_AT_stmt_list, buffer, pos);
         pos = writeAttrForm(DwarfForm.DW_FORM_sec_offset, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_loclists_base, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_sec_offset, buffer, pos);
         /*
          * Now terminate.
          */
@@ -130,17 +136,11 @@ private int writeMethodCompileUnitAbbrev(@SuppressWarnings("unused") DebugContex
         return pos;
     }
 
-    private int writePrimitiveTypeAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
+    private int writeNamespaceAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
         int pos = p;
-        pos = writeAbbrevCode(AbbrevCode.PRIMITIVE_TYPE, buffer, pos);
-        pos = writeTag(DwarfTag.DW_TAG_base_type, buffer, pos);
-        pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_no, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_byte_size, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_bit_size, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_encoding, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos);
+        pos = writeAbbrevCode(AbbrevCode.NAMESPACE, buffer, pos);
+        pos = writeTag(DwarfTag.DW_TAG_namespace, buffer, pos);
+        pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_yes, buffer, pos);
         pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos);
         pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos);
         /*
@@ -151,81 +151,69 @@ private int writePrimitiveTypeAbbrev(@SuppressWarnings("unused") DebugContext co
         return pos;
     }
 
-
-
-    private int writeTypedefAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
+    private int writeClassLayoutAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
         int pos = p;
-        /* A pointer to the class struct type. */
-        pos = writeAbbrevCode(AbbrevCode.TYPEDEF, buffer, pos);
-        pos = writeTag(DwarfTag.DW_TAG_typedef, buffer, pos);
-        pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_no, buffer, pos);
+
+        pos = writeAbbrevCode(AbbrevCode.CLASS_LAYOUT, buffer, pos);
+        pos = writeTag(DwarfTag.DW_TAG_class_type, buffer, pos);
+        pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_yes, buffer, pos);
         pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos);
         pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_declaration, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_signature, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_ref_sig8, buffer, pos);
         /*
          * Now terminate.
          */
         pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos);
         pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos);
-        return pos;
-    }
 
-    private int writeTypedefPointerAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
-        int pos = p;
-        /* A pointer to a typedef. */
-        pos = writeAbbrevCode(AbbrevCode.TYPEDEF_POINTER, buffer, pos);
-        pos = writeTag(DwarfTag.DW_TAG_pointer_type, buffer, pos);
-        pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_no, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_byte_size, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_type, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_ref4, buffer, pos);
-        /*
-         * Now terminate.
-         */
-        pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos);
         return pos;
     }
 
-
-    private int writeMethodLocationAbbrevs(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
+    private int writeMethodDeclarationAbbrevs(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
         int pos = p;
-        pos = writeMethodLocationAbbrev(context, AbbrevCode.METHOD_LOCATION, buffer, pos);
-        pos = writeMethodLocationAbbrev(context, AbbrevCode.METHOD_LOCATION_STATIC, buffer, pos);
+        pos = writeMethodDeclarationAbbrev(context, AbbrevCode.METHOD_DECLARATION, buffer, pos);
+        pos = writeMethodDeclarationAbbrev(context, AbbrevCode.METHOD_DECLARATION_INLINE, buffer, pos);
+        pos = writeMethodDeclarationAbbrev(context, AbbrevCode.METHOD_DECLARATION_STATIC, buffer, pos);
+        pos = writeMethodDeclarationAbbrev(context, AbbrevCode.METHOD_DECLARATION_INLINE_STATIC, buffer, pos);
         return pos;
     }
 
-    private int writeMethodLocationAbbrev(@SuppressWarnings("unused") DebugContext context, AbbrevCode abbrevCode, byte[] buffer, int p) {
+    private int writeMethodDeclarationAbbrev(@SuppressWarnings("unused") DebugContext context, AbbrevCode abbrevCode, byte[] buffer, int p) {
         int pos = p;
         pos = writeAbbrevCode(abbrevCode, buffer, pos);
         pos = writeTag(DwarfTag.DW_TAG_subprogram, buffer, pos);
         pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_yes, buffer, pos);
+        if (abbrevCode == AbbrevCode.METHOD_DECLARATION_INLINE || abbrevCode == AbbrevCode.METHOD_DECLARATION_INLINE_STATIC) {
+            pos = writeAttrType(DwarfAttribute.DW_AT_inline, buffer, pos);
+            pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos);
+        }
         pos = writeAttrType(DwarfAttribute.DW_AT_external, buffer, pos);
         pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos);
         pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos);
         pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos);
-//        pos = writeAttrType(DwarfAttribute.DW_AT_decl_file, buffer, pos);
-//        pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos);
-//        pos = writeAttrType(DwarfAttribute.DW_AT_decl_line, buffer, pos);
-//        pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos);
-//        pos = writeAttrType(DwarfAttribute.DW_AT_linkage_name, buffer, pos);
-//        pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_low_pc, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_addr, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_hi_pc, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_addr, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_decl_file, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_decl_line, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_linkage_name, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos);
         pos = writeAttrType(DwarfAttribute.DW_AT_type, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_ref_addr, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_ref_sig8, buffer, pos);
         pos = writeAttrType(DwarfAttribute.DW_AT_artificial, buffer, pos);
         pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos);
         pos = writeAttrType(DwarfAttribute.DW_AT_accessibility, buffer, pos);
         pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_declaration, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos);
         /* This is not in DWARF2 */
         // pos = writeAttrType(DW_AT_virtuality, buffer, pos);
         // pos = writeAttrForm(DW_FORM_data1, buffer, pos);
         pos = writeAttrType(DwarfAttribute.DW_AT_containing_type, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_ref_addr, buffer, pos);
-        if (abbrevCode == AbbrevCode.METHOD_LOCATION) {
+        pos = writeAttrForm(DwarfForm.DW_FORM_ref_sig8, buffer, pos);
+        if (abbrevCode == AbbrevCode.METHOD_DECLARATION || abbrevCode == AbbrevCode.METHOD_DECLARATION_INLINE) {
             pos = writeAttrType(DwarfAttribute.DW_AT_object_pointer, buffer, pos);
             pos = writeAttrForm(DwarfForm.DW_FORM_ref4, buffer, pos);
         }
@@ -237,19 +225,58 @@ private int writeMethodLocationAbbrev(@SuppressWarnings("unused") DebugContext c
         return pos;
     }
 
-    private int writeAbstractInlineMethodAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
+    private int writeMethodLocationAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
         int pos = p;
-        pos = writeAbbrevCode(AbbrevCode.ABSTRACT_INLINE_METHOD, buffer, pos);
+        pos = writeAbbrevCode(AbbrevCode.METHOD_LOCATION, buffer, pos);
         pos = writeTag(DwarfTag.DW_TAG_subprogram, buffer, pos);
         pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_yes, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_inline, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_low_pc, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_addr, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_hi_pc, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_addr, buffer, pos);
         pos = writeAttrType(DwarfAttribute.DW_AT_external, buffer, pos);
         pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_specification, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_ref_addr, buffer, pos);
+        /*
+         * Now terminate.
+         */
+        pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos);
+        return pos;
+    }
+
+    private int writeParameterDeclarationAbbrevs(DebugContext context, byte[] buffer, int p) {
+        int pos = p;
+        pos = writeParameterDeclarationAbbrev(context, AbbrevCode.METHOD_PARAMETER_DECLARATION_1, buffer, pos);
+        pos = writeParameterDeclarationAbbrev(context, AbbrevCode.METHOD_PARAMETER_DECLARATION_2, buffer, pos);
+        pos = writeParameterDeclarationAbbrev(context, AbbrevCode.METHOD_PARAMETER_DECLARATION_3, buffer, pos);
+        return pos;
+    }
+
+    private int writeParameterDeclarationAbbrev(@SuppressWarnings("unused") DebugContext context, AbbrevCode abbrevCode, byte[] buffer, int p) {
+        int pos = p;
+        pos = writeAbbrevCode(abbrevCode, buffer, pos);
+        pos = writeTag(DwarfTag.DW_TAG_formal_parameter, buffer, pos);
+        pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_no, buffer, pos);
         pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos);
         pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos);
+        if (abbrevCode == AbbrevCode.METHOD_PARAMETER_DECLARATION_2) {
+            /* Line numbers for parameter declarations are not (yet?) available. */
+            pos = writeAttrType(DwarfAttribute.DW_AT_decl_file, buffer, pos);
+            pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos);
+            pos = writeAttrType(DwarfAttribute.DW_AT_decl_line, buffer, pos);
+            pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos);
+        }
         pos = writeAttrType(DwarfAttribute.DW_AT_type, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_ref_addr, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_ref_sig8, buffer, pos);
+        if (abbrevCode == AbbrevCode.METHOD_PARAMETER_DECLARATION_1) {
+            /* Only this parameter is artificial and it has no line. */
+            pos = writeAttrType(DwarfAttribute.DW_AT_artificial, buffer, pos);
+            pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos);
+        }
+        pos = writeAttrType(DwarfAttribute.DW_AT_declaration, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos);
         /*
          * Now terminate.
          */
@@ -258,23 +285,34 @@ private int writeAbstractInlineMethodAbbrev(@SuppressWarnings("unused") DebugCon
         return pos;
     }
 
+    private int writeLocalDeclarationAbbrevs(DebugContext context, byte[] buffer, int p) {
+        int pos = p;
+        pos = writeLocalDeclarationAbbrev(context, AbbrevCode.METHOD_LOCAL_DECLARATION_1, buffer, pos);
+        pos = writeLocalDeclarationAbbrev(context, AbbrevCode.METHOD_LOCAL_DECLARATION_2, buffer, pos);
+        return pos;
+    }
 
-    private int writeInlinedSubroutineAbbrev(byte[] buffer, int p) {
+    private int writeLocalDeclarationAbbrev(@SuppressWarnings("unused") DebugContext context, AbbrevCode abbrevCode, byte[] buffer, int p) {
         int pos = p;
-        pos = writeAbbrevCode(AbbrevCode.INLINED_SUBROUTINE, buffer, pos);
-        pos = writeTag(DwarfTag.DW_TAG_inlined_subroutine, buffer, pos);
-        pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_yes, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_abstract_origin, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_ref4, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_low_pc, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_addr, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_hi_pc, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_addr, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_call_file, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_data4, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_call_line, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_data4, buffer, pos);
-        /* Now terminate. */
+        pos = writeAbbrevCode(abbrevCode, buffer, pos);
+        pos = writeTag(DwarfTag.DW_TAG_variable, buffer, pos);
+        pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_no, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos);
+        if (abbrevCode == AbbrevCode.METHOD_LOCAL_DECLARATION_1) {
+            /* Line numbers for parameter declarations are not (yet?) available. */
+            pos = writeAttrType(DwarfAttribute.DW_AT_decl_file, buffer, pos);
+            pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos);
+            pos = writeAttrType(DwarfAttribute.DW_AT_decl_line, buffer, pos);
+            pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos);
+        }
+        pos = writeAttrType(DwarfAttribute.DW_AT_type, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_ref_sig8, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_declaration, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos);
+        /*
+         * Now terminate.
+         */
         pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos);
         pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos);
         return pos;
@@ -299,13 +337,11 @@ private int writeParameterLocationAbbrev(@SuppressWarnings("unused") DebugContex
         pos = writeAbbrevCode(abbrevCode, buffer, pos);
         pos = writeTag(DwarfTag.DW_TAG_formal_parameter, buffer, pos);
         pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_no, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_type, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_abstract_origin, buffer, pos);
         pos = writeAttrForm(DwarfForm.DW_FORM_ref_addr, buffer, pos);
         if (abbrevCode == AbbrevCode.METHOD_PARAMETER_LOCATION_2) {
             pos = writeAttrType(DwarfAttribute.DW_AT_location, buffer, pos);
-            pos = writeAttrForm(DwarfForm.DW_FORM_sec_offset, buffer, pos);
+            pos = writeAttrForm(DwarfForm.DW_FORM_loclistx, buffer, pos);
         }
         /*
          * Now terminate.
@@ -320,13 +356,11 @@ private int writeLocalLocationAbbrev(@SuppressWarnings("unused") DebugContext co
         pos = writeAbbrevCode(abbrevCode, buffer, pos);
         pos = writeTag(DwarfTag.DW_TAG_variable, buffer, pos);
         pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_no, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_type, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_abstract_origin, buffer, pos);
         pos = writeAttrForm(DwarfForm.DW_FORM_ref_addr, buffer, pos);
         if (abbrevCode == AbbrevCode.METHOD_LOCAL_LOCATION_2) {
             pos = writeAttrType(DwarfAttribute.DW_AT_location, buffer, pos);
-            pos = writeAttrForm(DwarfForm.DW_FORM_sec_offset, buffer, pos);
+            pos = writeAttrForm(DwarfForm.DW_FORM_loclistx, buffer, pos);
         }
         /*
          * Now terminate.
@@ -341,4 +375,25 @@ private int writeNullAbbrev(@SuppressWarnings("unused") DebugContext context, by
         pos = writeAbbrevCode(AbbrevCode.NULL, buffer, pos);
         return pos;
     }
+
+    private int writeInlinedSubroutineAbbrev(byte[] buffer, int p) {
+        int pos = p;
+        pos = writeAbbrevCode(AbbrevCode.INLINED_SUBROUTINE_WITH_CHILDREN, buffer, pos);
+        pos = writeTag(DwarfTag.DW_TAG_inlined_subroutine, buffer, pos);
+        pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_yes, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_abstract_origin, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_ref4, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_low_pc, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_addr, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_hi_pc, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_addr, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_call_file, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_data4, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_call_line, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_data4, buffer, pos);
+        /* Now terminate. */
+        pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos);
+        return pos;
+    }
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfDebugInfo.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfDebugInfo.java
index c6ed71782697..7da54a2f805d 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfDebugInfo.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfDebugInfo.java
@@ -26,19 +26,19 @@
 
 package com.oracle.objectfile.runtime.dwarf;
 
-import com.oracle.objectfile.elf.ELFMachine;
+import java.nio.ByteOrder;
+
 import com.oracle.objectfile.runtime.RuntimeDebugInfoBase;
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocalInfo;
-import com.oracle.objectfile.runtime.debugentry.CompiledMethodEntry;
-import com.oracle.objectfile.runtime.debugentry.MethodEntry;
-import com.oracle.objectfile.runtime.debugentry.StructureTypeEntry;
-import com.oracle.objectfile.runtime.debugentry.TypeEntry;
-import com.oracle.objectfile.runtime.debugentry.range.Range;
-import com.oracle.objectfile.runtime.dwarf.constants.DwarfLanguage;
 import org.graalvm.collections.EconomicMap;
 
-import java.nio.ByteOrder;
-import java.util.HashMap;
+import com.oracle.objectfile.debugentry.ClassEntry;
+import com.oracle.objectfile.debugentry.MethodEntry;
+import com.oracle.objectfile.debugentry.StructureTypeEntry;
+import com.oracle.objectfile.debugentry.TypeEntry;
+import com.oracle.objectfile.debugentry.range.Range;
+import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalInfo;
+import com.oracle.objectfile.elf.ELFMachine;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfLanguage;
 
 /**
  * A class that models the debug info in an organization that facilitates generation of the required
@@ -46,6 +46,9 @@
  * DwarfSectionImpl that take responsibility for generating content for a specific section type.
  */
 public class RuntimeDwarfDebugInfo extends RuntimeDebugInfoBase {
+
+    public static final String HEAP_BEGIN_NAME = "__svm_heap_begin";
+
     /*
      * Define all the abbrev section codes we need for our DIEs.
      */
@@ -53,17 +56,24 @@ enum AbbrevCode {
         /* null marker which must come first as its ordinal has to equal zero */
         NULL,
         /* Level 0 DIEs. */
-        METHOD_UNIT,
+        CLASS_UNIT,
         /* Level 1 DIEs. */
-        PRIMITIVE_TYPE,
-        TYPEDEF,
-        TYPEDEF_POINTER,
+        NAMESPACE,
+        CLASS_LAYOUT,
         METHOD_LOCATION,
-        METHOD_LOCATION_STATIC,
-        ABSTRACT_INLINE_METHOD,
         /* Level 2 DIEs. */
+        METHOD_DECLARATION,
+        METHOD_DECLARATION_INLINE,
+        METHOD_DECLARATION_STATIC,
+        METHOD_DECLARATION_INLINE_STATIC,
         /* Level 2+K DIEs (where inline depth K >= 0) */
-        INLINED_SUBROUTINE,
+        INLINED_SUBROUTINE_WITH_CHILDREN,
+        /* Level 3 DIEs. */
+        METHOD_PARAMETER_DECLARATION_1,
+        METHOD_PARAMETER_DECLARATION_2,
+        METHOD_PARAMETER_DECLARATION_3,
+        METHOD_LOCAL_DECLARATION_1,
+        METHOD_LOCAL_DECLARATION_2,
         METHOD_PARAMETER_LOCATION_1,
         METHOD_PARAMETER_LOCATION_2,
         METHOD_LOCAL_LOCATION_1,
@@ -83,6 +93,21 @@ enum AbbrevCode {
     public static final byte rheapbase_x86 = (byte) 14;
     public static final byte rthread_x86 = (byte) 15;
 
+    /*
+     * A prefix used to label indirect types used to ensure gdb performs oop reference --> raw
+     * address translation
+     */
+    public static final String INDIRECT_PREFIX = "_z_.";
+    /*
+     * A prefix used for type signature generation to generate unique type signatures for type
+     * layout type units
+     */
+    public static final String LAYOUT_PREFIX = "_layout_.";
+    /*
+     * The name of the type for header field hub which needs special case processing to remove tag
+     * bits
+     */
+    public static final String HUB_TYPE_NAME = "java.lang.Class";
     /* Full byte/word values. */
     private final RuntimeDwarfStrSectionImpl dwarfStrSection;
     private final RuntimeDwarfAbbrevSectionImpl dwarfAbbrevSection;
@@ -105,7 +130,7 @@ enum AbbrevCode {
      * n.b. this collection includes entries for the structure types used to define the object and
      * array headers which do not have an associated TypeEntry.
      */
-    private final EconomicMap<TypeEntry, DwarfTypeProperties> typePropertiesIndex = EconomicMap.create();
+    private final EconomicMap<TypeEntry, DwarfClassProperties> classPropertiesIndex = EconomicMap.create();
 
     /**
      * A collection of method properties associated with each generated method record.
@@ -171,80 +196,26 @@ public byte getThreadRegister() {
     }
 
     /**
-     * A class used to associate properties with a specific type, the most important one being its
-     * index in the info section.
+     * A class used to associate extra properties with an instance class type.
      */
-    static class DwarfTypeProperties {
-        /**
-         * Index in debug_info section of type declaration for this class.
-         */
-        private int typeInfoIndex;
-        /**
-         * Index in debug_info section of indirect type declaration for this class.
-         *
-         * this is normally just the same as the index of the normal type declaration, however, when
-         * oops are stored in static and instance fields as offsets from the heapbase register gdb
-         * needs to be told how to convert these oops to raw addresses and this requires attaching a
-         * data_location address translation expression to an indirect type that wraps the object
-         * layout type. so, with that encoding this field will identify the wrapper type whenever
-         * the original type is an object, interface or array layout. primitive types and header
-         * types do not need translating.
-         */
-        private int indirectTypeInfoIndex;
+
+    static class DwarfClassProperties {
         /**
          * The type entry with which these properties are associated.
          */
-        private final TypeEntry typeEntry;
-
-        public int getTypeInfoIndex() {
-            return typeInfoIndex;
-        }
-
-        public void setTypeInfoIndex(int typeInfoIndex) {
-            this.typeInfoIndex = typeInfoIndex;
-        }
-
-        public int getIndirectTypeInfoIndex() {
-            return indirectTypeInfoIndex;
-        }
-
-        public void setIndirectTypeInfoIndex(int typeInfoIndex) {
-            this.indirectTypeInfoIndex = typeInfoIndex;
-        }
-
-        public TypeEntry getTypeEntry() {
-            return typeEntry;
-        }
-
-        DwarfTypeProperties(TypeEntry typeEntry) {
-            this.typeEntry = typeEntry;
-            this.typeInfoIndex = -1;
-            this.indirectTypeInfoIndex = -1;
-        }
-
-    }
-
-    /**
-     * A class used to associate extra properties with an instance class type.
-     */
-
-    static class DwarfClassProperties extends DwarfTypeProperties {
+        private final StructureTypeEntry typeEntry;
         /**
          * Index of the class entry's compile unit in the debug_info section.
          */
         private int cuIndex;
         /**
-         * Index of the class entry's class_layout DIE in the debug_info section.
+         * Index of the class entry's code ranges data in the debug_rnglists section.
          */
-        private int layoutIndex;
+        private int codeRangesIndex;
         /**
-         * Index of the class entry's indirect layout DIE in the debug_info section.
+         * Index of the class entry's code location data in the debug_loclists section.
          */
-        private int indirectLayoutIndex;
-        /**
-         * Index of the class entry's code ranges data in the debug_ranges section.
-         */
-        private int codeRangesIndex;
+        private int locationListIndex;
         /**
          * Index of the class entry's line data in the debug_line section.
          */
@@ -258,12 +229,15 @@ static class DwarfClassProperties extends DwarfTypeProperties {
          */
         private EconomicMap<String, Integer> fieldDeclarationIndex;
 
-        DwarfClassProperties(StructureTypeEntry entry) {
-            super(entry);
+        public StructureTypeEntry getTypeEntry() {
+            return typeEntry;
+        }
+
+        DwarfClassProperties(StructureTypeEntry typeEntry) {
+            this.typeEntry = typeEntry;
             this.cuIndex = -1;
-            this.layoutIndex = -1;
-            this.indirectLayoutIndex = -1;
             this.codeRangesIndex = -1;
+            this.locationListIndex = 0;
             this.lineIndex = -1;
             this.linePrologueSize = -1;
             fieldDeclarationIndex = null;
@@ -283,12 +257,12 @@ static class DwarfMethodProperties {
          * Per class map that identifies the info declarations for a top level method declaration or
          * an abstract inline method declaration.
          */
-        private HashMap<CompiledMethodEntry, DwarfLocalProperties> localPropertiesMap;
+        private EconomicMap<ClassEntry, DwarfLocalProperties> localPropertiesMap;
 
         /**
          * Per class map that identifies the info declaration for an abstract inline method.
          */
-        private HashMap<CompiledMethodEntry, Integer> abstractInlineMethodIndex;
+        private EconomicMap<ClassEntry, Integer> abstractInlineMethodIndex;
 
         DwarfMethodProperties() {
             methodDeclarationIndex = -1;
@@ -306,46 +280,36 @@ public void setMethodDeclarationIndex(int pos) {
             methodDeclarationIndex = pos;
         }
 
-        public DwarfLocalProperties getLocalProperties(CompiledMethodEntry compiledEntry) {
+        public DwarfLocalProperties getLocalProperties(ClassEntry classEntry) {
             if (localPropertiesMap == null) {
-                localPropertiesMap = new HashMap<>();
+                localPropertiesMap = EconomicMap.create();
             }
-            DwarfLocalProperties localProperties = localPropertiesMap.get(compiledEntry);
+            DwarfLocalProperties localProperties = localPropertiesMap.get(classEntry);
             if (localProperties == null) {
                 localProperties = new DwarfLocalProperties();
-                localPropertiesMap.put(compiledEntry, localProperties);
+                localPropertiesMap.put(classEntry, localProperties);
             }
             return localProperties;
         }
 
-        public void setAbstractInlineMethodIndex(CompiledMethodEntry compiledEntry, int pos) {
+        public void setAbstractInlineMethodIndex(ClassEntry classEntry, int pos) {
             if (abstractInlineMethodIndex == null) {
-                abstractInlineMethodIndex = new HashMap<>();
+                abstractInlineMethodIndex = EconomicMap.create();
             }
             // replace but check it did not change
-            Integer val = abstractInlineMethodIndex.put(compiledEntry, pos);
+            Integer val = abstractInlineMethodIndex.put(classEntry, pos);
             assert val == null || val == pos;
         }
 
-        public int getAbstractInlineMethodIndex(CompiledMethodEntry compiledEntry) {
+        public int getAbstractInlineMethodIndex(ClassEntry classEntry) {
             // should be set before we get here but an NPE will guard that
-            return abstractInlineMethodIndex.get(compiledEntry);
+            return abstractInlineMethodIndex.get(classEntry);
         }
     }
 
-    private DwarfTypeProperties addTypeProperties(TypeEntry typeEntry) {
-        assert typeEntry != null;
-        assert !typeEntry.isClass();
-        assert typePropertiesIndex.get(typeEntry) == null;
-        DwarfTypeProperties typeProperties = new DwarfTypeProperties(typeEntry);
-        this.typePropertiesIndex.put(typeEntry, typeProperties);
-        return typeProperties;
-    }
-
     private DwarfClassProperties addClassProperties(StructureTypeEntry entry) {
-        assert typePropertiesIndex.get(entry) == null;
         DwarfClassProperties classProperties = new DwarfClassProperties(entry);
-        this.typePropertiesIndex.put(entry, classProperties);
+        this.classPropertiesIndex.put(entry, classProperties);
         return classProperties;
     }
 
@@ -356,18 +320,8 @@ private DwarfMethodProperties addMethodProperties(MethodEntry methodEntry) {
         return methodProperties;
     }
 
-    private DwarfTypeProperties lookupTypeProperties(TypeEntry typeEntry) {
-        DwarfTypeProperties typeProperties = typePropertiesIndex.get(typeEntry);
-        if (typeProperties == null) {
-            typeProperties = addTypeProperties(typeEntry);
-        }
-        return typeProperties;
-    }
-
     private DwarfClassProperties lookupClassProperties(StructureTypeEntry entry) {
-        DwarfTypeProperties typeProperties = typePropertiesIndex.get(entry);
-        assert typeProperties == null || typeProperties instanceof DwarfClassProperties;
-        DwarfClassProperties classProperties = (DwarfClassProperties) typeProperties;
+        DwarfClassProperties classProperties = classPropertiesIndex.get(entry);
         if (classProperties == null) {
             classProperties = addClassProperties(entry);
         }
@@ -382,38 +336,83 @@ private DwarfMethodProperties lookupMethodProperties(MethodEntry methodEntry) {
         return methodProperties;
     }
 
-    void setTypeIndex(TypeEntry typeEntry, int idx) {
+    public void setCUIndex(ClassEntry classEntry, int idx) {
         assert idx >= 0;
-        DwarfTypeProperties typeProperties = lookupTypeProperties(typeEntry);
-        assert typeProperties.getTypeInfoIndex() == -1 || typeProperties.getTypeInfoIndex() == idx;
-        typeProperties.setTypeInfoIndex(idx);
+        DwarfClassProperties classProperties = lookupClassProperties(classEntry);
+        assert classProperties.getTypeEntry() == classEntry;
+        assert classProperties.cuIndex == -1 || classProperties.cuIndex == idx;
+        classProperties.cuIndex = idx;
     }
 
-    int getTypeIndex(TypeEntry typeEntry) {
-        DwarfTypeProperties typeProperties = lookupTypeProperties(typeEntry);
-        return getTypeIndex(typeProperties);
+    public int getCUIndex(ClassEntry classEntry) {
+        DwarfClassProperties classProperties;
+        classProperties = lookupClassProperties(classEntry);
+        assert classProperties.getTypeEntry() == classEntry;
+        assert classProperties.cuIndex >= 0;
+        return classProperties.cuIndex;
     }
 
-    int getTypeIndex(DwarfTypeProperties typeProperties) {
-        assert typeProperties.getTypeInfoIndex() >= 0;
-        return typeProperties.getTypeInfoIndex();
+    public void setCodeRangesIndex(ClassEntry classEntry, int idx) {
+        assert idx >= 0;
+        DwarfClassProperties classProperties = lookupClassProperties(classEntry);
+        assert classProperties.getTypeEntry() == classEntry;
+        assert classProperties.codeRangesIndex == -1 || classProperties.codeRangesIndex == idx;
+        classProperties.codeRangesIndex = idx;
     }
 
-    void setIndirectTypeIndex(TypeEntry typeEntry, int idx) {
+    public int getCodeRangesIndex(ClassEntry classEntry) {
+        DwarfClassProperties classProperties;
+        classProperties = lookupClassProperties(classEntry);
+        assert classProperties.getTypeEntry() == classEntry;
+        assert classProperties.codeRangesIndex >= 0;
+        return classProperties.codeRangesIndex;
+    }
+
+    public void setLocationListIndex(ClassEntry classEntry, int idx) {
         assert idx >= 0;
-        DwarfTypeProperties typeProperties = lookupTypeProperties(typeEntry);
-        assert typeProperties.getIndirectTypeInfoIndex() == -1 || typeProperties.getIndirectTypeInfoIndex() == idx;
-        typeProperties.setIndirectTypeInfoIndex(idx);
+        DwarfClassProperties classProperties = lookupClassProperties(classEntry);
+        assert classProperties.getTypeEntry() == classEntry;
+        assert classProperties.locationListIndex == 0 || classProperties.locationListIndex == idx;
+        classProperties.locationListIndex = idx;
     }
 
-    int getIndirectTypeIndex(TypeEntry typeEntry) {
-        DwarfTypeProperties typeProperties = lookupTypeProperties(typeEntry);
-        return getIndirectTypeIndex(typeProperties);
+    public int getLocationListIndex(ClassEntry classEntry) {
+        DwarfClassProperties classProperties;
+        classProperties = lookupClassProperties(classEntry);
+        assert classProperties.getTypeEntry() == classEntry;
+        return classProperties.locationListIndex;
     }
 
-    int getIndirectTypeIndex(DwarfTypeProperties typeProperties) {
-        assert typeProperties.getIndirectTypeInfoIndex() >= 0;
-        return typeProperties.getIndirectTypeInfoIndex();
+    public void setLineIndex(ClassEntry classEntry, int idx) {
+        assert idx >= 0;
+        DwarfClassProperties classProperties = lookupClassProperties(classEntry);
+        assert classProperties.getTypeEntry() == classEntry;
+        assert classProperties.lineIndex == -1 || classProperties.lineIndex == idx;
+        classProperties.lineIndex = idx;
+    }
+
+    public int getLineIndex(ClassEntry classEntry) {
+        DwarfClassProperties classProperties;
+        classProperties = lookupClassProperties(classEntry);
+        assert classProperties.getTypeEntry() == classEntry;
+        assert classProperties.lineIndex >= 0;
+        return classProperties.lineIndex;
+    }
+
+    public void setLinePrologueSize(ClassEntry classEntry, int size) {
+        assert size >= 0;
+        DwarfClassProperties classProperties = lookupClassProperties(classEntry);
+        assert classProperties.getTypeEntry() == classEntry;
+        assert classProperties.linePrologueSize == -1 || classProperties.linePrologueSize == size;
+        classProperties.linePrologueSize = size;
+    }
+
+    public int getLinePrologueSize(ClassEntry classEntry) {
+        DwarfClassProperties classProperties;
+        classProperties = lookupClassProperties(classEntry);
+        assert classProperties.getTypeEntry() == classEntry;
+        assert classProperties.linePrologueSize >= 0;
+        return classProperties.linePrologueSize;
     }
 
     public void setFieldDeclarationIndex(StructureTypeEntry entry, String fieldName, int pos) {
@@ -476,12 +475,12 @@ void setIndex(DebugLocalInfo localInfo, int index) {
         }
     }
 
-    public void setAbstractInlineMethodIndex(CompiledMethodEntry compiledEntry, MethodEntry methodEntry, int pos) {
-        lookupMethodProperties(methodEntry).setAbstractInlineMethodIndex(compiledEntry, pos);
+    public void setAbstractInlineMethodIndex(ClassEntry classEntry, MethodEntry methodEntry, int pos) {
+        lookupMethodProperties(methodEntry).setAbstractInlineMethodIndex(classEntry, pos);
     }
 
-    public int getAbstractInlineMethodIndex(CompiledMethodEntry compiledEntry, MethodEntry methodEntry) {
-        return lookupMethodProperties(methodEntry).getAbstractInlineMethodIndex(compiledEntry);
+    public int getAbstractInlineMethodIndex(ClassEntry classEntry, MethodEntry methodEntry) {
+        return lookupMethodProperties(methodEntry).getAbstractInlineMethodIndex(classEntry);
     }
 
     private DwarfLocalProperties addRangeLocalProperties(Range range) {
@@ -490,17 +489,17 @@ private DwarfLocalProperties addRangeLocalProperties(Range range) {
         return localProperties;
     }
 
-    public DwarfLocalProperties lookupLocalProperties(CompiledMethodEntry compiledEntry, MethodEntry methodEntry) {
-        return lookupMethodProperties(methodEntry).getLocalProperties(compiledEntry);
+    public DwarfLocalProperties lookupLocalProperties(ClassEntry classEntry, MethodEntry methodEntry) {
+        return lookupMethodProperties(methodEntry).getLocalProperties(classEntry);
     }
 
-    public void setMethodLocalIndex(CompiledMethodEntry compiledEntry, MethodEntry methodEntry, DebugLocalInfo localInfo, int index) {
-        DwarfLocalProperties localProperties = lookupLocalProperties(compiledEntry, methodEntry);
+    public void setMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, DebugLocalInfo localInfo, int index) {
+        DwarfLocalProperties localProperties = lookupLocalProperties(classEntry, methodEntry);
         localProperties.setIndex(localInfo, index);
     }
 
-    public int getMethodLocalIndex(CompiledMethodEntry compiledEntry, MethodEntry methodEntry, DebugLocalInfo localInfo) {
-        DwarfLocalProperties localProperties = lookupLocalProperties(compiledEntry, methodEntry);
+    public int getMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, DebugLocalInfo localInfo) {
+        DwarfLocalProperties localProperties = lookupLocalProperties(classEntry, methodEntry);
         assert localProperties != null : "get of non-existent local index";
         int index = localProperties.getIndex(localInfo);
         assert index >= 0 : "get of local index before it was set";
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImpl.java
index 22615c8b2984..0927c8705115 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImpl.java
@@ -26,11 +26,11 @@
 
 package com.oracle.objectfile.runtime.dwarf;
 
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider;
-import com.oracle.objectfile.runtime.debugentry.CompiledMethodEntry;
-import com.oracle.objectfile.runtime.debugentry.range.Range;
-import com.oracle.objectfile.runtime.dwarf.constants.DwarfFrameValue;
-import com.oracle.objectfile.runtime.dwarf.constants.DwarfSectionName;
+import com.oracle.objectfile.debugentry.CompiledMethodEntry;
+import com.oracle.objectfile.debugentry.range.Range;
+import com.oracle.objectfile.debuginfo.DebugInfoProvider;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfFrameValue;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName;
 import jdk.graal.compiler.debug.DebugContext;
 
 import java.util.List;
@@ -159,7 +159,7 @@ private int writeMethodFrames(byte[] buffer, int p) {
         return pos;
     }
 
-    protected abstract int writeFDEs(int frameSize, List<RuntimeDebugInfoProvider.DebugFrameSizeChange> frameSizeInfos, byte[] buffer, int pos);
+    protected abstract int writeFDEs(int frameSize, List<DebugInfoProvider.DebugFrameSizeChange> frameSizeInfos, byte[] buffer, int pos);
 
     private int writeFDEHeader(long lo, long hi, byte[] buffer, int p) {
         /*
@@ -187,7 +187,7 @@ private int writeFDEHeader(long lo, long hi, byte[] buffer, int p) {
         /* CIE_offset */
         pos = writeInt(0, buffer, pos);
         /* Initial address. */
-        pos = writeRelocatableCodeOffset(lo, buffer, pos);
+        pos = writeLong(lo, buffer, pos);
         /* Address range. */
         return writeLong(hi - lo, buffer, pos);
     }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImplAArch64.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImplAArch64.java
index bbab12626a7b..952675254824 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImplAArch64.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImplAArch64.java
@@ -26,7 +26,7 @@
 
 package com.oracle.objectfile.runtime.dwarf;
 
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider;
+import com.oracle.objectfile.debuginfo.DebugInfoProvider;
 
 import java.util.List;
 
@@ -73,14 +73,14 @@ public int writeInitialInstructions(byte[] buffer, int p) {
     }
 
     @Override
-    protected int writeFDEs(int frameSize, List<RuntimeDebugInfoProvider.DebugFrameSizeChange> frameSizeInfos, byte[] buffer, int p) {
+    protected int writeFDEs(int frameSize, List<DebugInfoProvider.DebugFrameSizeChange> frameSizeInfos, byte[] buffer, int p) {
         int pos = p;
         int currentOffset = 0;
-        for (RuntimeDebugInfoProvider.DebugFrameSizeChange debugFrameSizeInfo : frameSizeInfos) {
+        for (DebugInfoProvider.DebugFrameSizeChange debugFrameSizeInfo : frameSizeInfos) {
             int advance = debugFrameSizeInfo.getOffset() - currentOffset;
             currentOffset += advance;
             pos = writeAdvanceLoc(advance, buffer, pos);
-            if (debugFrameSizeInfo.getType() == RuntimeDebugInfoProvider.DebugFrameSizeChange.Type.EXTEND) {
+            if (debugFrameSizeInfo.getType() == DebugInfoProvider.DebugFrameSizeChange.Type.EXTEND) {
                 /*
                  * SP has been extended so rebase CFA using full frame.
                  *
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImplX86_64.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImplX86_64.java
index 3662dbb3fb78..9fbeaa3949d6 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImplX86_64.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImplX86_64.java
@@ -26,7 +26,7 @@
 
 package com.oracle.objectfile.runtime.dwarf;
 
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider;
+import com.oracle.objectfile.debuginfo.DebugInfoProvider;
 
 import java.util.List;
 
@@ -84,14 +84,14 @@ public int writeInitialInstructions(byte[] buffer, int p) {
     }
 
     @Override
-    protected int writeFDEs(int frameSize, List<RuntimeDebugInfoProvider.DebugFrameSizeChange> frameSizeInfos, byte[] buffer, int p) {
+    protected int writeFDEs(int frameSize, List<DebugInfoProvider.DebugFrameSizeChange> frameSizeInfos, byte[] buffer, int p) {
         int pos = p;
         int currentOffset = 0;
-        for (RuntimeDebugInfoProvider.DebugFrameSizeChange debugFrameSizeInfo : frameSizeInfos) {
+        for (DebugInfoProvider.DebugFrameSizeChange debugFrameSizeInfo : frameSizeInfos) {
             int advance = debugFrameSizeInfo.getOffset() - currentOffset;
             currentOffset += advance;
             pos = writeAdvanceLoc(advance, buffer, pos);
-            if (debugFrameSizeInfo.getType() == RuntimeDebugInfoProvider.DebugFrameSizeChange.Type.EXTEND) {
+            if (debugFrameSizeInfo.getType() == DebugInfoProvider.DebugFrameSizeChange.Type.EXTEND) {
                 /*
                  * SP has been extended so rebase CFA using full frame.
                  *
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfInfoSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfInfoSectionImpl.java
index d72393e2fed6..39e416bc66d1 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfInfoSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfInfoSectionImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
@@ -26,64 +26,54 @@
 
 package com.oracle.objectfile.runtime.dwarf;
 
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocalInfo;
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocalValueInfo;
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugPrimitiveTypeInfo;
-import com.oracle.objectfile.runtime.debugentry.CompiledMethodEntry;
-import com.oracle.objectfile.runtime.debugentry.FileEntry;
-import com.oracle.objectfile.runtime.debugentry.MethodEntry;
-import com.oracle.objectfile.runtime.debugentry.PrimitiveTypeEntry;
-import com.oracle.objectfile.runtime.debugentry.TypeEntry;
-import com.oracle.objectfile.runtime.debugentry.TypedefEntry;
-import com.oracle.objectfile.runtime.debugentry.range.PrimaryRange;
-import com.oracle.objectfile.runtime.debugentry.range.Range;
-import com.oracle.objectfile.runtime.debugentry.range.SubRange;
-import com.oracle.objectfile.runtime.dwarf.RuntimeDwarfDebugInfo.AbbrevCode;
-import com.oracle.objectfile.runtime.dwarf.constants.DwarfAccess;
-import com.oracle.objectfile.runtime.dwarf.constants.DwarfEncoding;
-import com.oracle.objectfile.runtime.dwarf.constants.DwarfExpressionOpcode;
-import com.oracle.objectfile.runtime.dwarf.constants.DwarfFlag;
-import com.oracle.objectfile.runtime.dwarf.constants.DwarfInline;
-import com.oracle.objectfile.runtime.dwarf.constants.DwarfLanguage;
-import com.oracle.objectfile.runtime.dwarf.constants.DwarfSectionName;
-import com.oracle.objectfile.runtime.dwarf.constants.DwarfVersion;
-import jdk.graal.compiler.debug.DebugContext;
-import jdk.vm.ci.meta.JavaConstant;
-import jdk.vm.ci.meta.JavaKind;
-import jdk.vm.ci.meta.PrimitiveConstant;
-
 import java.lang.reflect.Modifier;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 
-import static com.oracle.objectfile.runtime.dwarf.RuntimeDwarfDebugInfo.LANG_ENCODING;
+import org.graalvm.collections.EconomicSet;
+
+import com.oracle.objectfile.debugentry.ClassEntry;
+import com.oracle.objectfile.debugentry.CompiledMethodEntry;
+import com.oracle.objectfile.debugentry.FileEntry;
+import com.oracle.objectfile.debugentry.MethodEntry;
+import com.oracle.objectfile.debugentry.TypeEntry;
+import com.oracle.objectfile.debugentry.range.Range;
+import com.oracle.objectfile.debugentry.range.SubRange;
+import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalInfo;
+import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalValueInfo;
+import com.oracle.objectfile.runtime.dwarf.RuntimeDwarfDebugInfo.AbbrevCode;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfAccess;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfEncoding;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfFlag;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfInline;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfLanguage;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfUnitHeader;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfVersion;
+
+import jdk.graal.compiler.debug.DebugContext;
+import jdk.vm.ci.meta.JavaConstant;
+import jdk.vm.ci.meta.JavaKind;
+import jdk.vm.ci.meta.PrimitiveConstant;
 
 /**
  * Section generator for debug_info section.
  */
 public class RuntimeDwarfInfoSectionImpl extends RuntimeDwarfSectionImpl {
-
     /**
      * An info header section always contains a fixed number of bytes.
      */
-    private static final int DIE_HEADER_SIZE = 11;
+    private static final int CU_DIE_HEADER_SIZE = 12;
 
-    /**
-     * Normally the offset of DWARF type declarations are tracked using the type/class entry
-     * properties but that means they are only available to be read during the second pass when
-     * filling in type cross-references. However, we need to use the offset of the void type during
-     * the first pass as the target of later-generated foreign pointer types. So, this field saves
-     * it up front.
-     */
-    private int cuStart;
+    private int unitStart;
 
     public RuntimeDwarfInfoSectionImpl(RuntimeDwarfDebugInfo dwarfSections) {
         // debug_info section depends on loc section
-        super(dwarfSections, DwarfSectionName.DW_INFO_SECTION, DwarfSectionName.DW_LOC_SECTION);
+        super(dwarfSections, DwarfSectionName.DW_INFO_SECTION, DwarfSectionName.DW_LOCLISTS_SECTION);
         // initialize CU start to an invalid value
-        cuStart = -1;
+        unitStart = -1;
     }
 
     @Override
@@ -113,182 +103,167 @@ public void writeContent(DebugContext context) {
         assert pos == size;
     }
 
-    DwarfEncoding computeEncoding(int flags, int bitCount) {
-        assert bitCount > 0;
-        if ((flags & DebugPrimitiveTypeInfo.FLAG_NUMERIC) != 0) {
-            if (((flags & DebugPrimitiveTypeInfo.FLAG_INTEGRAL) != 0)) {
-                if ((flags & DebugPrimitiveTypeInfo.FLAG_SIGNED) != 0) {
-                    switch (bitCount) {
-                        case 8:
-                            return DwarfEncoding.DW_ATE_signed_char;
-                        default:
-                            assert bitCount == 16 || bitCount == 32 || bitCount == 64;
-                            return DwarfEncoding.DW_ATE_signed;
-                    }
-                } else {
-                    assert bitCount == 16;
-                    return DwarfEncoding.DW_ATE_unsigned;
-                }
-            } else {
-                assert bitCount == 32 || bitCount == 64;
-                return DwarfEncoding.DW_ATE_float;
-            }
-        } else {
-            assert bitCount == 1;
-            return DwarfEncoding.DW_ATE_boolean;
-        }
-    }
-
-    // generate a single CU for the runtime compiled method
     public int generateContent(DebugContext context, byte[] buffer) {
         int pos = 0;
 
-        pos = writeCU(context, buffer, pos);
-
-        /* Write all primitive types and types referenced from build time debuginfo */
-        pos = writeTypes(context, buffer, pos);
-
-        /* Write the runtime compiled method */
-        pos = writeMethod(context, compiledMethod(), buffer, pos);
-
-        /* Write abstract inlined methods */
-        pos = writeAbstractInlineMethods(context, compiledMethod(), buffer, pos);
-
-        /*
-         * Write a terminating null attribute.
-         */
-        pos = writeAttrNull(buffer, pos);
-
-        patchLength(0, buffer, pos);
-        return pos;
-    }
+        CompiledMethodEntry compiledMethodEntry = compiledMethod();
+        ClassEntry classEntry = compiledMethodEntry.getClassEntry();
 
-    public int writeCU(DebugContext context, byte[] buffer, int p) {
-        int pos = p;
+        // Write a Java class unit header
+        int lengthPos = pos;
+        log(context, "  [0x%08x] Instance class unit", pos);
         pos = writeCUHeader(buffer, pos);
-        assert pos == p + DIE_HEADER_SIZE;
-        AbbrevCode abbrevCode = AbbrevCode.METHOD_UNIT;
+        assert pos == lengthPos + CU_DIE_HEADER_SIZE;
+        AbbrevCode abbrevCode = AbbrevCode.CLASS_UNIT;
         log(context, "  [0x%08x] <0> Abbrev Number %d", pos, abbrevCode.ordinal());
         pos = writeAbbrevCode(abbrevCode, buffer, pos);
         log(context, "  [0x%08x]     language  %s", pos, "DW_LANG_Java");
-        pos = writeAttrLanguage(LANG_ENCODING, buffer, pos);
+        pos = writeAttrLanguage(RuntimeDwarfDebugInfo.LANG_ENCODING, buffer, pos);
         log(context, "  [0x%08x]     use_UTF8", pos);
         pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos);
-        String name = uniqueDebugString(dwarfSections.cuName());
+        String name = classEntry.getFullFileName();
+        if (name == null) {
+            name = classEntry.getTypeName().replace('.', '/') + ".java";
+        }
         log(context, "  [0x%08x]     name  0x%x (%s)", pos, debugStringIndex(name), name);
-        pos = writeStrSectionOffset(name, buffer, pos);
-        String compilationDirectory = uniqueDebugString(dwarfSections.getCachePath());
+        pos = writeStrSectionOffset(uniqueDebugString(name), buffer, pos);
+        String compilationDirectory = dwarfSections.getCachePath();
         log(context, "  [0x%08x]     comp_dir  0x%x (%s)", pos, debugStringIndex(compilationDirectory), compilationDirectory);
         pos = writeStrSectionOffset(compilationDirectory, buffer, pos);
-        long lo = compiledMethod().getPrimary().getLo();
+        long lo = classEntry.lowpc();
+        long hi = classEntry.hipc();
         log(context, "  [0x%08x]     low_pc  0x%x", pos, lo);
-        pos = writeAttrAddress(lo, buffer, pos);
-        long hi = compiledMethod().getPrimary().getHi();
-        log(context, "  [0x%08x]     high_pc  0x%x", pos, hi);
-        pos = writeAttrAddress(hi, buffer, pos);
-        int lineIndex = 0; // getLineIndex(compiledMethodEntry);
+        pos = writeLong(lo, buffer, pos);
+        log(context, "  [0x%08x]     hi_pc  0x%x", pos, hi);
+        pos = writeLong(hi, buffer, pos);
+        int lineIndex = getLineIndex(classEntry);
         log(context, "  [0x%08x]     stmt_list  0x%x", pos, lineIndex);
-        return writeLineSectionOffset(lineIndex, buffer, pos);
-    }
-
-    public int writeTypes(DebugContext context, byte[] buffer, int p) {
-        int pos = p;
-        // write primitives
-        for (PrimitiveTypeEntry primitiveType : primitiveTypes()) {
-            pos = writePrimitiveType(context, primitiveType, buffer, pos);
+        pos = writeLineSectionOffset(lineIndex, buffer, pos);
+        int locationListIndex = getLocationListIndex(classEntry);
+        log(context, "  [0x%08x]     loclists_base  0x%x", pos, locationListIndex);
+        pos = writeLocSectionOffset(locationListIndex, buffer, pos);
+        /* if the class has a loader then embed the children in a namespace */
+        String loaderId = classEntry.getLoaderId();
+        if (!loaderId.isEmpty()) {
+            pos = writeNameSpace(context, loaderId, buffer, pos);
         }
 
-        // write typedefs
-        for (TypedefEntry typedef : typedefs()) {
-            pos = writeTypedef(context, typedef, buffer, pos);
+        /* Now write the child DIEs starting with the layout and pointer type. */
+
+        // this works for interfaces, foreign types and classes, entry kind specifics are in the
+        // type units
+        pos = writeSkeletonClassLayout(context, classEntry, compiledMethodEntry, buffer, pos);
+
+        /* Write all compiled code locations */
+
+        pos = writeMethodLocation(context, classEntry, compiledMethodEntry, buffer, pos);
+
+        /* Write abstract inline methods. */
+
+        pos = writeAbstractInlineMethods(context, classEntry, compiledMethodEntry, buffer, pos);
+
+        /* if we opened a namespace then terminate its children */
+        if (!loaderId.isEmpty()) {
+            pos = writeAttrNull(buffer, pos);
         }
+
+        /*
+         * Write a terminating null attribute.
+         */
+        pos = writeAttrNull(buffer, pos);
+
+        /* Fix up the CU length. */
+        patchLength(lengthPos, buffer, pos);
         return pos;
     }
 
-    public int writePrimitiveType(DebugContext context, PrimitiveTypeEntry primitiveTypeEntry, byte[] buffer, int p) {
-        assert primitiveTypeEntry.getBitCount() > 0;
+    private int writeSkeletonClassLayout(DebugContext context, ClassEntry classEntry, CompiledMethodEntry compiledMethodEntry, byte[] buffer, int p) {
         int pos = p;
-        log(context, "  [0x%08x] primitive type %s", pos, primitiveTypeEntry.getTypeName());
-        /* Record the location of this type entry. */
-        setTypeIndex(primitiveTypeEntry, pos);
-
-        AbbrevCode abbrevCode = AbbrevCode.PRIMITIVE_TYPE;
+        log(context, "  [0x%08x] class layout", pos);
+        AbbrevCode abbrevCode = AbbrevCode.CLASS_LAYOUT;
         log(context, "  [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal());
         pos = writeAbbrevCode(abbrevCode, buffer, pos);
-        byte byteSize = (byte) primitiveTypeEntry.getSize();
-        log(context, "  [0x%08x]     byte_size  %d", pos, byteSize);
-        pos = writeAttrData1(byteSize, buffer, pos);
-        byte bitCount = (byte) primitiveTypeEntry.getBitCount();
-        log(context, "  [0x%08x]     bitCount  %d", pos, bitCount);
-        pos = writeAttrData1(bitCount, buffer, pos);
-        DwarfEncoding encoding = computeEncoding(primitiveTypeEntry.getFlags(), bitCount);
-        log(context, "  [0x%08x]     encoding  0x%x", pos, encoding.value());
-        pos = writeAttrEncoding(encoding, buffer, pos);
-        String name = uniqueDebugString(primitiveTypeEntry.getTypeName());
+        String name = classEntry.getTypeName();
         log(context, "  [0x%08x]     name  0x%x (%s)", pos, debugStringIndex(name), name);
-        return writeStrSectionOffset(name, buffer, pos);
+        pos = writeStrSectionOffset(name, buffer, pos);
+        log(context, "  [0x%08x]     declaration true", pos);
+        pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos);
+        long typeSignature = classEntry.getLayoutTypeSignature();
+        log(context, "  [0x%08x]     type specification 0x%x", pos, typeSignature);
+        pos = writeTypeSignature(typeSignature, buffer, pos);
+
+        pos = writeMethodDeclaration(context, classEntry, compiledMethodEntry.getPrimary().getMethodEntry(), false, buffer, pos);
+        /*
+         * Write a terminating null attribute.
+         */
+        return writeAttrNull(buffer, pos);
     }
 
-    public int writeTypedef(DebugContext context, TypedefEntry typedefEntry, byte[] buffer, int p) {
+    private int writeNameSpace(DebugContext context, String id, byte[] buffer, int p) {
         int pos = p;
-        log(context, "  [0x%08x] typedef %s", pos, typedefEntry.getTypeName());
-        int typedefIndex = pos;
-        AbbrevCode abbrevCode = AbbrevCode.TYPEDEF;
+        String name = uniqueDebugString(id);
+        assert !id.isEmpty();
+        log(context, "  [0x%08x] namespace %s", pos, name);
+        AbbrevCode abbrevCode = AbbrevCode.NAMESPACE;
         log(context, "  [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal());
         pos = writeAbbrevCode(abbrevCode, buffer, pos);
-        String name = uniqueDebugString(typedefEntry.getTypeName());
         log(context, "  [0x%08x]     name  0x%x (%s)", pos, debugStringIndex(name), name);
         pos = writeStrSectionOffset(name, buffer, pos);
-
-
-        log(context, "  [0x%08x] typedef pointer type %s", pos, typedefEntry.getTypeName());
-        /* Record the location of this type entry. */
-        setTypeIndex(typedefEntry, pos);
-        abbrevCode = AbbrevCode.TYPEDEF_POINTER;
-        log(context, "  [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal());
-        pos = writeAbbrevCode(abbrevCode, buffer, pos);
-        int pointerSize = dwarfSections.pointerSize();
-        log(context, "  [0x%08x]     byte_size 0x%x", pos, pointerSize);
-        pos = writeAttrData1((byte) pointerSize, buffer, pos);
-        log(context, "  [0x%08x]     type 0x%x", pos, typedefIndex);
-        return writeInfoSectionOffset(typedefIndex, buffer, pos);
+        return pos;
     }
 
-    public int writeMethod(DebugContext context, CompiledMethodEntry compiledEntry, byte[] buffer, int p) {
+    private int writeMethodDeclaration(DebugContext context, ClassEntry classEntry, MethodEntry method, boolean isInlined, byte[] buffer, int p) {
         int pos = p;
-
-        MethodEntry methodEntry = compiledEntry.getPrimary().getMethodEntry();
-        String methodKey = uniqueDebugString(methodEntry.getSymbolName());
-        setMethodDeclarationIndex(methodEntry, pos);
-        int modifiers = methodEntry.getModifiers();
+        String methodKey = method.getSymbolName();
+        String linkageName = uniqueDebugString(methodKey);
+        setMethodDeclarationIndex(method, pos);
+        int modifiers = method.getModifiers();
         boolean isStatic = Modifier.isStatic(modifiers);
-
-        log(context, "  [0x%08x] method location %s::%s", pos, methodEntry.ownerType().getTypeName(), methodEntry.methodName());
-        AbbrevCode abbrevCode = (isStatic ? AbbrevCode.METHOD_LOCATION_STATIC : AbbrevCode.METHOD_LOCATION);
+        log(context, "  [0x%08x] method declaration %s::%s", pos, classEntry.getTypeName(), method.methodName());
+        AbbrevCode abbrevCode;
+        if (isInlined) {
+            abbrevCode = (isStatic ? AbbrevCode.METHOD_DECLARATION_INLINE_STATIC : AbbrevCode.METHOD_DECLARATION_INLINE);
+        } else {
+            abbrevCode = (isStatic ? AbbrevCode.METHOD_DECLARATION_STATIC : AbbrevCode.METHOD_DECLARATION);
+        }
         log(context, "  [0x%08x] <2> Abbrev Number %d", pos, abbrevCode.ordinal());
         pos = writeAbbrevCode(abbrevCode, buffer, pos);
+        if (isInlined) {
+            log(context, "  [0x%08x]     inline  0x%x", pos, DwarfInline.DW_INL_inlined.value());
+            pos = writeAttrInline(DwarfInline.DW_INL_inlined, buffer, pos);
+        }
         log(context, "  [0x%08x]     external  true", pos);
         pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos);
-        String name = uniqueDebugString(methodEntry.methodName());
+        String name = uniqueDebugString(method.methodName());
         log(context, "  [0x%08x]     name 0x%x (%s)", pos, debugStringIndex(name), name);
         pos = writeStrSectionOffset(name, buffer, pos);
-        Range primary = compiledEntry.getPrimary();
-        log(context, "  [0x%08x]     lo_pc  0x%08x", pos, primary.getLo());
-        pos = writeAttrAddress(primary.getLo(), buffer, pos);
-        log(context, "  [0x%08x]     hi_pc  0x%08x", pos, primary.getHi());
-        pos = writeAttrAddress(primary.getHi(), buffer, pos);
-        TypeEntry returnType = methodEntry.getValueType();
-        int retTypeIdx = getTypeIndex(returnType);
-        log(context, "  [0x%08x]     type 0x%x (%s)", pos, retTypeIdx, returnType.getTypeName());
-        pos = writeInfoSectionOffset(retTypeIdx, buffer, pos);
-        log(context, "  [0x%08x]     artificial %s", pos, methodEntry.isDeopt() ? "true" : "false");
-        pos = writeFlag((methodEntry.isDeopt() ? DwarfFlag.DW_FLAG_true : DwarfFlag.DW_FLAG_false), buffer, pos);
+        FileEntry fileEntry = method.getFileEntry();
+        if (fileEntry == null) {
+            fileEntry = classEntry.getFileEntry();
+        }
+        assert fileEntry != null;
+        int fileIdx = classEntry.getFileIdx(fileEntry);
+        log(context, "  [0x%08x]     file 0x%x (%s)", pos, fileIdx, fileEntry.getFullName());
+        pos = writeAttrData2((short) fileIdx, buffer, pos);
+        int line = method.getLine();
+        log(context, "  [0x%08x]     line 0x%x", pos, line);
+        pos = writeAttrData2((short) line, buffer, pos);
+        log(context, "  [0x%08x]     linkage_name %s", pos, linkageName);
+        pos = writeStrSectionOffset(linkageName, buffer, pos);
+        TypeEntry returnType = method.getValueType();
+        long retTypeSignature = returnType.getTypeSignature();
+        log(context, "  [0x%08x]     type 0x%x (%s)", pos, retTypeSignature, returnType.getTypeName());
+        pos = writeTypeSignature(retTypeSignature, buffer, pos);
+        log(context, "  [0x%08x]     artificial %s", pos, method.isDeopt() ? "true" : "false");
+        pos = writeFlag((method.isDeopt() ? DwarfFlag.DW_FLAG_true : DwarfFlag.DW_FLAG_false), buffer, pos);
         log(context, "  [0x%08x]     accessibility %s", pos, "public");
         pos = writeAttrAccessibility(modifiers, buffer, pos);
-        int typeIdx = getTypeIndex(methodEntry.ownerType());
-        log(context, "  [0x%08x]     containing_type 0x%x (%s)", pos, typeIdx, methodEntry.ownerType().getTypeName());
-        pos = writeInfoSectionOffset(typeIdx, buffer, pos); //writeAttrRef4(typeIdx, buffer, pos);
-        if (!isStatic) {
+        log(context, "  [0x%08x]     declaration true", pos);
+        pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos);
+        long typeSignature = classEntry.getLayoutTypeSignature();
+        log(context, "  [0x%08x]     containing_type 0x%x (%s)", pos, typeSignature, classEntry.getTypeName());
+        pos = writeTypeSignature(typeSignature, buffer, pos);
+        if (abbrevCode == AbbrevCode.METHOD_DECLARATION || abbrevCode == AbbrevCode.METHOD_DECLARATION_INLINE) {
             /* Record the current position so we can back patch the object pointer. */
             int objectPointerIndex = pos;
             /*
@@ -301,29 +276,151 @@ public int writeMethod(DebugContext context, CompiledMethodEntry compiledEntry,
             log(context, "  [0x%08x]     object_pointer 0x%x", objectPointerIndex, pos);
             writeAttrRef4(pos, buffer, objectPointerIndex);
         }
-
-        HashMap<DebugLocalInfo, List<SubRange>> varRangeMap = primary.getVarRangeMap();
         /* Write method parameter declarations. */
-        pos = writeMethodParameterLocations(context, compiledEntry, varRangeMap, primary, 2, buffer, pos);
+        pos = writeMethodParameterDeclarations(context, classEntry, method, fileIdx, 3, buffer, pos);
         /* write method local declarations */
-        pos = writeMethodLocalLocations(context, compiledEntry, varRangeMap, primary, 2, buffer, pos);
+        pos = writeMethodLocalDeclarations(context, classEntry, method, fileIdx, 3, buffer, pos);
+        /*
+         * Write a terminating null attribute.
+         */
+        return writeAttrNull(buffer, pos);
+    }
 
+    private int writeMethodParameterDeclarations(DebugContext context, ClassEntry classEntry, MethodEntry method, int fileIdx, int level, byte[] buffer, int p) {
+        int pos = p;
+        int refAddr;
+        if (!Modifier.isStatic(method.getModifiers())) {
+            refAddr = pos;
+            DebugLocalInfo paramInfo = method.getThisParam();
+            setMethodLocalIndex(classEntry, method, paramInfo, refAddr);
+            pos = writeMethodParameterDeclaration(context, paramInfo, fileIdx, true, level, buffer, pos);
+        }
+        for (int i = 0; i < method.getParamCount(); i++) {
+            refAddr = pos;
+            DebugLocalInfo paramInfo = method.getParam(i);
+            setMethodLocalIndex(classEntry, method, paramInfo, refAddr);
+            pos = writeMethodParameterDeclaration(context, paramInfo, fileIdx, false, level, buffer, pos);
+        }
+        return pos;
+    }
+
+    private int writeMethodParameterDeclaration(DebugContext context, DebugLocalInfo paramInfo, int fileIdx, boolean artificial, int level, byte[] buffer,
+                                                int p) {
+        int pos = p;
+        log(context, "  [0x%08x] method parameter declaration", pos);
+        AbbrevCode abbrevCode;
+        String paramName = paramInfo.name();
+        TypeEntry paramType = lookupType(paramInfo.valueType());
+        int line = paramInfo.line();
+        if (artificial) {
+            abbrevCode = AbbrevCode.METHOD_PARAMETER_DECLARATION_1;
+        } else if (line >= 0) {
+            abbrevCode = AbbrevCode.METHOD_PARAMETER_DECLARATION_2;
+        } else {
+            abbrevCode = AbbrevCode.METHOD_PARAMETER_DECLARATION_3;
+        }
+        log(context, "  [0x%08x] <%d> Abbrev Number %d", pos, level, abbrevCode.ordinal());
+        pos = writeAbbrevCode(abbrevCode, buffer, pos);
+        log(context, "  [0x%08x]     name %s", pos, paramName);
+        pos = writeStrSectionOffset(uniqueDebugString(paramName), buffer, pos);
+        if (abbrevCode == AbbrevCode.METHOD_PARAMETER_DECLARATION_2) {
+            log(context, "  [0x%08x]     file 0x%x", pos, fileIdx);
+            pos = writeAttrData2((short) fileIdx, buffer, pos);
+            log(context, "  [0x%08x]     line 0x%x", pos, line);
+            pos = writeAttrData2((short) line, buffer, pos);
+        }
+        long typeSignature = paramType.getTypeSignature();
+        log(context, "  [0x%08x]     type 0x%x (%s)", pos, typeSignature, paramType.getTypeName());
+        pos = writeTypeSignature(typeSignature, buffer, pos);
+        if (abbrevCode == AbbrevCode.METHOD_PARAMETER_DECLARATION_1) {
+            log(context, "  [0x%08x]     artificial true", pos);
+            pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos);
+        }
+        log(context, "  [0x%08x]     declaration true", pos);
+        pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos);
+        return pos;
+    }
+
+    private int writeMethodLocalDeclarations(DebugContext context, ClassEntry classEntry, MethodEntry method, int fileIdx, int level, byte[] buffer, int p) {
+        int pos = p;
+        int refAddr;
+        for (int i = 0; i < method.getLocalCount(); i++) {
+            refAddr = pos;
+            DebugLocalInfo localInfo = method.getLocal(i);
+            setMethodLocalIndex(classEntry, method, localInfo, refAddr);
+            pos = writeMethodLocalDeclaration(context, localInfo, fileIdx, level, buffer, pos);
+        }
+        return pos;
+    }
+
+    private int writeMethodLocalDeclaration(DebugContext context, DebugLocalInfo paramInfo, int fileIdx, int level, byte[] buffer,
+                                            int p) {
+        int pos = p;
+        log(context, "  [0x%08x] method local declaration", pos);
+        AbbrevCode abbrevCode;
+        String paramName = paramInfo.name();
+        TypeEntry paramType = lookupType(paramInfo.valueType());
+        int line = paramInfo.line();
+        if (line >= 0) {
+            abbrevCode = AbbrevCode.METHOD_LOCAL_DECLARATION_1;
+        } else {
+            abbrevCode = AbbrevCode.METHOD_LOCAL_DECLARATION_2;
+        }
+        log(context, "  [0x%08x] <%d> Abbrev Number %d", pos, level, abbrevCode.ordinal());
+        pos = writeAbbrevCode(abbrevCode, buffer, pos);
+        log(context, "  [0x%08x]     name %s", pos, paramName);
+        pos = writeStrSectionOffset(uniqueDebugString(paramName), buffer, pos);
+        if (abbrevCode == AbbrevCode.METHOD_LOCAL_DECLARATION_1) {
+            log(context, "  [0x%08x]     file 0x%x", pos, fileIdx);
+            pos = writeAttrData2((short) fileIdx, buffer, pos);
+            log(context, "  [0x%08x]     line 0x%x", pos, line);
+            pos = writeAttrData2((short) line, buffer, pos);
+        }
+        long typeSignature = paramType.getTypeSignature();
+        log(context, "  [0x%08x]     type 0x%x (%s)", pos, typeSignature, paramType.getTypeName());
+        pos = writeTypeSignature(typeSignature, buffer, pos);
+        log(context, "  [0x%08x]     declaration true", pos);
+        pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos);
+        return pos;
+    }
+
+    private int writeMethodLocation(DebugContext context, ClassEntry classEntry, CompiledMethodEntry compiledEntry, byte[] buffer, int p) {
+        int pos = p;
+        Range primary = compiledEntry.getPrimary();
+        log(context, "  [0x%08x] method location", pos);
+        AbbrevCode abbrevCode = AbbrevCode.METHOD_LOCATION;
+        log(context, "  [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal());
+        pos = writeAbbrevCode(abbrevCode, buffer, pos);
+        log(context, "  [0x%08x]     lo_pc  0x%08x", pos, primary.getLo());
+        pos = writeLong(primary.getLo(), buffer, pos);
+        log(context, "  [0x%08x]     hi_pc  0x%08x", pos, primary.getHi());
+        pos = writeLong(primary.getHi(), buffer, pos);
+        /*
+         * Should pass true only if method is non-private.
+         */
+        log(context, "  [0x%08x]     external  true", pos);
+        pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos);
+        String methodKey = primary.getSymbolName();
+        int methodSpecOffset = getMethodDeclarationIndex(primary.getMethodEntry());
+        log(context, "  [0x%08x]     specification  0x%x (%s)", pos, methodSpecOffset, methodKey);
+        pos = writeInfoSectionOffset(methodSpecOffset, buffer, pos);
+        HashMap<DebugLocalInfo, List<SubRange>> varRangeMap = primary.getVarRangeMap();
+        pos = writeMethodParameterLocations(context, classEntry, varRangeMap, primary, 2, buffer, pos);
+        pos = writeMethodLocalLocations(context, classEntry, varRangeMap, primary, 2, buffer, pos);
         if (primary.includesInlineRanges()) {
             /*
              * the method has inlined ranges so write concrete inlined method entries as its
              * children
              */
-            pos = generateConcreteInlinedMethods(context, compiledEntry, buffer, pos);
+            pos = generateConcreteInlinedMethods(context, classEntry, compiledEntry, buffer, pos);
         }
-
         /*
          * Write a terminating null attribute.
          */
         return writeAttrNull(buffer, pos);
     }
 
-
-    private int writeMethodParameterLocations(DebugContext context, CompiledMethodEntry compiledEntry, HashMap<DebugLocalInfo, List<SubRange>> varRangeMap, Range range, int depth, byte[] buffer, int p) {
+    private int writeMethodParameterLocations(DebugContext context, ClassEntry classEntry, HashMap<DebugLocalInfo, List<SubRange>> varRangeMap, Range range, int depth, byte[] buffer, int p) {
         int pos = p;
         MethodEntry methodEntry;
         if (range.isPrimary()) {
@@ -334,18 +431,20 @@ private int writeMethodParameterLocations(DebugContext context, CompiledMethodEn
         }
         if (!Modifier.isStatic(methodEntry.getModifiers())) {
             DebugLocalInfo thisParamInfo = methodEntry.getThisParam();
+            int refAddr = getMethodLocalIndex(classEntry, methodEntry, thisParamInfo);
             List<SubRange> ranges = varRangeMap.get(thisParamInfo);
-            pos = writeMethodLocalLocation(context, range, thisParamInfo, ranges, depth, true, buffer, pos);
+            pos = writeMethodLocalLocation(context, range, thisParamInfo, refAddr, ranges, depth, true, buffer, pos);
         }
         for (int i = 0; i < methodEntry.getParamCount(); i++) {
             DebugLocalInfo paramInfo = methodEntry.getParam(i);
+            int refAddr = getMethodLocalIndex(classEntry, methodEntry, paramInfo);
             List<SubRange> ranges = varRangeMap.get(paramInfo);
-            pos = writeMethodLocalLocation(context, range, paramInfo, ranges, depth, true, buffer, pos);
+            pos = writeMethodLocalLocation(context, range, paramInfo, refAddr, ranges, depth, true, buffer, pos);
         }
         return pos;
     }
 
-    private int writeMethodLocalLocations(DebugContext context, CompiledMethodEntry compiledEntry, HashMap<DebugLocalInfo, List<SubRange>> varRangeMap, Range range, int depth, byte[] buffer, int p) {
+    private int writeMethodLocalLocations(DebugContext context, ClassEntry classEntry, HashMap<DebugLocalInfo, List<SubRange>> varRangeMap, Range range, int depth, byte[] buffer, int p) {
         int pos = p;
         MethodEntry methodEntry;
         if (range.isPrimary()) {
@@ -357,13 +456,14 @@ private int writeMethodLocalLocations(DebugContext context, CompiledMethodEntry
         int count = methodEntry.getLocalCount();
         for (int i = 0; i < count; i++) {
             DebugLocalInfo localInfo = methodEntry.getLocal(i);
+            int refAddr = getMethodLocalIndex(classEntry, methodEntry, localInfo);
             List<SubRange> ranges = varRangeMap.get(localInfo);
-            pos = writeMethodLocalLocation(context, range, localInfo, ranges, depth, false, buffer, pos);
+            pos = writeMethodLocalLocation(context, range, localInfo, refAddr, ranges, depth, false, buffer, pos);
         }
         return pos;
     }
 
-    private int writeMethodLocalLocation(DebugContext context, Range range, DebugLocalInfo localInfo, List<SubRange> ranges, int depth, boolean isParam, byte[] buffer,
+    private int writeMethodLocalLocation(DebugContext context, Range range, DebugLocalInfo localInfo, int refAddr, List<SubRange> ranges, int depth, boolean isParam, byte[] buffer,
                                          int p) {
         int pos = p;
         log(context, "  [0x%08x] method %s location %s:%s", pos, (isParam ? "parameter" : "local"), localInfo.name(), localInfo.typeName());
@@ -397,20 +497,13 @@ private int writeMethodLocalLocation(DebugContext context, Range range, DebugLoc
         }
         log(context, "  [0x%08x] <%d> Abbrev Number %d", pos, depth, abbrevCode.ordinal());
         pos = writeAbbrevCode(abbrevCode, buffer, pos);
-
-
-        String name = uniqueDebugString(localInfo.name());
-        log(context, "  [0x%08x]     name 0x%x (%s)", pos, debugStringIndex(name), name);
-        pos = writeStrSectionOffset(name, buffer, pos);
-        TypeEntry valueType = lookupType(localInfo.valueType());
-        int retTypeIdx = getTypeIndex(valueType);
-        log(context, "  [0x%08x]     type 0x%x (%s)", pos, retTypeIdx, valueType.getTypeName());
-        pos = writeInfoSectionOffset(retTypeIdx, buffer, pos);
-
-        if (!localValues.isEmpty()) {
-            int locRefAddr = getRangeLocalIndex(range, localInfo);
-            log(context, "  [0x%08x]     loc list  0x%x", pos, locRefAddr);
-            pos = writeLocSectionOffset(locRefAddr, buffer, pos);
+        log(context, "  [0x%08x]     specification  0x%x", pos, refAddr);
+        pos = writeInfoSectionOffset(refAddr, buffer, pos);
+        if (abbrevCode == AbbrevCode.METHOD_LOCAL_LOCATION_2 ||
+                abbrevCode == AbbrevCode.METHOD_PARAMETER_LOCATION_2) {
+            int locRefOffset = getRangeLocalIndex(range, localInfo);
+            log(context, "  [0x%08x]     loc list  0x%x", pos, locRefOffset);
+            pos = writeULEB(locRefOffset, buffer, pos);
         }
         return pos;
     }
@@ -418,7 +511,7 @@ private int writeMethodLocalLocation(DebugContext context, Range range, DebugLoc
     /**
      * Go through the subranges and generate concrete debug entries for inlined methods.
      */
-    private int generateConcreteInlinedMethods(DebugContext context, CompiledMethodEntry compiledEntry, byte[] buffer, int p) {
+    private int generateConcreteInlinedMethods(DebugContext context, ClassEntry classEntry, CompiledMethodEntry compiledEntry, byte[] buffer, int p) {
         Range primary = compiledEntry.getPrimary();
         if (primary.isLeaf()) {
             return p;
@@ -439,12 +532,12 @@ private int generateConcreteInlinedMethods(DebugContext context, CompiledMethodE
                 depth--;
             }
             depth = subrange.getDepth();
-            pos = writeInlineSubroutine(context, compiledEntry, subrange, depth + 2, buffer, pos);
+            pos = writeInlineSubroutine(context, classEntry, subrange, depth + 2, buffer, pos);
             HashMap<DebugLocalInfo, List<SubRange>> varRangeMap = subrange.getVarRangeMap();
             // increment depth to account for parameter and method locations
             depth++;
-            pos = writeMethodParameterLocations(context, compiledEntry, varRangeMap, subrange, depth + 2, buffer, pos);
-            pos = writeMethodLocalLocations(context, compiledEntry, varRangeMap, subrange, depth + 2, buffer, pos);
+            pos = writeMethodParameterLocations(context, classEntry, varRangeMap, subrange, depth + 2, buffer, pos);
+            pos = writeMethodLocalLocations(context, classEntry, varRangeMap, subrange, depth + 2, buffer, pos);
         }
         // if we just stepped out of a child range write nulls for each step up
         while (depth > 0) {
@@ -454,7 +547,7 @@ private int generateConcreteInlinedMethods(DebugContext context, CompiledMethodE
         return pos;
     }
 
-    private int writeInlineSubroutine(DebugContext context, CompiledMethodEntry compiledEntry, SubRange caller, int depth, byte[] buffer, int p) {
+    private int writeInlineSubroutine(DebugContext context, ClassEntry classEntry, SubRange caller, int depth, byte[] buffer, int p) {
         assert !caller.isLeaf();
         // the supplied range covers an inline call and references the caller method entry. its
         // child ranges all reference the same inlined called method. leaf children cover code for
@@ -462,25 +555,39 @@ private int writeInlineSubroutine(DebugContext context, CompiledMethodEntry comp
         // identify the inlined method by looking at the first callee
         Range callee = caller.getFirstCallee();
         MethodEntry methodEntry = callee.getMethodEntry();
-        String methodKey = uniqueDebugString(methodEntry.getSymbolName());
+        String methodKey = methodEntry.getSymbolName();
         /* the abstract index was written in the method's class entry */
-        int abstractOriginIndex = getAbstractInlineMethodIndex(compiledEntry, methodEntry); // getMethodDeclarationIndex(methodEntry);
+        int abstractOriginIndex = getAbstractInlineMethodIndex(classEntry, methodEntry);
 
         int pos = p;
         log(context, "  [0x%08x] concrete inline subroutine [0x%x, 0x%x] %s", pos, caller.getLo(), caller.getHi(), methodKey);
 
         int callLine = caller.getLine();
         assert callLine >= -1 : callLine;
-        int fileIndex = 0;
-        final AbbrevCode abbrevCode = AbbrevCode.INLINED_SUBROUTINE;
+        int fileIndex;
+        if (callLine == -1) {
+            log(context, "  Unable to retrieve call line for inlined method %s", callee.getFullMethodName());
+            /* continue with line 0 and fileIndex 1 as we must insert a tree node */
+            callLine = 0;
+            fileIndex = classEntry.getFileIdx();
+        } else {
+            FileEntry subFileEntry = caller.getFileEntry();
+            if (subFileEntry != null) {
+                fileIndex = classEntry.getFileIdx(subFileEntry);
+            } else {
+                log(context, "  Unable to retrieve caller FileEntry for inlined method %s (caller method %s)", callee.getFullMethodName(), caller.getFullMethodName());
+                fileIndex = classEntry.getFileIdx();
+            }
+        }
+        final AbbrevCode abbrevCode = AbbrevCode.INLINED_SUBROUTINE_WITH_CHILDREN;
         log(context, "  [0x%08x] <%d> Abbrev Number %d", pos, depth, abbrevCode.ordinal());
         pos = writeAbbrevCode(abbrevCode, buffer, pos);
         log(context, "  [0x%08x]     abstract_origin  0x%x", pos, abstractOriginIndex);
         pos = writeAttrRef4(abstractOriginIndex, buffer, pos);
         log(context, "  [0x%08x]     lo_pc  0x%08x", pos, caller.getLo());
-        pos = writeAttrAddress(caller.getLo(), buffer, pos);
+        pos = writeLong(caller.getLo(), buffer, pos);
         log(context, "  [0x%08x]     hi_pc  0x%08x", pos, caller.getHi());
-        pos = writeAttrAddress(caller.getHi(), buffer, pos);
+        pos = writeLong(caller.getHi(), buffer, pos);
         log(context, "  [0x%08x]     call_file  %d", pos, fileIndex);
         pos = writeAttrData4(fileIndex, buffer, pos);
         log(context, "  [0x%08x]     call_line  %d", pos, callLine);
@@ -488,11 +595,29 @@ private int writeInlineSubroutine(DebugContext context, CompiledMethodEntry comp
         return pos;
     }
 
-    private int writeAbstractInlineMethods(DebugContext context, CompiledMethodEntry compiledEntry, byte[] buffer, int p) {
+    private int writeAbstractInlineMethods(DebugContext context, ClassEntry classEntry, CompiledMethodEntry compiledMethodEntry, byte[] buffer, int p) {
+        EconomicSet<MethodEntry> inlinedMethods = collectInlinedMethods(context, compiledMethodEntry, p);
         int pos = p;
-        PrimaryRange primary = compiledEntry.getPrimary();
+        for (MethodEntry methodEntry : inlinedMethods) {
+            // n.b. class entry used to index the method belongs to the inlining method
+            // not the inlined method
+            setAbstractInlineMethodIndex(classEntry, methodEntry, pos);
+            // we need the full method declaration for inlined methods
+            pos = writeMethodDeclaration(context, classEntry, methodEntry, true, buffer, pos);
+            //writeAbstractInlineMethod(context, classEntry, methodEntry, buffer, pos);
+        }
+        return pos;
+    }
+
+    private EconomicSet<MethodEntry> collectInlinedMethods(DebugContext context, CompiledMethodEntry compiledMethodEntry, int p) {
+        final EconomicSet<MethodEntry> methods = EconomicSet.create();
+        addInlinedMethods(context, compiledMethodEntry, compiledMethodEntry.getPrimary(), methods, p);
+        return methods;
+    }
+
+    private void addInlinedMethods(DebugContext context, CompiledMethodEntry compiledEntry, Range primary, EconomicSet<MethodEntry> hashSet, int p) {
         if (primary.isLeaf()) {
-            return pos;
+            return;
         }
         verboseLog(context, "  [0x%08x] collect abstract inlined methods %s", p, primary.getFullMethodName());
         Iterator<SubRange> iterator = compiledEntry.topDownRangeIterator();
@@ -509,19 +634,16 @@ private int writeAbstractInlineMethods(DebugContext context, CompiledMethodEntry
             // identify the inlined method by looking at the first callee
             Range callee = subrange.getFirstCallee();
             MethodEntry methodEntry = callee.getMethodEntry();
-
-            // n.b. class entry used to index the method belongs to the inlining method
-            // not the inlined method
-            setAbstractInlineMethodIndex(compiledEntry, methodEntry, pos);
-            pos = writeAbstractInlineMethod(context, compiledEntry, methodEntry, buffer, pos);
+            if (hashSet.add(methodEntry)) {
+                verboseLog(context, "  [0x%08x]   add abstract inlined method %s", p, methodEntry.getSymbolName());
+            }
         }
-        return pos;
     }
 
-    private int writeAbstractInlineMethod(DebugContext context, CompiledMethodEntry compiledEntry, MethodEntry method, byte[] buffer, int p) {
+    private int writeAbstractInlineMethod(DebugContext context, ClassEntry classEntry, MethodEntry method, byte[] buffer, int p) {
         int pos = p;
-        log(context, "  [0x%08x] abstract inline method %s", pos, method.methodName());
-        AbbrevCode abbrevCode = AbbrevCode.ABSTRACT_INLINE_METHOD;
+        log(context, "  [0x%08x] abstract inline method %s::%s", pos, classEntry.getTypeName(), method.methodName());
+        AbbrevCode abbrevCode = AbbrevCode.NULL;
         log(context, "  [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal());
         pos = writeAbbrevCode(abbrevCode, buffer, pos);
         log(context, "  [0x%08x]     inline  0x%x", pos, DwarfInline.DW_INL_inlined.value());
@@ -531,15 +653,20 @@ private int writeAbstractInlineMethod(DebugContext context, CompiledMethodEntry
          */
         log(context, "  [0x%08x]     external  true", pos);
         pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos);
-
-        String name = uniqueDebugString(method.methodName());
-        log(context, "  [0x%08x]     name 0x%x (%s)", pos, debugStringIndex(name), name);
-        pos = writeStrSectionOffset(name, buffer, pos);
-        TypeEntry returnType = method.getValueType();
-        int retTypeIdx = getTypeIndex(returnType);
-        log(context, "  [0x%08x]     type 0x%x (%s)", pos, retTypeIdx, returnType.getTypeName());
-        pos = writeInfoSectionOffset(retTypeIdx, buffer, pos);
-
+        int methodSpecOffset = getMethodDeclarationIndex(method);
+        log(context, "  [0x%08x]     specification  0x%x", pos, methodSpecOffset);
+        pos = writeInfoSectionOffset(methodSpecOffset, buffer, pos);
+        FileEntry fileEntry = method.getFileEntry();
+        if (fileEntry == null) {
+            fileEntry = classEntry.getFileEntry();
+        }
+        assert fileEntry != null;
+        int fileIdx = classEntry.getFileIdx(fileEntry);
+        int level = 3;
+        // n.b. class entry used to index the params belongs to the inlining method
+        // not the inlined method
+        pos = writeMethodParameterDeclarations(context, classEntry, method, fileIdx, level, buffer, pos);
+        pos = writeMethodLocalDeclarations(context, classEntry, method, fileIdx, level, buffer, pos);
         /*
          * Write a terminating null attribute.
          */
@@ -548,32 +675,33 @@ private int writeAbstractInlineMethod(DebugContext context, CompiledMethodEntry
 
     private int writeAttrRef4(int reference, byte[] buffer, int p) {
         // make sure we have actually started writing a CU
-        assert cuStart >= 0;
+        assert unitStart >= 0;
         // writes a CU-relative offset
-        return writeAttrData4(reference - cuStart, buffer, p);
+        return writeAttrData4(reference - unitStart, buffer, p);
     }
 
     private int writeCUHeader(byte[] buffer, int p) {
         int pos = p;
+        unitStart = pos;
         /* CU length. */
         pos = writeInt(0, buffer, pos);
         /* DWARF version. */
-        pos = writeDwarfVersion(DwarfVersion.DW_VERSION_4, buffer, pos);
-        /* Abbrev offset. */
-        pos = writeAbbrevSectionOffset(0, buffer, pos);
+        pos = writeDwarfVersion(DwarfVersion.DW_VERSION_5, buffer, pos);
+        /* unit type */
+        pos = writeDwarfUnitHeader(DwarfUnitHeader.DW_UT_compile, buffer, pos);
         /* Address size. */
-        return writeByte((byte) 8, buffer, pos);
+        pos = writeByte((byte) 8, buffer, pos);
+        /* Abbrev offset. */
+        return writeAbbrevSectionOffset(0, buffer, pos);
     }
 
     @SuppressWarnings("unused")
     public int writeAttrString(String value, byte[] buffer, int p) {
-        int pos = p;
-        return writeUTF8StringBytes(value, buffer, pos);
+        return writeUTF8StringBytes(value, buffer, p);
     }
 
     public int writeAttrLanguage(DwarfLanguage language, byte[] buffer, int p) {
-        int pos = p;
-        return writeByte(language.value(), buffer, pos);
+        return writeByte(language.value(), buffer, p);
     }
 
     public int writeAttrEncoding(DwarfEncoding encoding, byte[] buffer, int p) {
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfLineSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfLineSectionImpl.java
index d2dfbd898aa0..f89f5f4d29fd 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfLineSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfLineSectionImpl.java
@@ -30,15 +30,15 @@
 import com.oracle.objectfile.LayoutDecisionMap;
 import com.oracle.objectfile.ObjectFile;
 import com.oracle.objectfile.debugentry.ClassEntry;
+import com.oracle.objectfile.debugentry.CompiledMethodEntry;
+import com.oracle.objectfile.debugentry.DirEntry;
+import com.oracle.objectfile.debugentry.FileEntry;
+import com.oracle.objectfile.debugentry.range.Range;
+import com.oracle.objectfile.debugentry.range.SubRange;
 import com.oracle.objectfile.elf.dwarf.DwarfSectionImpl;
-import com.oracle.objectfile.runtime.debugentry.CompiledMethodEntry;
-import com.oracle.objectfile.runtime.debugentry.DirEntry;
-import com.oracle.objectfile.runtime.debugentry.FileEntry;
-import com.oracle.objectfile.runtime.debugentry.range.Range;
-import com.oracle.objectfile.runtime.debugentry.range.SubRange;
-import com.oracle.objectfile.runtime.dwarf.constants.DwarfLineOpcode;
-import com.oracle.objectfile.runtime.dwarf.constants.DwarfSectionName;
-import com.oracle.objectfile.runtime.dwarf.constants.DwarfVersion;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfLineOpcode;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfVersion;
 import jdk.graal.compiler.debug.DebugContext;
 
 import java.util.Iterator;
@@ -87,7 +87,7 @@ public void createContent() {
          */
 
         CompiledMethodEntry compiledEntry = compiledMethod();
-        //setLineIndex(compiledEntry, byteCount.get());
+        setLineIndex(compiledEntry.getClassEntry(), 0);
         int headerSize = headerSize();
         int dirTableSize = computeDirTableSize(compiledEntry);
         int fileTableSize = computeFileTableSize(compiledEntry);
@@ -137,14 +137,23 @@ private int computeDirTableSize(CompiledMethodEntry compiledEntry) {
          * Table contains a sequence of 'nul'-terminated UTF8 dir name bytes followed by an extra
          * 'nul'.
          */
-        // TODO also check for subranges
-        DirEntry dirEntry = compiledEntry.getPrimary().getFileEntry().getDirEntry();
-        int length = countUTF8Bytes(dirEntry.getPathString()) + 1;
-        assert length > 1; // no empty strings
+        // DirEntry dirEntry = compiledEntry.getPrimary().getFileEntry().getDirEntry();
+        // int length = countUTF8Bytes(dirEntry.getPathString()) + 1;
+        // assert length > 1; // no empty strings
+
+
+        Cursor cursor = new Cursor();
+        compiledEntry.getClassEntry().dirStream().forEachOrdered(dirEntry -> {
+            int length = countUTF8Bytes(dirEntry.getPathString());
+            // We should never have a null or zero length entry in local dirs
+            assert length > 0;
+            cursor.add(length + 1);
+        });
         /*
          * Allow for terminator nul.
          */
-        return length + 1;
+        cursor.add(1);
+        return cursor.get();
     }
 
     private int computeFileTableSize(CompiledMethodEntry compiledEntry) {
@@ -155,19 +164,36 @@ private int computeFileTableSize(CompiledMethodEntry compiledEntry) {
          * time stamps
          */
         // TODO also check subranges
-        FileEntry fileEntry = compiledEntry.getPrimary().getFileEntry();
-        int length = countUTF8Bytes(fileEntry.getFileName()) + 1;
-        assert length > 1;
-        // The dir index gets written as a ULEB
-        int dirIdx = 1;
-        length += writeULEB(dirIdx, scratch, 0);
-        // The two zero timestamps require 1 byte each
-        length += 2;
-
+//        FileEntry fileEntry = compiledEntry.getPrimary().getFileEntry();
+//        int length = countUTF8Bytes(fileEntry.getFileName()) + 1;
+//        assert length > 1;
+//        // The dir index gets written as a ULEB
+//        int dirIdx = 1;
+//        length += writeULEB(dirIdx, scratch, 0);
+//        // The two zero timestamps require 1 byte each
+//        length += 2;
+
+        ClassEntry classEntry = compiledEntry.getClassEntry();
+
+        Cursor cursor = new Cursor();
+        classEntry.fileStream().forEachOrdered(fileEntry -> {
+            // We want the file base name excluding path.
+            String baseName = fileEntry.getFileName();
+            int length = countUTF8Bytes(baseName);
+            // We should never have a null or zero length entry in local files.
+            assert length > 0;
+            cursor.add(length + 1);
+            // The dir index gets written as a ULEB
+            int dirIdx = classEntry.getDirIdx(fileEntry);
+            cursor.add(writeULEB(dirIdx, scratch, 0));
+            // The two zero timestamps require 1 byte each
+            cursor.add(2);
+        });
         /*
          * Allow for terminator nul.
          */
-        return length + 1;
+        cursor.add(1);
+        return cursor.get();
     }
 
     @Override
@@ -198,7 +224,7 @@ public void writeContent(DebugContext context) {
 
         enableLog(context, pos);
         log(context, "  [0x%08x] DEBUG_LINE", pos);
-        // setLineIndex(classEntry, pos);
+        setLineIndex(compiledEntry.getClassEntry(), pos);
         int lengthPos = pos;
         pos = writeHeader(buffer, pos);
         log(context, "  [0x%08x] headerSize = 0x%08x", pos, pos);
@@ -290,27 +316,32 @@ private int writeDirTable(DebugContext context, CompiledMethodEntry compiledEntr
         /*
          * Write out the list of dirs
          */
-        int pos = p;
-        int idx = 1;
-
-        // TODO: foreach compiledEntry.dirs() - also check subranges
-        DirEntry dirEntry = compiledEntry.getPrimary().getMethodEntry().getFileEntry().getDirEntry();
-        String dirPath = dirEntry.getPathString();
-        pos = writeUTF8StringBytes(dirPath, buffer, pos);
-        idx++;
-
-//        classEntry.dirStream().forEach(dirEntry -> {
-//            int dirIdx = idx.get();
-//            assert (classEntry.getDirIdx(dirEntry) == dirIdx);
-//            String dirPath = dirEntry.getPathString();
-//            verboseLog(context, "  [0x%08x] %-4d %s", cursor.get(), dirIdx, dirPath);
-//            cursor.set(writeUTF8StringBytes(dirPath, buffer, cursor.get()));
-//            idx.add(1);
-//        });
+//        int pos = p;
+//        int idx = 1;
+//
+//        // TODO: foreach compiledEntry.dirs() - also check subranges
+//        DirEntry dirEntry = compiledEntry.getPrimary().getMethodEntry().getFileEntry().getDirEntry();
+//        String dirPath = dirEntry.getPathString();
+//        pos = writeUTF8StringBytes(dirPath, buffer, pos);
+//        idx++;
+
+        ClassEntry classEntry = compiledEntry.getClassEntry();
+
+        Cursor cursor = new Cursor(p);
+        Cursor idx = new Cursor(1);
+        classEntry.dirStream().forEach(dirEntry -> {
+            int dirIdx = idx.get();
+            assert (classEntry.getDirIdx(dirEntry) == dirIdx);
+            String dirPath = dirEntry.getPathString();
+            verboseLog(context, "  [0x%08x] %-4d %s", cursor.get(), dirIdx, dirPath);
+            cursor.set(writeUTF8StringBytes(dirPath, buffer, cursor.get()));
+            idx.add(1);
+        });
         /*
          * Separate dirs from files with a nul.
          */
-        return writeByte((byte) 0, buffer, pos);
+        cursor.set(writeByte((byte) 0, buffer, cursor.get()));
+        return cursor.get();
     }
 
     private int writeFileTable(DebugContext context, CompiledMethodEntry compiledEntry, byte[] buffer, int p) {
@@ -318,38 +349,42 @@ private int writeFileTable(DebugContext context, CompiledMethodEntry compiledEnt
         /*
          * Write out the list of files
          */
-        int pos = p;
-        int idx = 1;
-
-        // TODO: foreach compiledEntry.files() - also check subranges
-
-        FileEntry fileEntry = compiledEntry.getPrimary().getMethodEntry().getFileEntry();
-        String baseName = fileEntry.getFileName();
-        pos = writeUTF8StringBytes(baseName, buffer, pos);
-        pos = writeULEB(1, buffer, pos);  // TODO set correct dir index, if more than 1 dir in subranges
-        pos = writeULEB(0, buffer, pos);
-        pos = writeULEB(0, buffer, pos);
-        idx++;
-
-
-//        classEntry.fileStream().forEach(fileEntry -> {
-//            int pos = cursor.get();
-//            int fileIdx = idx.get();
-//            assert classEntry.getFileIdx(fileEntry) == fileIdx;
-//            int dirIdx = classEntry.getDirIdx(fileEntry);
-//            String baseName = fileEntry.getFileName();
-//            verboseLog(context, "  [0x%08x] %-5d %-5d %s", pos, fileIdx, dirIdx, baseName);
-//            pos = writeUTF8StringBytes(baseName, buffer, pos);
-//            pos = writeULEB(dirIdx, buffer, pos);
-//            pos = writeULEB(0, buffer, pos);
-//            pos = writeULEB(0, buffer, pos);
-//            cursor.set(pos);
-//            idx.add(1);
-//        });
+//        int pos = p;
+//        int idx = 1;
+//
+//        // TODO: foreach compiledEntry.files() - also check subranges
+//
+//        FileEntry fileEntry = compiledEntry.getPrimary().getMethodEntry().getFileEntry();
+//        String baseName = fileEntry.getFileName();
+//        pos = writeUTF8StringBytes(baseName, buffer, pos);
+//        pos = writeULEB(1, buffer, pos);  // TODO set correct dir index, if more than 1 dir in subranges
+//        pos = writeULEB(0, buffer, pos);
+//        pos = writeULEB(0, buffer, pos);
+//        idx++;
+
+        ClassEntry classEntry = compiledEntry.getClassEntry();
+
+        Cursor cursor = new Cursor(p);
+        Cursor idx = new Cursor(1);
+        classEntry.fileStream().forEach(fileEntry -> {
+            int pos = cursor.get();
+            int fileIdx = idx.get();
+            assert classEntry.getFileIdx(fileEntry) == fileIdx;
+            int dirIdx = classEntry.getDirIdx(fileEntry);
+            String baseName = fileEntry.getFileName();
+            verboseLog(context, "  [0x%08x] %-5d %-5d %s", pos, fileIdx, dirIdx, baseName);
+            pos = writeUTF8StringBytes(baseName, buffer, pos);
+            pos = writeULEB(dirIdx, buffer, pos);
+            pos = writeULEB(0, buffer, pos);
+            pos = writeULEB(0, buffer, pos);
+            cursor.set(pos);
+            idx.add(1);
+        });
         /*
          * Terminate files with a nul.
          */
-        return writeByte((byte) 0, buffer, pos);
+        cursor.set(writeByte((byte) 0, buffer, cursor.get()));
+        return cursor.get();
     }
 
     private long debugLine = 1;
@@ -358,6 +393,7 @@ private int writeFileTable(DebugContext context, CompiledMethodEntry compiledEnt
     private int writeCompiledMethodLineInfo(DebugContext context, CompiledMethodEntry compiledEntry, byte[] buffer, int p) {
         int pos = p;
         Range primaryRange = compiledEntry.getPrimary();
+        ClassEntry classEntry = compiledEntry.getClassEntry();
         // the compiled method might be a substitution and not in the file of the class entry
         FileEntry fileEntry = primaryRange.getFileEntry();
         if (fileEntry == null) {
@@ -366,7 +402,7 @@ private int writeCompiledMethodLineInfo(DebugContext context, CompiledMethodEntr
             return pos;
         }
         String file = fileEntry.getFileName();
-        int fileIdx = 1; // TODO classEntry.getFileIdx(fileEntry);
+        int fileIdx = classEntry.getFileIdx(fileEntry);
         /*
          * Each primary represents a method i.e. a contiguous sequence of subranges. For normal
          * methods we expect the first leaf range to start at offset 0 covering the method prologue.
@@ -382,7 +418,7 @@ private int writeCompiledMethodLineInfo(DebugContext context, CompiledMethodEntr
             if (line > 0) {
                 FileEntry firstFileEntry = prologueRange.getFileEntry();
                 if (firstFileEntry != null) {
-                    fileIdx = 1; // TODO classEntry.getFileIdx(firstFileEntry);
+                    fileIdx = classEntry.getFileIdx(firstFileEntry);
                 }
             }
         }
@@ -433,7 +469,7 @@ private int writeCompiledMethodLineInfo(DebugContext context, CompiledMethodEntr
                 continue;
             }
             String subfile = subFileEntry.getFileName();
-            int subFileIdx = 0; // TODO classEntry.getFileIdx(subFileEntry);
+            int subFileIdx = classEntry.getFileIdx(subFileEntry);
             assert subFileIdx > 0;
             long subLine = subrange.getLine();
             long subAddressLo = subrange.getLo();
@@ -562,7 +598,7 @@ private int computeLineNumberTableSize(CompiledMethodEntry compiledEntry) {
     private int writeLineNumberTable(DebugContext context, CompiledMethodEntry compiledEntry, byte[] buffer, int p) {
         int pos = p;
         String methodName = compiledEntry.getPrimary().getFullMethodNameWithParams();
-        String fileName = compiledEntry.getTypedefEntry().getTypeName();
+        String fileName = compiledEntry.getClassEntry().getTypeName();
         log(context, "  [0x%08x] %s %s", pos, methodName, fileName);
         pos = writeCompiledMethodLineInfo(context, compiledEntry, buffer, pos);
         return pos;
@@ -679,7 +715,7 @@ private int writeSetAddressOp(DebugContext context, long arg, byte[] buffer, int
          */
         pos = writeULEB(9, buffer, pos);
         pos = writeLineOpcode(opcode, buffer, pos);
-        return writeRelocatableCodeOffset(arg, buffer, pos);
+        return writeLong(arg, buffer, pos);
     }
 
     @SuppressWarnings("unused")
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfLocSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfLocSectionImpl.java
index 689193dd2158..b45e505a185c 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfLocSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfLocSectionImpl.java
@@ -26,19 +26,30 @@
 
 package com.oracle.objectfile.runtime.dwarf;
 
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
 import com.oracle.objectfile.BuildDependency;
 import com.oracle.objectfile.LayoutDecision;
 import com.oracle.objectfile.LayoutDecisionMap;
 import com.oracle.objectfile.ObjectFile;
+import com.oracle.objectfile.debugentry.ClassEntry;
+import com.oracle.objectfile.debugentry.CompiledMethodEntry;
+import com.oracle.objectfile.debugentry.range.Range;
+import com.oracle.objectfile.debugentry.range.SubRange;
+import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalInfo;
+import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalValueInfo;
 import com.oracle.objectfile.elf.ELFMachine;
 import com.oracle.objectfile.elf.ELFObjectFile;
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocalInfo;
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocalValueInfo;
-import com.oracle.objectfile.runtime.debugentry.CompiledMethodEntry;
-import com.oracle.objectfile.runtime.debugentry.range.Range;
-import com.oracle.objectfile.runtime.debugentry.range.SubRange;
-import com.oracle.objectfile.runtime.dwarf.constants.DwarfExpressionOpcode;
-import com.oracle.objectfile.runtime.dwarf.constants.DwarfSectionName;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfExpressionOpcode;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfLocationListEntry;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfVersion;
+
 import jdk.graal.compiler.debug.DebugContext;
 import jdk.vm.ci.aarch64.AArch64;
 import jdk.vm.ci.amd64.AMD64;
@@ -46,14 +57,6 @@
 import jdk.vm.ci.meta.JavaKind;
 import jdk.vm.ci.meta.PrimitiveConstant;
 
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
 /**
  * Section generator for debug_loc section.
  */
@@ -70,14 +73,14 @@ public class RuntimeDwarfLocSectionImpl extends RuntimeDwarfSectionImpl {
     private int dwarfStackRegister;
 
     private static final LayoutDecision.Kind[] targetLayoutKinds = {
-                    LayoutDecision.Kind.CONTENT,
-                    LayoutDecision.Kind.SIZE,
-                    /* Add this so we can use the text section base address for debug. */
-                    LayoutDecision.Kind.VADDR};
+            LayoutDecision.Kind.CONTENT,
+            LayoutDecision.Kind.SIZE,
+            /* Add this so we can use the text section base address for debug. */
+            LayoutDecision.Kind.VADDR};
 
     public RuntimeDwarfLocSectionImpl(RuntimeDwarfDebugInfo dwarfSections) {
         // debug_loc section depends on text section
-        super(dwarfSections, DwarfSectionName.DW_LOC_SECTION, DwarfSectionName.TEXT_SECTION, targetLayoutKinds);
+        super(dwarfSections, DwarfSectionName.DW_LOCLISTS_SECTION, DwarfSectionName.TEXT_SECTION, targetLayoutKinds);
         initDwarfRegMap();
     }
 
@@ -100,10 +103,9 @@ public Set<BuildDependency> getDependencies(Map<ObjectFile.Element, LayoutDecisi
     public void createContent() {
         assert !contentByteArrayCreated();
 
-        byte[] buffer = null;
-        int len = generateContent(null, buffer);
+        int len = generateContent(null, null);
 
-        buffer = new byte[len];
+        byte[] buffer = new byte[len];
         super.setContent(buffer);
     }
 
@@ -124,71 +126,90 @@ public void writeContent(DebugContext context) {
     }
 
     private int generateContent(DebugContext context, byte[] buffer) {
-        int pos = 0;
-        // n.b. We could do this by iterating over the compiled methods sequence. the
-        // reason for doing it in class entry order is to because it mirrors the
-        // order in which entries appear in the info section. That stops objdump
-        // posting spurious messages about overlaps and holes in the var ranges.
-        CompiledMethodEntry compiledEntry = dwarfSections.getCompiledMethod();
-        pos = writeCompiledMethodLocation(context, compiledEntry, buffer, pos);
-        return pos;
+        Cursor cursor = new Cursor();
+        CompiledMethodEntry compiledMethodEntry = compiledMethod();
+
+
+        List<LocationListEntry> locationListEntries = getLocationListEntries(compiledMethodEntry);
+        int entryCount = locationListEntries.size();
+
+        int lengthPos = cursor.get();
+        cursor.set(writeLocationListsHeader(entryCount, buffer, cursor.get()));
+
+        int baseOffset = cursor.get();
+        setLocationListIndex(compiledMethodEntry.getClassEntry(), baseOffset);
+        cursor.add(entryCount * 4);  // space for offset array
+
+        int index = 0;
+        for (LocationListEntry entry : locationListEntries) {
+            setRangeLocalIndex(entry.range(), entry.local(), index);
+            writeInt(cursor.get() - baseOffset, buffer, baseOffset + index * 4);
+            index++;
+            cursor.set(writeVarLocations(context, entry.local(), entry.base(), entry.rangeList(), buffer, cursor.get()));
+        }
+
+        /* Fix up location list length */
+        patchLength(lengthPos, buffer, cursor.get());
+
+        return cursor.get();
     }
 
-    private int writeCompiledMethodLocation(DebugContext context, CompiledMethodEntry compiledEntry, byte[] buffer, int p) {
+    private int writeLocationListsHeader(int offsetEntries, byte[] buffer, int p) {
         int pos = p;
-        Range primary = compiledEntry.getPrimary();
+        /* Loclists length. */
+        pos = writeInt(0, buffer, pos);
+        /* DWARF version. */
+        pos = writeDwarfVersion(DwarfVersion.DW_VERSION_5, buffer, pos);
+        /* Address size. */
+        pos = writeByte((byte) 8, buffer, pos);
+        /* Segment selector size. */
+        pos = writeByte((byte) 0, buffer, pos);
+        /* Offset entry count */
+        return writeInt(offsetEntries, buffer, pos);
+    }
+
+    private record LocationListEntry(Range range, long base, DebugLocalInfo local, List<SubRange> rangeList) {
+    }
+
+    private List<LocationListEntry> getLocationListEntries(CompiledMethodEntry compiledMethodEntry) {
+
+        Range primary = compiledMethodEntry.getPrimary();
         /*
-         * Note that offsets are written relative to the primary range base. This requires writing a
-         * base address entry before each of the location list ranges. It is possible to default the
-         * base address to the low_pc value of the compile unit for the compiled method's owning
-         * class, saving two words per location list. However, that forces the debugger to do a lot
-         * more up-front cross-referencing of CUs when it needs to resolve code addresses e.g. to
-         * set a breakpoint, leading to a very slow response for the user.
+         * Note that offsets are written relative to the primary range base. This requires
+         * writing a base address entry before each of the location list ranges. It is possible
+         * to default the base address to the low_pc value of the compile unit for the compiled
+         * method's owning class, saving two words per location list. However, that forces the
+         * debugger to do a lot more up-front cross-referencing of CUs when it needs to resolve
+         * code addresses e.g. to set a breakpoint, leading to a very slow response for the
+         * user.
          */
         long base = primary.getLo();
-        log(context, "  [0x%08x] top level locations [0x%x, 0x%x] method %s", pos, primary.getLo(), primary.getHi(), primary.getFullMethodNameWithParams());
-        pos = writeTopLevelLocations(context, compiledEntry, base, buffer, pos);
+        // location list entries for primary range
+        List<LocationListEntry> locationListEntries = new ArrayList<>(getRangeLocationListEntries(primary, base));
+        // location list entries for inlined calls
         if (!primary.isLeaf()) {
-            log(context, "  [0x%08x] inline locations %s", pos, primary.getFullMethodNameWithParams());
-            pos = writeInlineLocations(context, compiledEntry, base, buffer, pos);
-        }
-        return pos;
-    }
-
-    private int writeTopLevelLocations(DebugContext context, CompiledMethodEntry compiledEntry, long base, byte[] buffer, int p) {
-        int pos = p;
-        Range primary = compiledEntry.getPrimary();
-        HashMap<DebugLocalInfo, List<SubRange>> varRangeMap = primary.getVarRangeMap();
-        for (DebugLocalInfo local : varRangeMap.keySet()) {
-            List<SubRange> rangeList = varRangeMap.get(local);
-            if (!rangeList.isEmpty()) {
-                setRangeLocalIndex(primary, local, pos);
-                pos = writeVarLocations(context, local, base, rangeList, buffer, pos);
+            Iterator<SubRange> iterator = compiledMethodEntry.topDownRangeIterator();
+            while (iterator.hasNext()) {
+                SubRange subrange = iterator.next();
+                if (subrange.isLeaf()) {
+                    continue;
+                }
+                locationListEntries.addAll(getRangeLocationListEntries(subrange, base));
             }
         }
-        return pos;
+        return locationListEntries;
     }
 
-    private int writeInlineLocations(DebugContext context, CompiledMethodEntry compiledEntry, long base, byte[] buffer, int p) {
-        int pos = p;
-        Range primary = compiledEntry.getPrimary();
-        assert !primary.isLeaf();
-        Iterator<SubRange> iterator = compiledEntry.topDownRangeIterator();
-        while (iterator.hasNext()) {
-            SubRange subrange = iterator.next();
-            if (subrange.isLeaf()) {
-                continue;
-            }
-            HashMap<DebugLocalInfo, List<SubRange>> varRangeMap = subrange.getVarRangeMap();
-            for (DebugLocalInfo local : varRangeMap.keySet()) {
-                List<SubRange> rangeList = varRangeMap.get(local);
-                if (!rangeList.isEmpty()) {
-                    setRangeLocalIndex(subrange, local, pos);
-                    pos = writeVarLocations(context, local, base, rangeList, buffer, pos);
-                }
+    private List<LocationListEntry> getRangeLocationListEntries(Range range, long base) {
+        List<LocationListEntry> locationListEntries = new ArrayList<>();
+
+        for (Map.Entry<DebugLocalInfo, List<SubRange>> entry : range.getVarRangeMap().entrySet()) {
+            if (!entry.getValue().isEmpty()) {
+                locationListEntries.add(new LocationListEntry(range, base, entry.getKey(), entry.getValue()));
             }
         }
-        return pos;
+
+        return locationListEntries;
     }
 
     private int writeVarLocations(DebugContext context, DebugLocalInfo local, long base, List<SubRange> rangeList, byte[] buffer, int p) {
@@ -199,22 +220,16 @@ private int writeVarLocations(DebugContext context, DebugLocalInfo local, long b
 
         // write start of primary range as base address - see comment above for reasons why
         // we choose ot do this rather than use the relevant compile unit low_pc
-        pos = writeAttrData8(-1L, buffer, pos);
-        pos = writeAttrAddress(base, buffer, pos);
+        pos = writeLocationListEntry(DwarfLocationListEntry.DW_LLE_base_address, buffer, pos);
+        pos = writeLong(base, buffer, pos);
         // write ranges as offsets from base
-        boolean first = true;
         for (LocalValueExtent extent : extents) {
             DebugLocalValueInfo value = extent.value;
             assert (value != null);
-            System.out.printf("  [0x%08x]     local  %s:%s [0x%x, 0x%x] = %s%n", pos, value.name(), value.typeName(), extent.getLo(), extent.getHi(), formatValue(value));
             log(context, "  [0x%08x]     local  %s:%s [0x%x, 0x%x] = %s", pos, value.name(), value.typeName(), extent.getLo(), extent.getHi(), formatValue(value));
-            if (first) {
-                pos = writeAttrData8(0, buffer, pos);
-                first = false;
-            } else {
-                pos = writeAttrData8(extent.getLo() - base, buffer, pos);
-            }
-            pos = writeAttrData8(extent.getHi() - base, buffer, pos);
+            pos = writeLocationListEntry(DwarfLocationListEntry.DW_LLE_offset_pair, buffer, pos);
+            pos = writeULEB(extent.getLo() - base, buffer, pos);
+            pos = writeULEB(extent.getHi() - base, buffer, pos);
             switch (value.localKind()) {
                 case REGISTER:
                     pos = writeRegisterLocation(context, value.regIndex(), buffer, pos);
@@ -228,6 +243,8 @@ private int writeVarLocations(DebugContext context, DebugLocalInfo local, long b
                         pos = writePrimitiveConstantLocation(context, value.constantValue(), buffer, pos);
                     } else if (constant.isNull()) {
                         pos = writeNullConstantLocation(context, value.constantValue(), buffer, pos);
+                    } else {
+                        pos = writeObjectConstantLocation(context, value.constantValue(), value.heapOffset(), buffer, pos);
                     }
                     break;
                 default:
@@ -236,10 +253,7 @@ private int writeVarLocations(DebugContext context, DebugLocalInfo local, long b
             }
         }
         // write list terminator
-        pos = writeAttrData8(0, buffer, pos);
-        pos = writeAttrData8(0, buffer, pos);
-
-        return pos;
+        return writeLocationListEntry(DwarfLocationListEntry.DW_LLE_end_of_list, buffer, pos);
     }
 
     private int writeRegisterLocation(DebugContext context, int regIndex, byte[] buffer, int p) {
@@ -248,31 +262,31 @@ private int writeRegisterLocation(DebugContext context, int regIndex, byte[] buf
         int pos = p;
         if (targetIdx < 0x20) {
             // can write using DW_OP_reg<n>
-            short byteCount = 1;
+            int byteCount = 1;
             byte reg = (byte) targetIdx;
-            pos = writeShort(byteCount, buffer, pos);
+            pos = writeULEB(byteCount, buffer, pos);
             pos = writeExprOpcodeReg(reg, buffer, pos);
             verboseLog(context, "  [0x%08x]     REGOP count %d op 0x%x", pos, byteCount, DwarfExpressionOpcode.DW_OP_reg0.value() + reg);
         } else {
             // have to write using DW_OP_regx + LEB operand
             assert targetIdx < 128 : "unexpectedly high reg index!";
-            short byteCount = 2;
-            pos = writeShort(byteCount, buffer, pos);
+            int byteCount = 2;
+            pos = writeULEB(byteCount, buffer, pos);
             pos = writeExprOpcode(DwarfExpressionOpcode.DW_OP_regx, buffer, pos);
             pos = writeULEB(targetIdx, buffer, pos);
             verboseLog(context, "  [0x%08x]     REGOP count %d op 0x%x reg %d", pos, byteCount, DwarfExpressionOpcode.DW_OP_regx.value(), targetIdx);
-            // target idx written as ULEB should fit in one byte
-            assert pos == p + 4 : "wrote the wrong number of bytes!";
+            // byte count and target idx written as ULEB should fit in one byte
+            assert pos == p + 3 : "wrote the wrong number of bytes!";
         }
         return pos;
     }
 
     private int writeStackLocation(DebugContext context, int offset, byte[] buffer, int p) {
         int pos = p;
-        short byteCount = 0;
+        int byteCount = 0;
         byte sp = (byte) getDwarfStackRegister();
         int patchPos = pos;
-        pos = writeShort(byteCount, buffer, pos);
+        pos = writeULEB(byteCount, buffer, pos);
         int zeroPos = pos;
         if (sp < 0x20) {
             // fold the base reg index into the op
@@ -284,8 +298,8 @@ private int writeStackLocation(DebugContext context, int offset, byte[] buffer,
         }
         pos = writeSLEB(offset, buffer, pos);
         // now backpatch the byte count
-        byteCount = (byte) (pos - zeroPos);
-        writeShort(byteCount, buffer, patchPos);
+        byteCount = (pos - zeroPos);
+        writeULEB(byteCount, buffer, patchPos);
         if (sp < 0x20) {
             verboseLog(context, "  [0x%08x]     STACKOP count %d op 0x%x offset %d", pos, byteCount, (DwarfExpressionOpcode.DW_OP_breg0.value() + sp), 0 - offset);
         } else {
@@ -302,7 +316,7 @@ private int writePrimitiveConstantLocation(DebugContext context, JavaConstant co
         int dataByteCount = kind.getByteCount();
         // total bytes is op + uleb + dataByteCount
         int byteCount = 1 + 1 + dataByteCount;
-        pos = writeShort((short) byteCount, buffer, pos);
+        pos = writeULEB(byteCount, buffer, pos);
         pos = writeExprOpcode(op, buffer, pos);
         pos = writeULEB(dataByteCount, buffer, pos);
         if (dataByteCount == 1) {
@@ -331,7 +345,7 @@ private int writeNullConstantLocation(DebugContext context, JavaConstant constan
         int dataByteCount = 8;
         // total bytes is op + uleb + dataByteCount
         int byteCount = 1 + 1 + dataByteCount;
-        pos = writeShort((short) byteCount, buffer, pos);
+        pos = writeULEB(byteCount, buffer, pos);
         pos = writeExprOpcode(op, buffer, pos);
         pos = writeULEB(dataByteCount, buffer, pos);
         pos = writeAttrData8(0, buffer, pos);
@@ -339,6 +353,14 @@ private int writeNullConstantLocation(DebugContext context, JavaConstant constan
         return pos;
     }
 
+    private int writeObjectConstantLocation(DebugContext context, JavaConstant constant, long heapOffset, byte[] buffer, int p) {
+        assert constant.getJavaKind() == JavaKind.Object && !constant.isNull();
+        int pos = p;
+        pos = writeHeapLocationLocList(heapOffset, buffer, pos);
+        verboseLog(context, "  [0x%08x]     CONSTANT (object) %s", pos, constant.toValueString());
+        return pos;
+    }
+
     // auxiliary class used to collect per-range locations for a given local
     // merging adjacent ranges with the same location
     static class LocalValueExtent {
@@ -408,25 +430,40 @@ private int getDwarfStackRegister() {
     }
 
     private int mapToDwarfReg(int regIdx) {
-        assert regIdx >= 0 : "negative register index!";
-        assert regIdx < dwarfRegMap.length : String.format("register index %d exceeds map range %d", regIdx, dwarfRegMap.length);
-        return dwarfRegMap[regIdx];
+        if (regIdx < 0) {
+            throw new AssertionError("Requesting dwarf register number for negative register index");
+        }
+        if (regIdx >= dwarfRegMap.length) {
+            throw new AssertionError("Register index " + regIdx + " exceeds map range " + dwarfRegMap.length);
+        }
+        int dwarfRegNum = dwarfRegMap[regIdx];
+        if (dwarfRegNum < 0) {
+            throw new AssertionError("Register index " + regIdx + " does not map to valid dwarf register number");
+        }
+        return dwarfRegNum;
     }
 
     private void initDwarfRegMap() {
         if (dwarfSections.elfMachine == ELFMachine.AArch64) {
-            dwarfRegMap = GRAAL_AARCH64_TO_DWARF_REG_MAP;
+            dwarfRegMap = graalToDwarfRegMap(DwarfRegEncodingAArch64.values());
             dwarfStackRegister = DwarfRegEncodingAArch64.SP.getDwarfEncoding();
         } else {
             assert dwarfSections.elfMachine == ELFMachine.X86_64 : "must be";
-            dwarfRegMap = GRAAL_X86_64_TO_DWARF_REG_MAP;
+            dwarfRegMap = graalToDwarfRegMap(DwarfRegEncodingAMD64.values());
             dwarfStackRegister = DwarfRegEncodingAMD64.RSP.getDwarfEncoding();
         }
     }
 
+    private interface DwarfRegEncoding {
+
+        int getDwarfEncoding();
+
+        int getGraalEncoding();
+    }
+
     // Register numbers used by DWARF for AArch64 registers encoded
     // along with their respective GraalVM compiler number.
-    public enum DwarfRegEncodingAArch64 {
+    public enum DwarfRegEncodingAArch64 implements DwarfRegEncoding {
         R0(0, AArch64.r0.number),
         R1(1, AArch64.r1.number),
         R2(2, AArch64.r2.number),
@@ -502,28 +539,22 @@ public enum DwarfRegEncodingAArch64 {
             this.graalEncoding = graalEncoding;
         }
 
-        public static int graalOrder(DwarfRegEncodingAArch64 e1, DwarfRegEncodingAArch64 e2) {
-            return Integer.compare(e1.graalEncoding, e2.graalEncoding);
-        }
-
+        @Override
         public int getDwarfEncoding() {
             return dwarfEncoding;
         }
-    }
 
-    // Map from compiler AArch64 register numbers to corresponding DWARF AArch64 register encoding.
-    // Register numbers for compiler general purpose and float registers occupy index ranges 0-31
-    // and 34-65 respectively. Table entries provided the corresponding number used by DWARF to
-    // identify the same register. Note that the table includes entries for ZR (32) and SP (33)
-    // even though we should not see those register numbers appearing in location values.
-    private static final int[] GRAAL_AARCH64_TO_DWARF_REG_MAP = Arrays.stream(DwarfRegEncodingAArch64.values()).sorted(DwarfRegEncodingAArch64::graalOrder)
-                    .mapToInt(DwarfRegEncodingAArch64::getDwarfEncoding).toArray();
+        @Override
+        public int getGraalEncoding() {
+            return graalEncoding;
+        }
+    }
 
     // Register numbers used by DWARF for AMD64 registers encoded
     // along with their respective GraalVM compiler number. n.b. some of the initial
     // 8 general purpose registers have different Dwarf and GraalVM encodings. For
     // example the compiler number for RDX is 3 while the DWARF number for RDX is 1.
-    public enum DwarfRegEncodingAMD64 {
+    public enum DwarfRegEncodingAMD64 implements DwarfRegEncoding {
         RAX(0, AMD64.rax.number),
         RDX(1, AMD64.rdx.number),
         RCX(2, AMD64.rcx.number),
@@ -593,16 +624,25 @@ public static int graalOrder(DwarfRegEncodingAMD64 e1, DwarfRegEncodingAMD64 e2)
             return Integer.compare(e1.graalEncoding, e2.graalEncoding);
         }
 
+        @Override
         public int getDwarfEncoding() {
             return dwarfEncoding;
         }
-    }
 
-    // Map from compiler X86_64 register numbers to corresponding DWARF AMD64 register encoding.
-    // Register numbers for general purpose and float registers occupy index ranges 0-15 and 16-31
-    // respectively. Table entries provide the corresponding number used by DWARF to identify the
-    // same register.
-    private static final int[] GRAAL_X86_64_TO_DWARF_REG_MAP = Arrays.stream(DwarfRegEncodingAMD64.values()).sorted(DwarfRegEncodingAMD64::graalOrder).mapToInt(DwarfRegEncodingAMD64::getDwarfEncoding)
-                    .toArray();
+        @Override
+        public int getGraalEncoding() {
+            return graalEncoding;
+        }
+    }
 
+    // Map from compiler register numbers to corresponding DWARF register numbers.
+    private static int[] graalToDwarfRegMap(DwarfRegEncoding[] encoding) {
+        int size = Arrays.stream(encoding).mapToInt(DwarfRegEncoding::getGraalEncoding).max().orElseThrow() + 1;
+        int[] regMap = new int[size];
+        Arrays.fill(regMap, -1);
+        for (DwarfRegEncoding regEncoding : encoding) {
+            regMap[regEncoding.getGraalEncoding()] = regEncoding.getDwarfEncoding();
+        }
+        return regMap;
+    }
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfSectionImpl.java
index 7d674335a638..a7d4a25d035e 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfSectionImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
@@ -26,37 +26,44 @@
 
 package com.oracle.objectfile.runtime.dwarf;
 
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
+
 import com.oracle.objectfile.BasicProgbitsSectionImpl;
 import com.oracle.objectfile.BuildDependency;
 import com.oracle.objectfile.LayoutDecision;
 import com.oracle.objectfile.LayoutDecisionMap;
 import com.oracle.objectfile.ObjectFile;
+import com.oracle.objectfile.debugentry.ArrayTypeEntry;
+import com.oracle.objectfile.debugentry.ClassEntry;
+import com.oracle.objectfile.debugentry.CompiledMethodEntry;
+import com.oracle.objectfile.debugentry.DirEntry;
+import com.oracle.objectfile.debugentry.FileEntry;
+import com.oracle.objectfile.debugentry.MethodEntry;
+import com.oracle.objectfile.debugentry.PrimitiveTypeEntry;
+import com.oracle.objectfile.debugentry.StructureTypeEntry;
+import com.oracle.objectfile.debugentry.TypeEntry;
+import com.oracle.objectfile.debugentry.range.Range;
+import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalInfo;
+import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalValueInfo;
 import com.oracle.objectfile.elf.ELFMachine;
 import com.oracle.objectfile.elf.ELFObjectFile;
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocalInfo;
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugLocalValueInfo;
-import com.oracle.objectfile.runtime.debugentry.CompiledMethodEntry;
-import com.oracle.objectfile.runtime.debugentry.DirEntry;
-import com.oracle.objectfile.runtime.debugentry.FileEntry;
-import com.oracle.objectfile.runtime.debugentry.MethodEntry;
-import com.oracle.objectfile.runtime.debugentry.PrimitiveTypeEntry;
-import com.oracle.objectfile.runtime.debugentry.TypeEntry;
-import com.oracle.objectfile.runtime.debugentry.TypedefEntry;
-import com.oracle.objectfile.runtime.debugentry.range.Range;
 import com.oracle.objectfile.runtime.dwarf.RuntimeDwarfDebugInfo.AbbrevCode;
-import com.oracle.objectfile.runtime.dwarf.constants.DwarfExpressionOpcode;
-import com.oracle.objectfile.runtime.dwarf.constants.DwarfFlag;
-import com.oracle.objectfile.runtime.dwarf.constants.DwarfSectionName;
-import com.oracle.objectfile.runtime.dwarf.constants.DwarfTag;
-import com.oracle.objectfile.runtime.dwarf.constants.DwarfVersion;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfExpressionOpcode;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfFlag;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfLocationListEntry;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfRangeListEntry;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfTag;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfUnitHeader;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfVersion;
+
 import jdk.graal.compiler.debug.DebugContext;
 import jdk.vm.ci.meta.ResolvedJavaType;
 
-import java.nio.ByteOrder;
-import java.nio.charset.StandardCharsets;
-import java.util.Map;
-import java.util.Set;
-
 /**
  * A class from which all DWARF debug sections inherit providing common behaviours.
  */
@@ -115,8 +122,8 @@ public int get() {
      * The default layout properties.
      */
     private static final LayoutDecision.Kind[] defaultTargetSectionKinds = {
-                    LayoutDecision.Kind.CONTENT,
-                    LayoutDecision.Kind.SIZE
+            LayoutDecision.Kind.CONTENT,
+            LayoutDecision.Kind.SIZE
     };
 
     public RuntimeDwarfSectionImpl(RuntimeDwarfDebugInfo dwarfSections, DwarfSectionName name, DwarfSectionName targetName) {
@@ -155,7 +162,7 @@ public boolean isAArch64() {
     /**
      * Check whether the contents byte array has been sized and created. n.b. this does not imply
      * that data has been written to the byte array.
-     * 
+     *
      * @return true if the contents byte array has been sized and created otherwise false.
      */
     public boolean contentByteArrayCreated() {
@@ -278,8 +285,18 @@ protected int putRelocatableCodeOffset(long l, byte[] buffer, int p) {
         /*
          * Mark address so it is relocated relative to the start of the text segment.
          */
-        // markRelocationSite(pos, ObjectFile.RelocationKind.DIRECT_8, DwarfSectionName.TEXT_SECTION.value(), l);
-        pos = writeLong(l, buffer, pos);
+        markRelocationSite(pos, ObjectFile.RelocationKind.DIRECT_8, DwarfSectionName.TEXT_SECTION.value(), l);
+        pos = writeLong(0, buffer, pos);
+        return pos;
+    }
+
+    protected int putRelocatableHeapOffset(long l, byte[] buffer, int p) {
+        int pos = p;
+        /*
+         * Mark address so it is relocated relative to the start of the heap.
+         */
+        markRelocationSite(pos, ObjectFile.RelocationKind.DIRECT_8, RuntimeDwarfDebugInfo.HEAP_BEGIN_NAME, l);
+        pos = writeLong(0, buffer, pos);
         return pos;
     }
 
@@ -288,8 +305,8 @@ protected int putRelocatableDwarfSectionOffset(int offset, byte[] buffer, String
         /*
          * Mark address so it is relocated relative to the start of the desired section.
          */
-        // markRelocationSite(pos, ObjectFile.RelocationKind.DIRECT_4, referencedSectionName, offset);
-        pos = writeInt(offset, buffer, pos);
+        markRelocationSite(pos, ObjectFile.RelocationKind.DIRECT_4, referencedSectionName, offset);
+        pos = writeInt(0, buffer, pos);
         return pos;
     }
 
@@ -392,6 +409,14 @@ protected int writeRelocatableCodeOffset(long l, byte[] buffer, int p) {
         }
     }
 
+    protected int writeRelocatableHeapOffset(long l, byte[] buffer, int p) {
+        if (buffer != null) {
+            return putRelocatableHeapOffset(l, buffer, p);
+        } else {
+            return p + 8;
+        }
+    }
+
     protected int writeULEB(long val, byte[] buffer, int p) {
         if (buffer != null) {
             // write to the buffer at the supplied position
@@ -462,6 +487,14 @@ protected int writeAbbrevCode(AbbrevCode code, byte[] buffer, int pos) {
         return writeSLEB(code.ordinal(), buffer, pos);
     }
 
+    protected int writeRangeListEntry(DwarfRangeListEntry rangeListEntry, byte[] buffer, int pos) {
+        return writeByte(rangeListEntry.value(), buffer, pos);
+    }
+
+    protected int writeLocationListEntry(DwarfLocationListEntry locationListEntry, byte[] buffer, int pos) {
+        return writeByte(locationListEntry.value(), buffer, pos);
+    }
+
     protected int writeTag(DwarfTag dwarfTag, byte[] buffer, int pos) {
         int code = dwarfTag.value();
         if (code == 0) {
@@ -475,6 +508,14 @@ protected int writeDwarfVersion(DwarfVersion dwarfVersion, byte[] buffer, int po
         return writeShort(dwarfVersion.value(), buffer, pos);
     }
 
+    protected int writeDwarfUnitHeader(DwarfUnitHeader dwarfUnitHeader, byte[] buffer, int pos) {
+        return writeByte((byte) dwarfUnitHeader.value(), buffer, pos);
+    }
+
+    protected int writeTypeSignature(long typeSignature, byte[] buffer, int pos) {
+        return writeLong(typeSignature, buffer, pos);
+    }
+
     protected int writeFlag(DwarfFlag flag, byte[] buffer, int pos) {
         return writeByte(flag.value(), buffer, pos);
     }
@@ -508,10 +549,6 @@ protected int writeLineSectionOffset(int offset, byte[] buffer, int pos) {
         return writeDwarfSectionOffset(offset, buffer, DwarfSectionName.DW_LINE_SECTION, pos);
     }
 
-    protected int writeRangesSectionOffset(int offset, byte[] buffer, int pos) {
-        return writeDwarfSectionOffset(offset, buffer, DwarfSectionName.DW_RANGES_SECTION, pos);
-    }
-
     protected int writeAbbrevSectionOffset(int offset, byte[] buffer, int pos) {
         return writeDwarfSectionOffset(offset, buffer, DwarfSectionName.DW_ABBREV_SECTION, pos);
     }
@@ -527,7 +564,7 @@ private int writeStrSectionOffset(int offset, byte[] buffer, int pos) {
     }
 
     protected int writeLocSectionOffset(int offset, byte[] buffer, int pos) {
-        return writeDwarfSectionOffset(offset, buffer, DwarfSectionName.DW_LOC_SECTION, pos);
+        return writeDwarfSectionOffset(offset, buffer, DwarfSectionName.DW_LOCLISTS_SECTION, pos);
     }
 
     protected int writeDwarfSectionOffset(int offset, byte[] buffer, DwarfSectionName referencedSectionName, int pos) {
@@ -545,6 +582,76 @@ protected int writeAttrNull(byte[] buffer, int pos) {
         return writeTag(DwarfTag.DW_TAG_null, buffer, pos);
     }
 
+    /*
+     * Write a heap location expression preceded by a ULEB block size count as appropriate for an
+     * attribute with FORM exprloc. If a heapbase register is in use the generated expression
+     * computes the location as a constant offset from the runtime heap base register. If a heapbase
+     * register is not in use it computes the location as a fixed, relocatable offset from the
+     * link-time heap base address.
+     */
+    protected int writeHeapLocationExprLoc(long offset, byte[] buffer, int p) {
+        int pos = p;
+        /*
+         * We have to size the DWARF location expression by writing it to the scratch buffer so we
+         * can write its size as a ULEB before the expression itself.
+         */
+        int size = writeHeapLocation(offset, null, 0);
+
+        /* Write the size and expression into the output buffer. */
+        pos = writeULEB(size, buffer, pos);
+        return writeHeapLocation(offset, buffer, pos);
+    }
+
+    /*
+     * Write a heap location expression preceded by a ULEB block size count as appropriate for
+     * location list in the debug_loc section. If a heapbase register is in use the generated
+     * expression computes the location as a constant offset from the runtime heap base register. If
+     * a heapbase register is not in use it computes the location as a fixed, relocatable offset
+     * from the link-time heap base address.
+     */
+    protected int writeHeapLocationLocList(long offset, byte[] buffer, int p) {
+        int pos = p;
+        int len = 0;
+        int lenPos = pos;
+        // write dummy length
+        pos = writeULEB(len, buffer, pos);
+        int zeroPos = pos;
+        pos = writeHeapLocation(offset, buffer, pos);
+        pos = writeExprOpcode(DwarfExpressionOpcode.DW_OP_stack_value, buffer, pos);
+        // backpatch length
+        len = pos - zeroPos;
+        writeULEB(len, buffer, lenPos);
+        return pos;
+    }
+
+    /*
+     * Write a bare heap location expression as appropriate for a single location. If useHeapBase is
+     * true the generated expression computes the location as a constant offset from the runtime
+     * heap base register. If useHeapBase is false it computes the location as a fixed, relocatable
+     * offset from the link-time heap base address.
+     */
+    protected int writeHeapLocation(long offset, byte[] buffer, int p) {
+        if (dwarfSections.useHeapBase()) {
+            return writeHeapLocationBaseRelative(offset, buffer, p);
+        } else {
+            return writeHeapLocationRelocatable(offset, buffer, p);
+        }
+    }
+
+    private int writeHeapLocationBaseRelative(long offset, byte[] buffer, int p) {
+        int pos = p;
+        /* Write a location rebasing the offset relative to the heapbase register. */
+        pos = writeExprOpcodeBReg(dwarfSections.getHeapbaseRegister(), buffer, pos);
+        return writeSLEB(offset, buffer, pos);
+    }
+
+    private int writeHeapLocationRelocatable(long offset, byte[] buffer, int p) {
+        int pos = p;
+        /* Write a relocatable address relative to the heap section start. */
+        pos = writeExprOpcode(DwarfExpressionOpcode.DW_OP_addr, buffer, pos);
+        return writeRelocatableHeapOffset(offset, buffer, pos);
+    }
+
     protected static String formatValue(DebugLocalValueInfo value) {
         switch (value.localKind()) {
             case REGISTER:
@@ -562,7 +669,7 @@ protected static String formatValue(DebugLocalValueInfo value) {
     /**
      * Identify the section after which this debug section needs to be ordered when sizing and
      * creating content.
-     * 
+     *
      * @return the name of the preceding section.
      */
     public final String targetName() {
@@ -571,7 +678,7 @@ public final String targetName() {
 
     /**
      * Identify this debug section by name.
-     * 
+     *
      * @return the name of the debug section.
      */
     public final String getSectionName() {
@@ -638,30 +745,29 @@ public Set<BuildDependency> getDependencies(Map<ObjectFile.Element, LayoutDecisi
 
     /**
      * Retrieve a stream of all types notified via the DebugTypeInfo API.
-     * 
+     *
      * @return a stream of all types notified via the DebugTypeInfo API.
      */
-    protected Iterable<TypeEntry> types() {
-        return dwarfSections.getTypes();
+    protected Stream<TypeEntry> typeStream() {
+        return dwarfSections.getTypes().stream();
     }
 
     /**
      * Retrieve a stream of all primitive types notified via the DebugTypeInfo API.
-     * 
+     *
      * @return a stream of all primitive types notified via the DebugTypeInfo API.
      */
-    protected Iterable<PrimitiveTypeEntry> primitiveTypes() {
-        return dwarfSections.getPrimitiveTypes();
+    protected Stream<PrimitiveTypeEntry> primitiveTypeStream() {
+        return typeStream().filter(TypeEntry::isPrimitive).map(entry -> ((PrimitiveTypeEntry) entry));
     }
 
     /**
-     * Retrieve a stream of all instance classes, including interfaces and enums, notified via the
-     * DebugTypeInfo API.
+     * Retrieve a stream of all array types notified via the DebugTypeInfo API.
      *
-     * @return a stream of all instance classes notified via the DebugTypeInfo API.
+     * @return a stream of all array types notified via the DebugTypeInfo API.
      */
-    protected Iterable<TypedefEntry> typedefs() {
-        return dwarfSections.getTypedefs();
+    protected Stream<ArrayTypeEntry> arrayTypeStream() {
+        return typeStream().filter(TypeEntry::isArray).map(entry -> ((ArrayTypeEntry) entry));
     }
 
     /**
@@ -704,15 +810,67 @@ protected TypeEntry lookupType(ResolvedJavaType type) {
         return dwarfSections.lookupTypeEntry(type);
     }
 
-    protected int getTypeIndex(TypeEntry typeEntry) {
+    protected int getCUIndex(ClassEntry classEntry) {
+        if (!contentByteArrayCreated()) {
+            return 0;
+        }
+        return dwarfSections.getCUIndex(classEntry);
+    }
+
+    protected void setCUIndex(ClassEntry classEntry, int idx) {
+        dwarfSections.setCUIndex(classEntry, idx);
+    }
+
+    protected void setCodeRangesIndex(ClassEntry classEntry, int pos) {
+        dwarfSections.setCodeRangesIndex(classEntry, pos);
+    }
+
+    protected int getCodeRangesIndex(ClassEntry classEntry) {
         if (!contentByteArrayCreated()) {
-            return -1;
+            return 0;
         }
-        return dwarfSections.getTypeIndex(typeEntry);
+        return dwarfSections.getCodeRangesIndex(classEntry);
+    }
+
+    protected void setLocationListIndex(ClassEntry classEntry, int pos) {
+        dwarfSections.setLocationListIndex(classEntry, pos);
+    }
+
+    protected int getLocationListIndex(ClassEntry classEntry) {
+        return dwarfSections.getLocationListIndex(classEntry);
     }
 
-    protected void setTypeIndex(TypeEntry typeEntry, int pos) {
-        dwarfSections.setTypeIndex(typeEntry, pos);
+    protected void setLineIndex(ClassEntry classEntry, int pos) {
+        dwarfSections.setLineIndex(classEntry, pos);
+    }
+
+    protected int getLineIndex(ClassEntry classEntry) {
+        if (!contentByteArrayCreated()) {
+            return 0;
+        }
+        return dwarfSections.getLineIndex(classEntry);
+    }
+
+    protected void setLinePrologueSize(ClassEntry classEntry, int pos) {
+        dwarfSections.setLinePrologueSize(classEntry, pos);
+    }
+
+    protected int getLinePrologueSize(ClassEntry classEntry) {
+        if (!contentByteArrayCreated()) {
+            return 0;
+        }
+        return dwarfSections.getLinePrologueSize(classEntry);
+    }
+
+    protected void setFieldDeclarationIndex(StructureTypeEntry entry, String fieldName, int pos) {
+        dwarfSections.setFieldDeclarationIndex(entry, fieldName, pos);
+    }
+
+    protected int getFieldDeclarationIndex(StructureTypeEntry entry, String fieldName) {
+        if (!contentByteArrayCreated()) {
+            return 0;
+        }
+        return dwarfSections.getFieldDeclarationIndex(entry, fieldName);
     }
 
     protected void setMethodDeclarationIndex(MethodEntry methodEntry, int pos) {
@@ -726,52 +884,54 @@ protected int getMethodDeclarationIndex(MethodEntry methodEntry) {
         return dwarfSections.getMethodDeclarationIndex(methodEntry);
     }
 
-    protected void setAbstractInlineMethodIndex(CompiledMethodEntry compiledEntry, MethodEntry methodEntry, int pos) {
-        dwarfSections.setAbstractInlineMethodIndex(compiledEntry, methodEntry, pos);
+    protected void setAbstractInlineMethodIndex(ClassEntry classEntry, MethodEntry methodEntry, int pos) {
+        dwarfSections.setAbstractInlineMethodIndex(classEntry, methodEntry, pos);
     }
 
-    protected int getAbstractInlineMethodIndex(CompiledMethodEntry compiledEntry, MethodEntry methodEntry) {
+    protected int getAbstractInlineMethodIndex(ClassEntry classEntry, MethodEntry methodEntry) {
         if (!contentByteArrayCreated()) {
             return 0;
         }
-        return dwarfSections.getAbstractInlineMethodIndex(compiledEntry, methodEntry);
+        return dwarfSections.getAbstractInlineMethodIndex(classEntry, methodEntry);
     }
 
     /**
      * Record the info section offset of a local (or parameter) declaration DIE appearing as a child
      * of a standard method declaration or an abstract inline method declaration.
      *
+     * @param classEntry the class of the top level method being declared or inlined into
      * @param methodEntry the method being declared or inlined.
      * @param localInfo the local or param whose index is to be recorded.
      * @param index the info section offset to be recorded.
      */
-    protected void setMethodLocalIndex(CompiledMethodEntry compiledEntry, MethodEntry methodEntry, DebugLocalInfo localInfo, int index) {
-        dwarfSections.setMethodLocalIndex(compiledEntry, methodEntry, localInfo, index);
+    protected void setMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, DebugLocalInfo localInfo, int index) {
+        dwarfSections.setMethodLocalIndex(classEntry, methodEntry, localInfo, index);
     }
 
     /**
      * Retrieve the info section offset of a local (or parameter) declaration DIE appearing as a
      * child of a standard method declaration or an abstract inline method declaration.
      *
+     * @param classEntry the class of the top level method being declared or inlined into
      * @param methodEntry the method being declared or imported
      * @param localInfo the local or param whose index is to be retrieved.
      * @return the associated info section offset.
      */
-    protected int getMethodLocalIndex(CompiledMethodEntry compiledEntry, MethodEntry methodEntry, DebugLocalInfo localInfo) {
+    protected int getMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, DebugLocalInfo localInfo) {
         if (!contentByteArrayCreated()) {
             return 0;
         }
-        return dwarfSections.getMethodLocalIndex(compiledEntry, methodEntry, localInfo);
+        return dwarfSections.getMethodLocalIndex(classEntry, methodEntry, localInfo);
     }
 
     /**
      * Record the info section offset of a local (or parameter) location DIE associated with a top
      * level (primary) or inline method range.
-     * 
+     *
      * @param range the top level (primary) or inline range to which the local (or parameter)
      *            belongs.
      * @param localInfo the local or param whose index is to be recorded.
-     * @param index the info section offset to be recorded.
+     * @param index the info section offset index to be recorded.
      */
     protected void setRangeLocalIndex(Range range, DebugLocalInfo localInfo, int index) {
         dwarfSections.setRangeLocalIndex(range, localInfo, index);
@@ -780,16 +940,13 @@ protected void setRangeLocalIndex(Range range, DebugLocalInfo localInfo, int ind
     /**
      * Retrieve the info section offset of a local (or parameter) location DIE associated with a top
      * level (primary) or inline method range.
-     * 
+     *
      * @param range the top level (primary) or inline range to which the local (or parameter)
      *            belongs.
      * @param localInfo the local or param whose index is to be retrieved.
      * @return the associated info section offset.
      */
     protected int getRangeLocalIndex(Range range, DebugLocalInfo localInfo) {
-        if (!contentByteArrayCreated()) {
-            return 0;
-        }
         return dwarfSections.getRangeLocalIndex(range, localInfo);
     }
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfStrSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfStrSectionImpl.java
index b64043997f67..101ae05cb34c 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfStrSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfStrSectionImpl.java
@@ -26,8 +26,8 @@
 
 package com.oracle.objectfile.runtime.dwarf;
 
-import com.oracle.objectfile.runtime.debugentry.StringEntry;
-import com.oracle.objectfile.runtime.dwarf.constants.DwarfSectionName;
+import com.oracle.objectfile.debugentry.StringEntry;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName;
 import jdk.graal.compiler.debug.DebugContext;
 
 /**
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfAccess.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfAccess.java
deleted file mode 100644
index fb983dfa3be0..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfAccess.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.dwarf.constants;
-
-/**
- * DW_AT_Accessibility attribute values.
- */
-public enum DwarfAccess {
-    DW_ACCESS_public((byte) 1),
-    DW_ACCESS_protected((byte) 2),
-    DW_ACCESS_private((byte) 3);
-
-    private final byte value;
-
-    DwarfAccess(byte b) {
-        value = b;
-    }
-
-    public byte value() {
-        return value;
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfAttribute.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfAttribute.java
deleted file mode 100644
index b0a3a55d5601..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfAttribute.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.dwarf.constants;
-
-/**
- * All the Dwarf attributes needed to populate DIEs generated by GraalVM.
- */
-public enum DwarfAttribute {
-    DW_AT_null(0x0),
-    DW_AT_location(0x02),
-    DW_AT_name(0x3),
-    DW_AT_byte_size(0x0b),
-    DW_AT_bit_size(0x0d),
-    DW_AT_stmt_list(0x10),
-    DW_AT_low_pc(0x11),
-    DW_AT_hi_pc(0x12),
-    DW_AT_language(0x13),
-    DW_AT_comp_dir(0x1b),
-    DW_AT_containing_type(0x1d),
-    DW_AT_inline(0x20),
-    DW_AT_producer(0x25),
-    DW_AT_abstract_origin(0x31),
-    DW_AT_accessibility(0x32),
-    DW_AT_artificial(0x34),
-    DW_AT_count(0x37),
-    DW_AT_data_member_location(0x38),
-    @SuppressWarnings("unused")
-    DW_AT_decl_column(0x39),
-    DW_AT_decl_file(0x3a),
-    DW_AT_decl_line(0x3b),
-    DW_AT_declaration(0x3c),
-    DW_AT_encoding(0x3e),
-    DW_AT_external(0x3f),
-    @SuppressWarnings("unused")
-    DW_AT_return_addr(0x2a),
-    @SuppressWarnings("unused")
-    DW_AT_frame_base(0x40),
-    DW_AT_specification(0x47),
-    DW_AT_type(0x49),
-    DW_AT_data_location(0x50),
-    DW_AT_use_UTF8(0x53),
-    DW_AT_ranges(0x55),
-    DW_AT_call_file(0x58),
-    DW_AT_call_line(0x59),
-    DW_AT_object_pointer(0x64),
-    DW_AT_linkage_name(0x6e);
-
-    private final int value;
-
-    DwarfAttribute(int v) {
-        value = v;
-    }
-
-    public int value() {
-        return value;
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfEncoding.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfEncoding.java
deleted file mode 100644
index 2a77d43545b2..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfEncoding.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.dwarf.constants;
-
-/**
- * DW_AT_encoding attribute values.
- */
-public enum DwarfEncoding {
-    @SuppressWarnings("unused")
-    DW_ATE_address((byte) 0x1),
-    DW_ATE_boolean((byte) 0x2),
-    DW_ATE_float((byte) 0x4),
-    DW_ATE_signed((byte) 0x5),
-    DW_ATE_signed_char((byte) 0x6),
-    DW_ATE_unsigned((byte) 0x7),
-    @SuppressWarnings("unused")
-    DW_ATE_unsigned_char((byte) 0x8);
-
-    private final byte value;
-
-    DwarfEncoding(byte b) {
-        value = b;
-    }
-
-    public byte value() {
-        return value;
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfExpressionOpcode.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfExpressionOpcode.java
deleted file mode 100644
index 37c3a7f5a05e..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfExpressionOpcode.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.dwarf.constants;
-
-/**
- * Values used to build DWARF expressions and locations.
- */
-public enum DwarfExpressionOpcode {
-    DW_OP_addr((byte) 0x03),
-    @SuppressWarnings("unused")
-    DW_OP_deref((byte) 0x06),
-    DW_OP_dup((byte) 0x12),
-    DW_OP_and((byte) 0x1a),
-    DW_OP_not((byte) 0x20),
-    DW_OP_plus((byte) 0x22),
-    DW_OP_shl((byte) 0x24),
-    DW_OP_shr((byte) 0x25),
-    DW_OP_bra((byte) 0x28),
-    DW_OP_eq((byte) 0x29),
-    DW_OP_lit0((byte) 0x30),
-    DW_OP_reg0((byte) 0x50),
-    DW_OP_breg0((byte) 0x70),
-    DW_OP_regx((byte) 0x90),
-    DW_OP_bregx((byte) 0x92),
-    DW_OP_push_object_address((byte) 0x97),
-    DW_OP_implicit_value((byte) 0x9e),
-    DW_OP_stack_value((byte) 0x9f);
-
-    private final byte value;
-
-    DwarfExpressionOpcode(byte b) {
-        value = b;
-    }
-
-    public byte value() {
-        return value;
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfFlag.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfFlag.java
deleted file mode 100644
index 2331185bf3a0..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfFlag.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.dwarf.constants;
-
-/**
- * DW_FORM_flag only has two possible attribute values.
- */
-public enum DwarfFlag {
-    @SuppressWarnings("unused")
-    DW_FLAG_false((byte) 0),
-    DW_FLAG_true((byte) 1);
-
-    private final byte value;
-
-    DwarfFlag(byte b) {
-        value = b;
-    }
-
-    public byte value() {
-        return value;
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfForm.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfForm.java
deleted file mode 100644
index e272dc6c970c..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfForm.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.dwarf.constants;
-
-/**
- * All the Dwarf attribute forms needed to type attribute values generated by GraalVM.
- */
-public enum DwarfForm {
-    DW_FORM_null(0x0),
-    DW_FORM_addr(0x1),
-    DW_FORM_data2(0x05),
-    DW_FORM_data4(0x6),
-    @SuppressWarnings("unused")
-    DW_FORM_data8(0x7),
-    @SuppressWarnings("unused")
-    DW_FORM_string(0x8),
-    @SuppressWarnings("unused")
-    DW_FORM_block1(0x0a),
-    DW_FORM_ref_addr(0x10),
-    @SuppressWarnings("unused")
-    DW_FORM_ref1(0x11),
-    @SuppressWarnings("unused")
-    DW_FORM_ref2(0x12),
-    DW_FORM_ref4(0x13),
-    @SuppressWarnings("unused")
-    DW_FORM_ref8(0x14),
-    DW_FORM_sec_offset(0x17),
-    DW_FORM_data1(0x0b),
-    DW_FORM_flag(0xc),
-    DW_FORM_strp(0xe),
-    DW_FORM_expr_loc(0x18);
-
-    private final int value;
-
-    DwarfForm(int i) {
-        value = i;
-    }
-
-    public int value() {
-        return value;
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfFrameValue.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfFrameValue.java
deleted file mode 100644
index 4ea3ca2146f4..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfFrameValue.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.dwarf.constants;
-
-/**
- * Constants that appear in CIE and FDE frame section entries.
- */
-public enum DwarfFrameValue {
-    DW_CFA_CIE_version((byte) 1),
-    /* Values encoded in high 2 bits. */
-    DW_CFA_advance_loc((byte) 0x1),
-    DW_CFA_offset((byte) 0x2),
-    DW_CFA_restore((byte) 0x3),
-    /* Values encoded in low 6 bits. */
-    DW_CFA_nop((byte) 0x0),
-    @SuppressWarnings("unused")
-    DW_CFA_set_loc1((byte) 0x1),
-    DW_CFA_advance_loc1((byte) 0x2),
-    DW_CFA_advance_loc2((byte) 0x3),
-    DW_CFA_advance_loc4((byte) 0x4),
-    @SuppressWarnings("unused")
-    DW_CFA_offset_extended((byte) 0x5),
-    @SuppressWarnings("unused")
-    DW_CFA_restore_extended((byte) 0x6),
-    @SuppressWarnings("unused")
-    DW_CFA_undefined((byte) 0x7),
-    @SuppressWarnings("unused")
-    DW_CFA_same_value((byte) 0x8),
-    DW_CFA_register((byte) 0x9),
-    DW_CFA_def_cfa((byte) 0xc),
-    @SuppressWarnings("unused")
-    DW_CFA_def_cfa_register((byte) 0xd),
-    DW_CFA_def_cfa_offset((byte) 0xe);
-
-    private final byte value;
-
-    DwarfFrameValue(byte b) {
-        value = b;
-    }
-
-    public byte value() {
-        return value;
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfHasChildren.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfHasChildren.java
deleted file mode 100644
index 10b4584d06d7..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfHasChildren.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.dwarf.constants;
-
-/*
- * Compile unit DIE header has_children attribute values.
- */
-public enum DwarfHasChildren {
-    DW_CHILDREN_no((byte) 0),
-    DW_CHILDREN_yes((byte) 1);
-
-    private final byte value;
-
-    DwarfHasChildren(byte b) {
-        value = b;
-    }
-
-    public byte value() {
-        return value;
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfInline.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfInline.java
deleted file mode 100644
index ae2b73bdf62a..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfInline.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.dwarf.constants;
-
-/*
- * Values for DW_AT_inline attribute.
- */
-public enum DwarfInline {
-    @SuppressWarnings("unused")
-    DW_INL_not_inlined((byte) 0),
-    DW_INL_inlined((byte) 1),
-    @SuppressWarnings("unused")
-    DW_INL_declared_not_inlined((byte) 2),
-    @SuppressWarnings("unused")
-    DW_INL_declared_inlined((byte) 3);
-
-    private final byte value;
-
-    DwarfInline(byte b) {
-        value = b;
-    }
-
-    public byte value() {
-        return value;
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfLanguage.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfLanguage.java
deleted file mode 100644
index b0259904cb96..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfLanguage.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.dwarf.constants;
-
-/*
- * DW_AT_language attribute has a range of pre-defined values but we
- * are only interested in Java.
- */
-public enum DwarfLanguage {
-    DW_LANG_Java((byte) 0xb);
-
-    private final byte value;
-
-    DwarfLanguage(byte b) {
-        value = b;
-    }
-
-    public byte value() {
-        return value;
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfLineOpcode.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfLineOpcode.java
deleted file mode 100644
index d112261254fd..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfLineOpcode.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.dwarf.constants;
-
-/**
- * Standard line section opcodes defined by Dwarf 4.
- */
-public enum DwarfLineOpcode {
-    /*
-     * 0 can be inserted as a prefix for extended opcodes.
-     */
-    DW_LNS_extended_prefix((byte) 0),
-    /*
-     * Append current state as matrix row 0 args.
-     */
-    DW_LNS_copy((byte) 1),
-    /*
-     * Increment address 1 uleb arg.
-     */
-    DW_LNS_advance_pc((byte) 2),
-    /*
-     * Increment line 1 sleb arg.
-     */
-    DW_LNS_advance_line((byte) 3),
-    /*
-     * Set file 1 uleb arg.
-     */
-    DW_LNS_set_file((byte) 4),
-    /*
-     * sSet column 1 uleb arg.
-     */
-    DW_LNS_set_column((byte) 5),
-    /*
-     * Flip is_stmt 0 args.
-     */
-    DW_LNS_negate_stmt((byte) 6),
-    /*
-     * Set end sequence and copy row 0 args.
-     */
-    DW_LNS_set_basic_block((byte) 7),
-    /*
-     * Increment address as per opcode 255 0 args.
-     */
-    DW_LNS_const_add_pc((byte) 8),
-    /*
-     * Increment address 1 ushort arg.
-     */
-    DW_LNS_fixed_advance_pc((byte) 9),
-    /*
-     * Increment address 1 ushort arg.
-     */
-    @SuppressWarnings("unused")
-    DW_LNS_set_prologue_end((byte) 10),
-    /*
-     * Increment address 1 ushort arg.
-     */
-    @SuppressWarnings("unused")
-    DW_LNS_set_epilogue_begin((byte) 11),
-    /*
-     * Extended line section opcodes defined by DWARF 2.
-     */
-    /*
-     * There is no extended opcode 0.
-     */
-    @SuppressWarnings("unused")
-    DW_LNE_undefined((byte) 0),
-    /*
-     * End sequence of addresses.
-     */
-    DW_LNE_end_sequence((byte) 1),
-    /*
-     * Set address as explicit long argument.
-     */
-    DW_LNE_set_address((byte) 2),
-    /*
-     * Set file as explicit string argument.
-     */
-    DW_LNE_define_file((byte) 3);
-
-    private final byte value;
-
-    DwarfLineOpcode(byte b) {
-        value = b;
-    }
-
-    public byte value() {
-        return value;
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfSectionName.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfSectionName.java
deleted file mode 100644
index a2b21e7c9d52..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfSectionName.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.dwarf.constants;
-
-/**
- * Various ELF sections created by GraalVM including all debug info sections. The enum sequence
- * starts with the text section (not defined in the DWARF spec and not created by debug info code).
- */
-public enum DwarfSectionName {
-    TEXT_SECTION(".text"),
-    DW_STR_SECTION(".debug_str"),
-    DW_LINE_SECTION(".debug_line"),
-    DW_FRAME_SECTION(".debug_frame"),
-    DW_ABBREV_SECTION(".debug_abbrev"),
-    DW_INFO_SECTION(".debug_info"),
-    DW_LOC_SECTION(".debug_loc"),
-    DW_ARANGES_SECTION(".debug_aranges"),
-    DW_RANGES_SECTION(".debug_ranges");
-
-    private final String value;
-
-    DwarfSectionName(String s) {
-        value = s;
-    }
-
-    public String value() {
-        return value;
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfTag.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfTag.java
deleted file mode 100644
index 2d3ded731b57..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfTag.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.dwarf.constants;
-
-/**
- * All the Dwarf tags needed to type DIEs generated by GraalVM.
- */
-public enum DwarfTag {
-    DW_TAG_null(0),
-    DW_TAG_array_type(0x01),
-    DW_TAG_class_type(0x02),
-    DW_TAG_formal_parameter(0x05),
-    DW_TAG_member(0x0d),
-    DW_TAG_pointer_type(0x0f),
-    DW_TAG_compile_unit(0x11),
-    DW_TAG_structure_type(0x13),
-    DW_TAG_typedef(0x16),
-    DW_TAG_union_type(0x17),
-    DW_TAG_inheritance(0x1c),
-    DW_TAG_subrange_type(0x21),
-    DW_TAG_base_type(0x24),
-    DW_TAG_constant(0x27),
-    DW_TAG_subprogram(0x2e),
-    DW_TAG_variable(0x34),
-    DW_TAG_namespace(0x39),
-    DW_TAG_unspecified_type(0x3b),
-    DW_TAG_inlined_subroutine(0x1d);
-
-    private final int value;
-
-    DwarfTag(int i) {
-        value = i;
-    }
-
-    public int value() {
-        return value;
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfVersion.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfVersion.java
deleted file mode 100644
index f49c4c1f50c2..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/constants/DwarfVersion.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.dwarf.constants;
-
-/**
- * The DWARF version values used by GraalVM.
- */
-public enum DwarfVersion {
-
-    /**
-     * Currently generated debug info relies on DWARF spec version 4. However, some sections may
-     * still need to be generated as version 2.
-     */
-    DW_VERSION_2((short) 2),
-    DW_VERSION_4((short) 4);
-
-    private final short value;
-
-    DwarfVersion(short s) {
-        value = s;
-    }
-
-    public short value() {
-        return value;
-    }
-
-}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateBFDNameProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateBFDNameProvider.java
new file mode 100644
index 000000000000..45765febdc8b
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateBFDNameProvider.java
@@ -0,0 +1,882 @@
+/*
+ * Copyright (c) 2012, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+package com.oracle.svm.core.debug;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.util.List;
+
+import org.graalvm.collections.EconomicMap;
+
+import com.oracle.svm.core.SubstrateUtil;
+import com.oracle.svm.core.UniqueShortNameProvider;
+import com.oracle.svm.core.util.VMError;
+
+import jdk.vm.ci.meta.JavaKind;
+import jdk.vm.ci.meta.JavaType;
+import jdk.vm.ci.meta.ResolvedJavaType;
+import jdk.vm.ci.meta.Signature;
+import jdk.vm.ci.meta.UnresolvedJavaType;
+
+
+public class SubstrateBFDNameProvider implements UniqueShortNameProvider {
+
+    public SubstrateBFDNameProvider(List<ClassLoader> ignore) {
+        this.ignoredLoaders = ignore;
+    }
+
+    @Override
+    public String uniqueShortName(ClassLoader loader, ResolvedJavaType declaringClass, String methodName, Signature methodSignature, boolean isConstructor) {
+        String loaderName = uniqueShortLoaderName(loader);
+        return bfdMangle(loaderName, declaringClass, methodName, methodSignature, isConstructor);
+    }
+
+    @Override
+    public String uniqueShortName(Member m) {
+        return bfdMangle(m);
+    }
+
+    @Override
+    public String uniqueShortLoaderName(ClassLoader loader) {
+        // no need to qualify classes loaded by a builtin loader
+        if (isBuiltinLoader(loader)) {
+            return "";
+        }
+        if (isGraalImageLoader(loader)) {
+            return "";
+        }
+        String name = SubstrateUtil.classLoaderNameAndId(loader);
+        // name will look like "org.foo.bar.FooBarClassLoader @1234"
+        // trim it down to something more manageable
+        // escaping quotes in the classlaoder name does not work in GDB
+        // but the name is still unique without quotes
+        name = SubstrateUtil.stripPackage(name);
+        name = stripOuterClass(name);
+        name = name.replace(" @", "_").replace("'", "").replace("\"", "");
+        return name;
+    }
+
+    private String classLoaderNameAndId(ResolvedJavaType type) {
+        return uniqueShortLoaderName(getClassLoader(type));
+    }
+
+    private static String stripOuterClass(String name) {
+        return name.substring(name.lastIndexOf('$') + 1);
+    }
+
+    private static ClassLoader getClassLoader(ResolvedJavaType type) {
+        if (type.isPrimitive()) {
+            return null;
+        }
+        if (type.isArray()) {
+            return getClassLoader(type.getElementalType());
+        }
+        return type.getClass().getClassLoader();
+    }
+
+    private final List<ClassLoader> ignoredLoaders;
+
+    private static final String BUILTIN_CLASSLOADER_NAME = "jdk.internal.loader.BuiltinClassLoader";
+
+    private static boolean isBuiltinLoader(ClassLoader loader) {
+        if (loader == null) {
+            return true;
+        }
+        // built in loaders are all subclasses of jdk.internal.loader.BuiltinClassLoader
+        Class<?> loaderClazz = loader.getClass();
+        do {
+            if (loaderClazz.getName().equals(BUILTIN_CLASSLOADER_NAME)) {
+                return true;
+            }
+            loaderClazz = loaderClazz.getSuperclass();
+        } while (loaderClazz != Object.class);
+
+        return false;
+    }
+
+    private boolean isGraalImageLoader(ClassLoader loader) {
+        // Graal installs its own system loader and loaders for application and image classes whose
+        // classes do not need qualifying with a loader id
+        if (ignoredLoaders.contains(loader)) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * mangle a resolved method name in a format the binutils demangler will understand. This should
+     * allow all Linux tools to translate mangled symbol names to recognisable Java names in the
+     * same format as derived from the DWARF info, i.e. fully qualified classname using '.'
+     * separator, member name separated using '::' and parameter/return types printed either using
+     * the Java primitive name or, for oops, as a pointer to a struct whose name is that of the Java
+     * type.
+     *
+     * @param declaringClass the class owning the method implementation
+     * @param memberName the simple name of the method
+     * @param methodSignature the signature of the method
+     * @param isConstructor true if this method is a constructor otherwise false
+     * @return a unique mangled name for the method
+     */
+    public String bfdMangle(String loaderName, ResolvedJavaType declaringClass, String memberName, Signature methodSignature, boolean isConstructor) {
+        /*-
+         * The bfd library demangle API currently supports decoding of Java names if they are
+         * using a scheme similar to that used for C++. Unfortunately, none of the tools which
+         * reply on this API pass on the details that the mangled name in question is for a
+         * Java method. However, it is still possible to pass a name to the demangler that the
+         * C++ demangle algorithm will correctly demangle to a Java name. The scheme used mirrors
+         * the one used to generate names for DWARF. It assumes that the linker can tolerate '.',
+         * '[' and ']' characters in an ELF symbol name, which is not a problem on Linux.
+         *
+         * The BFD Demangle Encoding and Algorithm:
+         *
+         * The bfd Java encoding expects java symbols in variant of C++ format.
+         *
+         * A mangled name starts with "_Z"
+         *
+         * Base Symbols:
+         *
+         * Base symbols encode with a decimal length prefix followed by the relevant characters:
+         *
+         *   Foo -> 3Foo
+         *   org.my.Foo -> 10org.my.Foo
+         *
+         * n.b. The latter encoding is acceptable by the demangler -- characters in the encoded
+         * symbol text are opaque as far as it is concerned.
+         *
+         * Qualified Class Name Symbols:
+         *
+         * The standard Java encoding assumes that the package separator '.' will be elided from
+         * symbol text by encoding a package qualified class name as a (possibly recursive)
+         * namespace encoding:
+         *
+         *   org.my.Foo -> N3org2my3FooE
+         *
+         * The bfd Java demangle algorithm understands that the leading base symbols in this
+         * encoding are package elements and will successfully demangle this latter encoding to
+         * "org.my.Foo". However, the default C++ demangle algorithm will demangle it to
+         * "org::my::Foo" since it regards the leading base symbols as naming namespaces.
+         *
+         * In consequence, the Graal mangler chooses to encode Java package qualified class names
+         * in the format preceding the last one i.e. as a single base symbol with embedded '.'
+         * characters:
+         *
+         *   org.my.Foo -> 10org.my.Foo
+         *
+         * Qualified Member Name Symbols:
+         *
+         * A member name is encoded by concatenating the class name and member selector
+         * as elements of a hierarchical (namespace) encoding:
+         *
+         *   org.my.Foo.doAFoo -> N10org.my.Foo6doAFooE
+         *   org.my.Foo.doAFoo -> N3org2my3Foo6doAFooE
+         *
+         * Note again that although the Java demangle algorithm expects the second of the above
+         * two formats the Graal mangler employs the preceding format where the package and class
+         * name are presented as a single base name with embedded '.' characters.
+         *
+         * Loader Qualified Class and Member Name Symbols:
+         *
+         * The above encoding scheme can fail to guarantee uniqueness of the encoding of a class
+         * name or the corresponding class component of a qualified member name. Duplication can
+         * occur when the same class bytecode is loaded by more than one class loader. Not all
+         * loaders are susceptible to this problem. However, for those that are susceptible a unique
+         * id String for the class loader needs to be composed with the class id String:
+         *
+         * {loader AppLoader @1234}org.my.Foo -> N14AppLoader_123410org.my.FooE
+         * {loader AppLoader @1234}org.my.Foo.doAFoo -> N14AppLoader_123410org.my.Foo6doAFooE
+         *
+         * Note that in order for the debugger and Linux tools to be able to link the demangled name
+         * to the corresponding DWARF records the same namespace identifier must be used to qualify the
+         * DWARF declaration of the class.
+         *
+         * Qualified Method Name With Signature Symbols:
+         *
+         * A full encoding for a method name requires concatenating encodings for the return
+         * type and parameter types to the method name encoding.
+         *
+         * <rType> <method> '(' <paramType>+ ')' -> <methodencoding> 'J' (<rTypeencoding> <paramencoding>+ | 'v')
+         *
+         * Return Types:
+         *
+         * The encoding for the return type is appended first. It is preceded with a 'J', to
+         * mark it as a return type rather than the first parameter type. A primitive return type
+         * is encoded using a single letter encoding (see below). An object return type employs the
+         * qualified class encoding described with a preceding 'P', to mark it as a pointer type:
+         *
+         *   java.lang.String doAFoo(...) -> <methodencoding> JP16java.lang.String <paramencoding>+
+         *
+         * An array return type also requires a J and P prefix. Details of the array type encoding
+         * are provided below.
+         *
+         * Note that a pointer type is employed for consistency with the DWARF type scheme.
+         * That scheme also models oops as pointers to a struct whose name is taken from the
+         * Java class.
+         *
+         * Note also that a foreign pointer type -- i.e. a Java interface or, occasionally, class used to
+         * model a native pointer -- is encoded without the P prefix. That is because for such cases the
+         * DWARF encoding uses the Java name as a typedef to the relevant pointer type.
+         *
+         * Void Signatures and Void Return Types:
+         *
+         * If the method has no parameters then this is represented by encoding the single type
+         * void using the standard primitive encoding for that type:
+         *
+         *   void -> v
+         *
+         * The void encoding is also used to encode the return type of a void method:
+         *
+         *   void doAFoobar(...) <methodencoding> Jv <paramencoding>+
+         *
+         * Non-void Signatures
+         *
+         * Parameter type encodings are simply appended in order. The demangle algorithm detects
+         * the parameter count by decoding each successive type encoding. Parameters have either
+         * primitive type, non-array object type or array type.
+         *
+         * Primitive Parameter Types
+         *
+         * Primitive parameter types (or return types) may be encoded using a single letter
+         * where the symbol encodes a C++ primitive type with the same name and bit layout and
+         * interpretation as the Java type:
+         *
+         *   short -> s
+         *   int -> i
+         *   long -> l
+         *   float -> f
+         *   double -> d
+         *   void -> v
+         *
+         * Other primitive types need to be encoded as symbols
+         *
+         *   boolean -> 7boolean
+         *   byte -> 4byte
+         *   char -> 4char
+         *
+         * In these latter cases the single letter encodings that identifies a C++ type with the
+         * same bit layout and interpretation (respectively, b, c and t) encode for differently
+         * named C++ type (respectively "bool", "char", and unsigned short). Their use would cause
+         * method signatures to be printed with a wrong and potentially misleading name e.g. if
+         * method void Foo::foo(boolean, byte, char) were encoded as _ZN3Foo3fooEJvbct it would
+         * then demangle as Foo::foo(bool, char, unsigned short).
+         *
+         * It would be possible to encode the name "char" using symbol c. However, that would
+         * suggest that the type is a signed 8 bit value rather than a 16 bit unsigned value.
+         * Note that the info section includes appropriate definitions of these three primitive
+         * types with the appropriate names so tools which attempt to resolve the symbol names to
+         * type info should work correctly.
+         *
+         * Object parameter types (which includes interfaces and enums) are encoded using the class
+         * name encoding described above preceded by  prefix 'P'.
+         *
+         * java.lang.String doAFoo(int, java.lang.String) ->  <methodencoding> JP16java.lang.StringiP16java.lang.String
+         *
+         * Note that no type is emitted for the implicit 'this' argument
+         *
+         * Array Parameter Types:
+         *
+         * Array parameter types are encoded as bare symbols with the relevant number of square bracket pairs
+         * for their dimension:
+         *
+         *   Foo[] -> 5Foo[]
+         *   java.lang.Object[][] -> 20java.lang.Object[][]
+         *
+         * Note that the Java bfd encoding expects arrays to be encoded as template type instances
+         * using a pseudo-template named JArray, with 'I' and 'E' appended as start and end delimiters
+         * for the embedded template argument lists:
+         *
+         * Foo[] ->  P6JArrayI3FooE
+         * Foo[][] ->  P6JArrayIP6JArrayI3FooEE
+         *
+         * The bfd Java demangler recognises this special pseudo template and translates it back into
+         * the expected Java form i.e. decode(JArray<XXX>) -> concatenate(decode(XXX), "[]"). However,
+         * the default bfd C++ demangler will not perform this translation.
+         *
+         * Foreign pointer parameter types (which may be modeled as interfaces and enums) are encoded using the
+         * class name encoding but without the prefix 'P'.
+         *
+         * com.oracle.svm.core.posix.PosixUtils.dlsym(org.graalvm.word.PointerBase, java.lang.String)
+         *   ->
+         * <method-encoding>  28org.graalvm.word.PointerBaseP16java.lang.String
+         *
+         *
+         * Note that no type is emitted for the implicit 'this' argument
+         *
+         * Substitutions:
+         *
+         * Symbol elements are indexed and can be referenced using a shorthand index, saving
+         * space in the symbol encoding.
+         *
+         * For example, consider C++ equivalent of String.compareTo and its corresponding encoding
+         *
+         *   int java::lang::String::compareTo(java::lang::String)
+         *      -> _ZN4java4lang6String9compareToEJiPS1_
+         *
+         * The symbol encodings 4java, 4lang and 6String and 9compareTo establish
+         * successive bindings for the indexed substitution variables S_, S0_, S1_ and S2_ to
+         * the respective names java, java::lang, java::lang::String and java::lang::String::compareTo
+         * (note that bindings accumulate preceding elements when symbols occur inside a namespace).
+         * The encoding of the argument list uses S1_ references the 3rd binding i.e. the class
+         * name. The problem with this, as seen before when using namespaces is that the demangler
+         * accumulates names using a '::' separator between the namespace components rather than the
+         * desired  '.' separator.
+         *
+         * The Graal encoding can work around the namespace separator as shown earlier and still employ
+         * symbols as prefixes in order to produce more concise encodings. The full encoding for the
+         *
+         *   int String.compareTo(java.lang.String)
+         *      -> _ZN16java.lang.String9compareToEJiP16java.lang.String
+         *
+         * However, it is also possible to encode this more concisely as
+         *
+         *   int String.compareTo(java.lang.String)
+         *      -> _ZN16java.lang.String9compareToEJiPS_
+         *
+         * S_ translates to the first symbol introduced in the namespace i.e. java.lang.String.
+         *
+         * Substitutions can also occur when parameter types are repeated e.g.
+         *
+         * int Arrays.NaturalOrder.compare(Object first, Object second)
+         *      -> _ZN19Arrays$NaturalOrder7compareEJiPP16java.lang.ObjectPS_2
+         * 
+         * In this case the class name symbol 19Arrays$NaturalOrder binds $_ to Arrays$NaturalOrder,
+         * the method name symbol 7compare binds $1_ to Arrays$NaturalOrder::compareTo and the
+         * first parameter type name symbol 16java.lang.Object binds $2_ to java.lang.Object.
+         *
+         * Indexed symbol references are encoded as "S_", "S0_", ... "S9_", "SA_", ... "SZ_", "S10_", ...
+         * i.e. after "$_" for index 0, successive encodings for index i embded the base 36 digits for
+         * (i - 1) between "S" and "_".
+         */
+        return new BFDMangler(this).mangle(loaderName, declaringClass, memberName, methodSignature, isConstructor);
+    }
+
+    /**
+     * mangle a reflective member name in a format the binutils demangler will understand. This
+     * should allow all Linux tools to translate mangled symbol names to recognisable Java names in
+     * the same format as derived from the DWARF info, i.e. fully qualified classname using '.'
+     * separator, member name separated using '::' and parameter/return types printed either using
+     * the Java primitive name or, for oops, as a pointer to a struct whose name is that of the Java
+     * type.
+     *
+     * @param m the reflective member whose name is to be mangled
+     * @return a unique mangled name for the method
+     */
+    public String bfdMangle(Member m) {
+        return new BFDMangler(this).mangle(m);
+    }
+
+    private static class BFDMangler {
+
+        /*
+         * The mangler tracks certain elements as they are inserted into the mangled name and bind
+         * short symbol name to use as substitutions. If the same element needs to be re-inserted
+         * the mangler can embed the short symbol instead of generating the previously mangled full
+         * text. Short symbol names are bound in a well-defined sequence at well-defined points
+         * during mangling. This allows the demangler to identify exactly which text in the previous
+         * input must be used to replace a short symbol name.
+         *
+         * A binding occurs whenever a simple name is mangled outside of a namespace. For example, a
+         * top level class name mangled as 3Hello will bind the next available short name with the
+         * result that it will demangle to Hello and can be used to replace later occurrences of the
+         * string Hello.
+         *
+         * When a sequence of simple names is mangled inside a namespace substitution bindings are
+         * recorded for each successive composite namespace prefix but not for the final symbol
+         * itself. For example, when method name Hello::main is mangled to N5Hello4main4 a single
+         * binding is recorded which demangles to Hello. If a class has been loaded by an
+         * application loader and, hence, has a name which includes a loader namespace prefix (to
+         * avoid the possibility of the same named class being loaded by two different loaders) then
+         * the method name, say AppCL504::Hello::main, would mangle to a namespace encoding with 3
+         * elements N8AppCL5045Hello4mainE which would introduce two bindings, the first demangling
+         * to AppCL504 and the second to AppCL504::Hello.
+         *
+         * A binding is also recorded whenever a pointer type is mangled. The bound symbol demangles
+         * to whatever the text decoded from the scope of the P followed by a '*' to translate it to
+         * a pointer type. For example, when the type in parameter list (Foo*) is encoded as P3Foo a
+         * binding is recorded for the pointer as well as for the type Foo. The first binding
+         * demangles to Foo. The second binding demangles to Foo*.
+         *
+         * n.b. repeated use of the pointer operator results in repeated bindings. So, if a
+         * parameter with type Foo** were to be encoded as PP3Foo three bindings would recorded
+         * which demangle to Foo, Foo* and Foo**. However, multiple indirections do not occur in
+         * Java signatures.
+         *
+         * n.b.b. repeated mentions of the same symbol would redundantly record a binding. For
+         * example if int Hello::compareTo(Hello*) were mangled to _ZN5Hello9compareToEJiP5Hello the
+         * resulting bindings would be S_ ==> Hello, S0_ ==> Hello::compareTo, S1_ ==> Hello and S2_
+         * ==> Hello* i.e. both S_ and S1_ would demangle to Hello. This situation should never
+         * arise. A name can always be correctly encoded without repeats. In the above example that
+         * would be _ZN5Hello9compareToEJiPS_.
+         */
+        final SubstrateBFDNameProvider nameProvider;
+        final StringBuilder sb;
+
+        // A list of lookup names identifying substituted elements. A prospective name for an
+        // element that is about to be encoded can be looked up in this list. If a match is found
+        // the list index can be used to identify the relevant short symbol. If it is not found
+        // then inserting the name serves to allocate the short name associated with the inserted
+        // element's result index.
+        /**
+         * A map relating lookup names to the index for the corresponding short name with which it
+         * can be substituted. A prospective name for an element that is about to be encoded can be
+         * looked up in this list. If a match is found the list index can be used to identify the
+         * relevant short symbol. If it is not found then it can be inserted to allocate the next
+         * short name, using the current size of the map to identify the next available index.
+         */
+        EconomicMap<LookupName, Integer> bindings;
+
+        /**
+         * A lookup name is used as a key to record and subsequently lookup a short symbol that can
+         * replace one or more elements of a mangled name. A lookup name is usually just a simple
+         * string, i.e. some text that is to be mangled and to which the corresponding short symbol
+         * should decode. However, substitutions for namespace prefixes (e.g. AppLoader506::Foo)
+         * require a composite lookup name that composes a namespace prefix (AppLoader506) with a
+         * trailing simple name (Foo). The corresponding short symbol will demangle to the string
+         * produced by composing the prefix and simple name with a :: separator (i.e. restoring
+         * AppLoader506::Foo). Short symbols for pointer types (e.g. Foo* or AppLoader506::Foo*)
+         * require a pointer lookup name that identifies the substituted text as a pointer to some
+         * underlying type (e.g. Foo or AppLoader506::Foo). The corresponding short symbol will
+         * demangle to the string produced by demangling the underlying type with a * suffix. In
+         * theory the prefix for a pointer type lookup name might be defined by another pointer type
+         * lookup name (if, say, we needed to encode type Foo**). In practice, that case should not
+         * arise with Java method signatures.
+         */
+        private sealed interface LookupName permits SimpleLookupName, CompositeLookupName {
+        }
+
+        private sealed interface CompositeLookupName extends LookupName permits NamespaceLookupName, PointerLookupName {
+        }
+
+        private record SimpleLookupName(String value) implements LookupName {
+            @Override
+            public String toString() {
+                return value;
+            }
+        }
+
+        private record NamespaceLookupName(String prefix, LookupName tail) implements CompositeLookupName {
+            @Override
+            public String toString() {
+                return prefix + "::" + tail.toString();
+            }
+        }
+
+        private record PointerLookupName(LookupName tail) implements CompositeLookupName {
+            @Override
+            public String toString() {
+                return tail.toString() + "*";
+            }
+        }
+
+        BFDMangler(SubstrateBFDNameProvider provider) {
+            nameProvider = provider;
+            sb = new StringBuilder("_Z");
+            bindings = EconomicMap.create();
+        }
+
+        public String mangle(String loaderName, ResolvedJavaType declaringClass, String memberName, Signature methodSignature, boolean isConstructor) {
+            String fqn = declaringClass.toJavaName();
+            String selector = memberName;
+            if (isConstructor) {
+                assert methodSignature != null;
+                // replace <init> with the class name n.b. it may include a disambiguating suffix
+                assert selector.startsWith("<init>");
+                String replacement = fqn;
+                int index = replacement.lastIndexOf('.');
+                if (index >= 0) {
+                    replacement = fqn.substring(index + 1);
+                }
+                selector = selector.replace("<init>", replacement);
+            }
+            mangleClassAndMemberName(loaderName, fqn, selector);
+            if (methodSignature != null) {
+                if (!isConstructor) {
+                    mangleReturnType(methodSignature, declaringClass);
+                }
+                mangleParams(methodSignature, declaringClass);
+            }
+            return sb.toString();
+        }
+
+        public String mangle(Member member) {
+            Class<?> declaringClass = member.getDeclaringClass();
+            String loaderName = nameProvider.uniqueShortLoaderName(declaringClass.getClassLoader());
+            String className = declaringClass.getName();
+            String selector = member.getName();
+            boolean isConstructor = member instanceof Constructor<?>;
+            boolean isMethod = member instanceof Method;
+
+            if (isConstructor) {
+                assert selector.equals("<init>");
+                selector = SubstrateUtil.stripPackage(className);
+            }
+            mangleClassAndMemberName(loaderName, className, selector);
+            if (isMethod) {
+                Method method = (Method) member;
+                mangleReturnType(method.getReturnType());
+            }
+            if (isConstructor || isMethod) {
+                Executable executable = (Executable) member;
+                mangleParams(executable.getParameters());
+            }
+            return sb.toString();
+        }
+
+        private void mangleWriteSimpleName(String s) {
+            // a simple name starting with a digit would invalidate the C++ mangled encoding scheme
+            assert !s.startsWith("[0-9]");
+            sb.append(s.length());
+            sb.append(s);
+        }
+
+        private void mangleWriteSubstitutableNameRecord(String name) {
+            LookupName lookupName = new SimpleLookupName(name);
+            if (!substituteName(new SimpleLookupName(name))) {
+                // failed so mangle the name and create a binding to track it
+                mangleWriteSimpleName(name);
+                recordName(lookupName);
+            }
+        }
+
+        private void mangleWriteSubstitutableNameNoRecord(String name) {
+            LookupName lookupName = new SimpleLookupName(name);
+            if (!substituteName(lookupName)) {
+                // failed so mangle the name
+                mangleWriteSimpleName(name);
+            }
+        }
+
+        private void mangleWriteSubstitutablePrefixedName(String prefix, String name) {
+            // this should only be called when inserting a sequence into a namespace
+            assert sb.charAt(sb.length() - 1) == 'N';
+            // we can substitute both symbols
+            mangleWriteSubstitutableNameRecord(prefix);
+            // In theory the trailing name at the end of the namespace ought
+            // to be able to be encoded with a short name i.e. we ought to be
+            // able to call mangleWriteSubstitutableNameNoRecord(name) at this
+            // point. Indeed, the demangler *will* translate a substitution when it
+            // is inserted at the end of a namespace e.g.
+            //
+            // c++filt _ZN3foo3barS_E --> foo::bar::foo
+            //
+            // However, gdb will barf on the $_ and refuse to translate the symbol
+            // So, in order to keep gdb happy we have to avoid translating this
+            // final name ane emit it as a simple name e.g. with the above example
+            // we would generate _ZN3foo3bar3fooE.
+
+            mangleWriteSimpleName(name);
+        }
+
+        private void mangleWriteSubstitutablePrefixedName(String prefix1, String prefix2, String name) {
+            // this should only be called when inserting a sequence into a namespace
+            assert sb.charAt(sb.length() - 1) == 'N';
+            // we can substitute the composed prefix followed by the name
+            // or we can substitute all three individual symbols
+            LookupName simpleLookupName = new SimpleLookupName(prefix2);
+            LookupName namespaceLookupName = new NamespaceLookupName(prefix1, simpleLookupName);
+            // try substituting the combined prefix
+            if (!substituteName(namespaceLookupName)) {
+                // we may still be able to establish a binding for the first prefix
+                mangleWriteSubstitutableNameRecord(prefix1);
+                // we cannot establish a binding for the trailing prefix
+                mangleWriteSubstitutableNameNoRecord(prefix2);
+                recordName(namespaceLookupName);
+            }
+            // see comment in previous method as to why we call mangleWriteSimpleName
+            // instead of mangleWriteSubstitutableNameNoRecord(name)
+            mangleWriteSimpleName(name);
+        }
+
+        private boolean substituteName(LookupName name) {
+            Integer index = bindings.get(name);
+            if (index != null) {
+                writeSubstitution(index.intValue());
+                return true;
+            }
+            return false;
+        }
+
+        private static boolean encodeLoaderName(String loaderName) {
+            return loaderName != null && !loaderName.isEmpty();
+        }
+
+        private void mangleClassAndMemberName(String loaderName, String className, String methodName) {
+            sb.append('N');
+            if (encodeLoaderName(loaderName)) {
+                mangleWriteSubstitutablePrefixedName(loaderName, className, methodName);
+            } else {
+                mangleWriteSubstitutablePrefixedName(className, methodName);
+            }
+            sb.append('E');
+        }
+
+        private void mangleClassName(String loaderName, String className) {
+            boolean encodeLoaderName = encodeLoaderName(loaderName);
+            if (encodeLoaderName) {
+                sb.append('N');
+                // only leading elements of namespace encoding may be recorded as substitutions
+                mangleWriteSubstitutablePrefixedName(loaderName, className);
+                sb.append('E');
+            } else {
+                mangleWriteSubstitutableNameRecord(className);
+            }
+        }
+
+        private void mangleClassPointer(String loaderName, String className) {
+            boolean encodeLoaderName = encodeLoaderName(loaderName);
+            LookupName classLookup = new SimpleLookupName(className);
+            LookupName lookup = classLookup;
+            if (encodeLoaderName) {
+                lookup = new NamespaceLookupName(loaderName, classLookup);
+            }
+            PointerLookupName pointerLookup = new PointerLookupName(lookup);
+            // see if we can use a short name for the pointer
+            if (!substituteName(pointerLookup)) {
+                // failed - so we need to mark this as a pointer,
+                // encode the class name and (only) then record a
+                // binding for the pointer, ensuring that bindings
+                // for the pointer type and its referent appear in
+                // the expected order
+                sb.append("P");
+                mangleClassName(loaderName, className);
+                recordName(pointerLookup);
+            }
+        }
+
+        private void mangleReturnType(Signature methodSignature, ResolvedJavaType owner) {
+            sb.append('J');
+            mangleType(methodSignature.getReturnType(owner));
+        }
+
+        private void mangleReturnType(Class<?> type) {
+            sb.append('J');
+            mangleType(type);
+        }
+
+        private void mangleParams(Signature methodSignature, ResolvedJavaType owner) {
+            int count = methodSignature.getParameterCount(false);
+            if (count == 0) {
+                mangleTypeChar('V');
+            } else {
+                for (int i = 0; i < count; i++) {
+                    mangleType(methodSignature.getParameterType(i, owner));
+                }
+            }
+        }
+
+        private void mangleParams(Parameter[] params) {
+            if (params.length == 0) {
+                mangleTypeChar('V');
+            } else {
+                for (int i = 0; i < params.length; i++) {
+                    mangleType(params[i].getType());
+                }
+            }
+        }
+
+        private void mangleType(JavaType type) {
+            if (type instanceof ResolvedJavaType) {
+                mangleType((ResolvedJavaType) type);
+            } else if (type instanceof UnresolvedJavaType) {
+                mangleType((UnresolvedJavaType) type);
+            } else {
+                throw VMError.shouldNotReachHere("Unexpected JavaType for " + type);
+            }
+        }
+
+        private void mangleType(ResolvedJavaType type) {
+            if (type.isPrimitive()) {
+                manglePrimitiveType(type);
+            } else if (type.isArray()) {
+                mangleArrayType(type);
+            } else {
+                String loaderName = nameProvider.classLoaderNameAndId(type);
+                String className = type.toJavaName();
+                if (nameProvider.needsPointerPrefix(type)) {
+                    mangleClassPointer(loaderName, className);
+                } else {
+                    mangleClassName(loaderName, className);
+                }
+            }
+        }
+
+        private void mangleType(UnresolvedJavaType type) {
+            if (type.isArray()) {
+                mangleArrayType(type);
+            } else {
+                mangleClassPointer("", type.toJavaName());
+            }
+        }
+
+        private void mangleType(Class<?> type) {
+            if (type.isPrimitive()) {
+                manglePrimitiveType(type);
+            } else if (type.isArray()) {
+                mangleArrayType(type);
+            } else {
+                mangleClassPointer(nameProvider.uniqueShortLoaderName(type.getClassLoader()), type.getName());
+            }
+        }
+
+        private void mangleArrayType(ResolvedJavaType arrayType) {
+            // It may seem odd that we consider placing array names in a namespace.
+            // However, note that we are faking arrays with classes that embed the
+            // actual array data and identifying the array class with a name derived by
+            // appending [] pairs to the base element type. If the base name may need a
+            // loader namespace prefix to disambiguate repeated loads of the same class
+            // then so may this fake array name.
+            int count = 1;
+            ResolvedJavaType baseType = arrayType.getComponentType();
+            while (baseType.isArray()) {
+                count++;
+                baseType = baseType.getComponentType();
+            }
+            String loaderName = nameProvider.classLoaderNameAndId(baseType);
+            mangleArrayPointer(loaderName, baseType.toJavaName(), count);
+        }
+
+        private void mangleArrayType(UnresolvedJavaType arrayType) {
+            int count = 1;
+            JavaType baseType = arrayType.getComponentType();
+            while (baseType.isArray()) {
+                count++;
+                baseType = baseType.getComponentType();
+            }
+            mangleArrayPointer("", baseType.toJavaName(), count);
+        }
+
+        private void mangleArrayType(Class<?> arrayType) {
+            // See above for why we consider placing array names in a namespace.
+            int count = 1;
+            Class<?> baseType = arrayType.getComponentType();
+            while (baseType.isArray()) {
+                baseType = baseType.getComponentType();
+            }
+            String loaderName = nameProvider.uniqueShortLoaderName(baseType.getClassLoader());
+            mangleArrayPointer(loaderName, baseType.getName(), count);
+        }
+
+        private void mangleArrayPointer(String loaderName, String baseName, int dims) {
+            // an array is just a class with a name that includes trailing [] pairs
+            mangleClassPointer(loaderName, makeArrayName(baseName, dims));
+        }
+
+        private static String makeArrayName(String baseName, int dims) {
+            StringBuilder sb1 = new StringBuilder();
+            sb1.append(baseName);
+            for (int i = 0; i < dims; i++) {
+                sb1.append("[]");
+            }
+            return sb1.toString();
+        }
+
+        private void manglePrimitiveType(ResolvedJavaType type) {
+            char c = type.getJavaKind().getTypeChar();
+            mangleTypeChar(c);
+        }
+
+        private void manglePrimitiveType(Class<?> type) {
+            char c = JavaKind.fromJavaClass(type).getTypeChar();
+            mangleTypeChar(c);
+        }
+
+        private void mangleTypeChar(char c) {
+            // we can use single char encodings for most primitive types
+            // but we need to encode boolean, byte and char specially
+            switch (c) {
+                case 'Z':
+                    mangleWriteSubstitutableNameRecord("boolean");
+                    return;
+                case 'B':
+                    mangleWriteSubstitutableNameRecord("byte");
+                    return;
+                case 'S':
+                    sb.append("s");
+                    return;
+                case 'C':
+                    mangleWriteSubstitutableNameRecord("char");
+                    return;
+                case 'I':
+                    sb.append("i");
+                    return;
+                case 'J':
+                    sb.append("l");
+                    return;
+                case 'F':
+                    sb.append("f");
+                    return;
+                case 'D':
+                    sb.append("d");
+                    return;
+                case 'V':
+                    sb.append("v");
+                    return;
+                default:
+                    // should never reach here
+                    assert false : "invalid kind for primitive type " + c;
+            }
+        }
+
+        private void writeSubstitution(int i) {
+            sb.append('S');
+            // i = 0 has no digits, i = 1 -> 0, ... i = 10 -> 9, i = 11 -> A, ... i = 36 -> Z, i =
+            // 37 -> 10, ...
+            // allow for at most up 2 base 36 digits
+            if (i > 36) {
+                sb.append(b36((i - 1) / 36));
+                sb.append(b36((i - 1) % 36));
+            } else if (i > 0) {
+                sb.append(b36(i - 1));
+            }
+            sb.append('_');
+        }
+
+        private static char b36(int i) {
+            if (i < 10) {
+                return (char) ('0' + i);
+            } else {
+                return (char) ('A' + (i - 10));
+            }
+        }
+
+        private void recordName(LookupName name) {
+            bindings.put(name, bindings.size());
+        }
+    }
+
+    /**
+     * Determine whether a type modeled as a Java object type needs to be encoded using pointer
+     * prefix P.
+     *
+     * @param type The type to be checked.
+     * @return true if the type needs to be encoded using pointer prefix P otherwise false.
+     */
+    private boolean needsPointerPrefix(ResolvedJavaType type) {
+        return true;
+    }
+
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
index 1af0574e51a7..723c01d3a435 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
@@ -3,31 +3,39 @@
 import com.oracle.objectfile.BasicNobitsSectionImpl;
 import com.oracle.objectfile.ObjectFile;
 import com.oracle.objectfile.SectionName;
+import com.oracle.objectfile.debuginfo.DebugInfoProvider;
 import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider;
 import com.oracle.svm.core.SubstrateOptions;
-import com.oracle.svm.core.c.CGlobalData;
-import com.oracle.svm.core.c.CGlobalDataFactory;
 import com.oracle.svm.core.c.NonmovableArray;
 import com.oracle.svm.core.c.NonmovableArrays;
 import com.oracle.svm.core.code.CompilationResultFrameTree;
 import com.oracle.svm.core.config.ConfigurationValues;
 import com.oracle.svm.core.graal.code.SubstrateBackend;
+import com.oracle.svm.core.graal.code.SubstrateCallingConvention;
+import com.oracle.svm.core.graal.code.SubstrateCallingConventionKind;
+import com.oracle.svm.core.graal.code.SubstrateCallingConventionType;
 import com.oracle.svm.core.graal.meta.RuntimeConfiguration;
+import com.oracle.svm.core.graal.meta.SubstrateRegisterConfig;
 import com.oracle.svm.core.heap.Heap;
 import com.oracle.svm.core.heap.ObjectHeader;
+import com.oracle.svm.core.meta.SharedMethod;
 import com.oracle.svm.core.nmt.NmtCategory;
 import com.oracle.svm.core.os.VirtualMemoryProvider;
 import jdk.graal.compiler.code.CompilationResult;
 import jdk.graal.compiler.core.common.CompressEncoding;
 import jdk.graal.compiler.core.common.NumUtil;
+import jdk.graal.compiler.core.target.Backend;
 import jdk.graal.compiler.debug.DebugContext;
 import jdk.graal.compiler.graph.NodeSourcePosition;
 import jdk.graal.compiler.java.StableMethodNameFormatter;
-import jdk.graal.compiler.word.Word;
+import jdk.graal.compiler.util.Digest;
 import jdk.vm.ci.code.BytecodeFrame;
 import jdk.vm.ci.code.BytecodePosition;
+import jdk.vm.ci.code.CallingConvention;
+import jdk.vm.ci.code.RegisterConfig;
 import jdk.vm.ci.code.RegisterValue;
 import jdk.vm.ci.code.StackSlot;
+import jdk.vm.ci.meta.AllocatableValue;
 import jdk.vm.ci.meta.JavaConstant;
 import jdk.vm.ci.meta.JavaKind;
 import jdk.vm.ci.meta.JavaValue;
@@ -57,9 +65,10 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.function.Consumer;
+import java.util.stream.Stream;
 
-import static com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugFrameSizeChange.Type.CONTRACT;
-import static com.oracle.objectfile.runtime.RuntimeDebugInfoProvider.DebugFrameSizeChange.Type.EXTEND;
+import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFrameSizeChange.Type.CONTRACT;
+import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFrameSizeChange.Type.EXTEND;
 
 
 public class SubstrateDebugInfoProvider implements RuntimeDebugInfoProvider {
@@ -82,9 +91,6 @@ public class SubstrateDebugInfoProvider implements RuntimeDebugInfoProvider {
     private final List<ObjectFile.Element> sortedObjectFileElements;
     private final int debugInfoSize;
 
-    public static final CGlobalData<Word> TEST_DATA = CGlobalDataFactory.forSymbol("__svm_heap_begin");
-    //public static final CGlobalData<Word> TEST_DATA2 = CGlobalDataFactory.forSymbol("__svm_test_symbol");
-
 
     public SubstrateDebugInfoProvider(DebugContext debugContext, ResolvedJavaMethod method, CompilationResult compilation, RuntimeConfiguration runtimeConfiguration, long codeAddress, int codeSize) {
         this.debugContext = debugContext;
@@ -112,9 +118,6 @@ public SubstrateDebugInfoProvider(DebugContext debugContext, ResolvedJavaMethod
         sortedObjectFileElements = new ArrayList<>();
         debugInfoSize = objectFile.bake(sortedObjectFileElements);
         dumpObjectFile();
-
-        //System.out.println("__svm_heap_begin: " + TEST_DATA.get().rawValue());
-        //System.out.println("__svm_test_symbol: " + TEST_DATA2.get().rawValue());
     }
 
     public NonmovableArray<Byte> writeDebugInfoData() {
@@ -185,13 +188,29 @@ public int compiledCodeMax() {
     }
 
     @Override
-    public Iterable<DebugTypeInfo> typeInfoProvider() {
-        return List.of();
+    public DebugInfoProvider.DebugCodeInfo codeInfoProvider() {
+        return new SubstrateDebugCodeInfo(method, compilation);
     }
 
     @Override
-    public DebugCodeInfo codeInfoProvider() {
-        return new SubstrateDebugCodeInfo(method, compilation);
+    public DebugInfoProvider.DebugTypeInfo createDebugTypeInfo(ResolvedJavaType javaType) {
+
+        try (DebugContext.Scope s = debugContext.scope("DebugTypeInfo", javaType.toJavaName())) {
+            if (javaType.isInstanceClass()) {
+                return new SubstrateDebugInstanceTypeInfo(javaType);
+            } else if (javaType.isInterface()) {
+                return new SubstrateDebugInterfaceTypeInfo(javaType);
+            } else if (javaType.isArray()) {
+                return new SubstrateDebugArrayTypeInfo(javaType);
+            } else if (javaType.isPrimitive()) {
+                return new SubstrateDebugPrimitiveTypeInfo(javaType);
+            } else {
+                System.out.println(javaType.getName());
+                return new SubstrateDebugForeignTypeInfo(javaType);
+            }
+        } catch (Throwable e) {
+            throw debugContext.handle(e);
+        }
     }
 
     @Override
@@ -204,7 +223,7 @@ public void recordActivity() {
 
     }
 
-    private class SubstrateDebugFileInfo implements DebugFileInfo {
+    private class SubstrateDebugFileInfo implements DebugInfoProvider.DebugFileInfo {
         private final Path fullFilePath;
         private final String fileName;
 
@@ -250,7 +269,7 @@ public Path filePath() {
 
 
     // actually unused currently
-    private abstract class SubstrateDebugTypeInfo extends SubstrateDebugFileInfo implements DebugTypeInfo {
+    private abstract class SubstrateDebugTypeInfo extends SubstrateDebugFileInfo implements DebugInfoProvider.DebugTypeInfo {
         protected final ResolvedJavaType type;
 
         SubstrateDebugTypeInfo(ResolvedJavaType type) {
@@ -258,6 +277,11 @@ private abstract class SubstrateDebugTypeInfo extends SubstrateDebugFileInfo imp
             this.type = type;
         }
 
+        @Override
+        public long typeSignature(String prefix) {
+            return Digest.digestAsUUID(prefix + typeName()).getLeastSignificantBits();
+        }
+
         @Override
         public ResolvedJavaType idType() {
             return type;
@@ -265,7 +289,11 @@ public ResolvedJavaType idType() {
 
         @Override
         public void debugContext(Consumer<DebugContext> action) {
-
+            try (DebugContext.Scope s = debugContext.scope("DebugTypeInfo", method)) {
+                action.accept(debugContext);
+            } catch (Throwable e) {
+                throw debugContext.handle(e);
+            }
         }
 
         @Override
@@ -281,28 +309,181 @@ public long classOffset() {
 
         @Override
         public int size() {
-            if (type.isPrimitive()) {
-                JavaKind javaKind = type.getJavaKind();
-                return (javaKind == JavaKind.Void ? 0 : javaKind.getByteCount());
-            } else {
-                // typedef
-                return pointerSize();
-            }
+            return 0;
         }
     }
 
-    private class SubstrateDebugTypedefInfo extends SubstrateDebugTypeInfo implements DebugTypedefInfo {
-        SubstrateDebugTypedefInfo(ResolvedJavaType type) {
-            super(type);
+    private class SubstrateDebugEnumTypeInfo extends SubstrateDebugInstanceTypeInfo implements DebugInfoProvider.DebugEnumTypeInfo {
+
+        SubstrateDebugEnumTypeInfo(ResolvedJavaType enumType) {
+            super(enumType);
+        }
+
+        @Override
+        public DebugTypeKind typeKind() {
+            return DebugTypeKind.ENUM;
+        }
+    }
+
+    private class SubstrateDebugInstanceTypeInfo extends SubstrateDebugTypeInfo implements DebugInfoProvider.DebugInstanceTypeInfo {
+        SubstrateDebugInstanceTypeInfo(ResolvedJavaType javaType) {
+            super(javaType);
+        }
+
+        @Override
+        public long typeSignature(String prefix) {
+            return super.typeSignature(prefix + loaderName());
+        }
+
+        @Override
+        public DebugTypeKind typeKind() {
+            return DebugTypeKind.INSTANCE;
+        }
+
+        @Override
+        public String loaderName() {
+            // return UniqueShortNameProvider.singleton().uniqueShortLoaderName(type.getClass().getClassLoader());
+            return ImageSingletons.lookup(SubstrateBFDNameProvider.class).uniqueShortLoaderName(type.getClass().getClassLoader());
+        }
+
+        @Override
+        public Stream<DebugInfoProvider.DebugFieldInfo> fieldInfoProvider() {
+            return Stream.empty();
+        }
+
+        @Override
+        public Stream<DebugInfoProvider.DebugMethodInfo> methodInfoProvider() {
+            return Stream.empty();
+        }
+
+        @Override
+        public ResolvedJavaType superClass() {
+            return type.getSuperclass();
+        }
+
+        @Override
+        public Stream<ResolvedJavaType> interfaces() {
+            // map through getOriginal so we can use the result as an id type
+            return Arrays.stream(type.getInterfaces());
+        }
+    }
+
+    private class SubstrateDebugInterfaceTypeInfo extends SubstrateDebugInstanceTypeInfo implements DebugInfoProvider.DebugInterfaceTypeInfo {
+
+        SubstrateDebugInterfaceTypeInfo(ResolvedJavaType interfaceType) {
+            super(interfaceType);
+        }
+
+        @Override
+        public DebugTypeKind typeKind() {
+            return DebugTypeKind.INTERFACE;
+        }
+    }
+
+    private class SubstrateDebugForeignTypeInfo extends SubstrateDebugInstanceTypeInfo implements DebugInfoProvider.DebugForeignTypeInfo {
+
+        SubstrateDebugForeignTypeInfo(ResolvedJavaType foreignType) {
+            super(foreignType);
+        }
+
+        @Override
+        public DebugTypeKind typeKind() {
+            return DebugTypeKind.FOREIGN;
+        }
+
+        @Override
+        public String typedefName() {
+            return "";
+        }
+
+        @Override
+        public boolean isWord() {
+            return false;
+        }
+
+        @Override
+        public boolean isStruct() {
+            return false;
+        }
+
+        @Override
+        public boolean isPointer() {
+            return false;
+        }
+
+        @Override
+        public boolean isIntegral() {
+            return false;
+        }
+
+        @Override
+        public boolean isFloat() {
+            return false;
+        }
+
+        @Override
+        public boolean isSigned() {
+            return false;
+        }
+
+        @Override
+        public ResolvedJavaType parent() {
+            return null;
+        }
+
+        @Override
+        public ResolvedJavaType pointerTo() {
+            return null;
+        }
+    }
+
+    private class SubstrateDebugArrayTypeInfo extends SubstrateDebugTypeInfo implements DebugInfoProvider.DebugArrayTypeInfo {
+
+        SubstrateDebugArrayTypeInfo(ResolvedJavaType arrayClass) {
+            super(arrayClass);
+        }
+
+        @Override
+        public long typeSignature(String prefix) {
+            ResolvedJavaType elementType = type.getComponentType();
+            while (elementType.isArray()) {
+                elementType = elementType.getComponentType();
+            }
+            String loaderId = "";
+            if (elementType.isInstanceClass() || elementType.isInterface()) {
+                // loaderId = UniqueShortNameProvider.singleton().uniqueShortLoaderName(elementType.getClass().getClassLoader());
+                loaderId = ImageSingletons.lookup(SubstrateBFDNameProvider.class).uniqueShortLoaderName(elementType.getClass().getClassLoader());
+            }
+            return super.typeSignature(prefix + loaderId);
         }
 
         @Override
         public DebugTypeKind typeKind() {
-            return DebugTypeKind.TYPEDEF;
+            return DebugTypeKind.ARRAY;
+        }
+
+        @Override
+        public int baseSize() {
+            return 0;
+        }
+
+        @Override
+        public int lengthOffset() {
+            return 0;
+        }
+
+        @Override
+        public ResolvedJavaType elementType() {
+            return type.getComponentType();
+        }
+
+        @Override
+        public Stream<DebugInfoProvider.DebugFieldInfo> fieldInfoProvider() {
+            return Stream.empty();
         }
     }
 
-    private class SubstrateDebugPrimitiveTypeInfo extends SubstrateDebugTypeInfo implements DebugPrimitiveTypeInfo {
+    private class SubstrateDebugPrimitiveTypeInfo extends SubstrateDebugTypeInfo implements DebugInfoProvider.DebugPrimitiveTypeInfo {
 
         SubstrateDebugPrimitiveTypeInfo(ResolvedJavaType type) {
             super(type);
@@ -340,11 +521,11 @@ public int flags() {
         }
     }
 
-    private class SubstrateDebugMethodInfo extends SubstrateDebugFileInfo implements DebugMethodInfo {
+    private class SubstrateDebugMethodInfo extends SubstrateDebugFileInfo implements DebugInfoProvider.DebugMethodInfo {
         protected final ResolvedJavaMethod method;
         protected int line;
-        protected final List<DebugLocalInfo> paramInfo;
-        protected final DebugLocalInfo thisParamInfo;
+        protected final List<DebugInfoProvider.DebugLocalInfo> paramInfo;
+        protected final DebugInfoProvider.DebugLocalInfo thisParamInfo;
 
         SubstrateDebugMethodInfo(ResolvedJavaMethod method) {
             super(method);
@@ -367,10 +548,10 @@ private class SubstrateDebugMethodInfo extends SubstrateDebugFileInfo implements
             }
         }
 
-        private List<DebugLocalInfo> createParamInfo(ResolvedJavaMethod method, int line) {
+        private List<DebugInfoProvider.DebugLocalInfo> createParamInfo(ResolvedJavaMethod method, int line) {
             Signature signature = method.getSignature();
             int parameterCount = signature.getParameterCount(false);
-            List<DebugLocalInfo> paramInfos = new ArrayList<>(parameterCount);
+            List<DebugInfoProvider.DebugLocalInfo> paramInfos = new ArrayList<>(parameterCount);
             LocalVariableTable table = method.getLocalVariableTable();
             int slot = 0;
             ResolvedJavaType ownerType = method.getDeclaringClass();
@@ -419,18 +600,19 @@ public int line() {
         }
 
         @Override
-        public List<DebugLocalInfo> getParamInfo() {
-            return paramInfo;
+        public DebugInfoProvider.DebugLocalInfo[] getParamInfo() {
+            return paramInfo.toArray(new DebugInfoProvider.DebugLocalInfo[]{});
         }
 
         @Override
-        public DebugLocalInfo getThisParamInfo() {
+        public DebugInfoProvider.DebugLocalInfo getThisParamInfo() {
             return thisParamInfo;
         }
 
         @Override
         public String symbolNameForMethod() {
-            return method.getName(); //SubstrateUtil.uniqueShortName(method);
+            // SubstrateOptions.ImageSymbolsPrefix.getValue() + getUniqueShortName(sm);
+            return ImageSingletons.lookup(SubstrateBFDNameProvider.class).uniqueShortName(null, method.getDeclaringClass(), method.getName(), method.getSignature(), method.isConstructor());
         }
 
         @Override
@@ -470,7 +652,7 @@ public ResolvedJavaMethod idMethod() {
         }
     }
 
-    private class SubstrateDebugCodeInfo extends SubstrateDebugMethodInfo implements DebugCodeInfo {
+    private class SubstrateDebugCodeInfo extends SubstrateDebugMethodInfo implements DebugInfoProvider.DebugCodeInfo {
         private final CompilationResult compilation;
 
         SubstrateDebugCodeInfo(ResolvedJavaMethod method, CompilationResult compilation) {
@@ -499,14 +681,14 @@ public long addressHi() {
         }
 
         @Override
-        public Iterable<DebugLocationInfo> locationInfoProvider() {
+        public Stream<DebugInfoProvider.DebugLocationInfo> locationInfoProvider() {
             int maxDepth = Integer.MAX_VALUE; //SubstrateOptions.DebugCodeInfoMaxDepth.getValue();
             boolean useSourceMappings = false; //SubstrateOptions.DebugCodeInfoUseSourceMappings.getValue();
             final CompilationResultFrameTree.CallNode root = new CompilationResultFrameTree.Builder(debugContext, compilation.getTargetCodeSize(), maxDepth, useSourceMappings, true).build(compilation);
             if (root == null) {
-                return List.of();
+                return Stream.empty();
             }
-            final List<DebugLocationInfo> locationInfos = new ArrayList<>();
+            final List<DebugInfoProvider.DebugLocationInfo> locationInfos = new ArrayList<>();
             int frameSize = getFrameSize();
             final CompilationResultFrameTree.Visitor visitor = new MultiLevelVisitor(locationInfos, frameSize);
             // arguments passed by visitor to apply are
@@ -514,7 +696,74 @@ public Iterable<DebugLocationInfo> locationInfoProvider() {
             // CallNode nodeToEmbed parent call node to convert to entry code leaf
             // NativeImageDebugLocationInfo leaf into which current leaf may be merged
             root.visitChildren(visitor, (Object) null, (Object) null, (Object) null);
-            return locationInfos;
+            // try to add a location record for offset zero
+            updateInitialLocation(locationInfos);
+            return locationInfos.stream();
+        }
+
+        private int findMarkOffset(SubstrateBackend.SubstrateMarkId markId) {
+            for (CompilationResult.CodeMark mark : compilation.getMarks()) {
+                if (mark.id.equals(markId)) {
+                    return mark.pcOffset;
+                }
+            }
+            return -1;
+        }
+
+        private void updateInitialLocation(List<DebugInfoProvider.DebugLocationInfo> locationInfos) {
+            int prologueEnd = findMarkOffset(SubstrateBackend.SubstrateMarkId.PROLOGUE_END);
+            if (prologueEnd < 0) {
+                // this is not a normal compiled method so give up
+                return;
+            }
+            int stackDecrement = findMarkOffset(SubstrateBackend.SubstrateMarkId.PROLOGUE_DECD_RSP);
+            if (stackDecrement < 0) {
+                // this is not a normal compiled method so give up
+                return;
+            }
+            // if there are any location info records then the first one will be for
+            // a nop which follows the stack decrement, stack range check and pushes
+            // of arguments into the stack frame.
+            //
+            // We can construct synthetic location info covering the first instruction
+            // based on the method arguments and the calling convention and that will
+            // normally be valid right up to the nop. In exceptional cases a call
+            // might pass arguments on the stack, in which case the stack decrement will
+            // invalidate the original stack locations. Providing location info for that
+            // case requires adding two locations, one for initial instruction that does
+            // the stack decrement and another for the range up to the nop. They will
+            // be essentially the same but the stack locations will be adjusted to account
+            // for the different value of the stack pointer.
+
+            if (locationInfos.isEmpty()) {
+                // this is not a normal compiled method so give up
+                return;
+            }
+            SubstrateDebugLocationInfo firstLocation = (SubstrateDebugLocationInfo) locationInfos.get(0);
+            long firstLocationOffset = firstLocation.addressLo();
+
+            if (firstLocationOffset == 0) {
+                // this is not a normal compiled method so give up
+                return;
+            }
+            if (firstLocationOffset < prologueEnd) {
+                // this is not a normal compiled method so give up
+                return;
+            }
+            // create a synthetic location record including details of passed arguments
+            ParamLocationProducer locProducer = new ParamLocationProducer(method);
+            debugContext.log(DebugContext.DETAILED_LEVEL, "Add synthetic Location Info : %s (0, %d)", method.getName(), firstLocationOffset - 1);
+            SubstrateDebugLocationInfo locationInfo = new SubstrateDebugLocationInfo(method, firstLocationOffset, locProducer);
+            // if the prologue extends beyond the stack extend and uses the stack then the info
+            // needs
+            // splitting at the extend point with the stack offsets adjusted in the new info
+            if (locProducer.usesStack() && firstLocationOffset > stackDecrement) {
+                SubstrateDebugLocationInfo splitLocationInfo = locationInfo.split(stackDecrement, getFrameSize());
+                debugContext.log(DebugContext.DETAILED_LEVEL, "Split synthetic Location Info : %s (%d, %d) (%d, %d)", locationInfo.name(), 0,
+                        locationInfo.addressLo() - 1, locationInfo.addressLo(), locationInfo.addressHi() - 1);
+                locationInfos.add(0, splitLocationInfo);
+            }
+            locationInfos.add(0, locationInfo);
         }
 
         // indices for arguments passed to SingleLevelVisitor::apply
@@ -524,10 +773,10 @@ public Iterable<DebugLocationInfo> locationInfoProvider() {
 
         private abstract class SingleLevelVisitor implements CompilationResultFrameTree.Visitor {
 
-            protected final List<DebugLocationInfo> locationInfos;
+            protected final List<DebugInfoProvider.DebugLocationInfo> locationInfos;
             protected final int frameSize;
 
-            SingleLevelVisitor(List<DebugLocationInfo> locationInfos, int frameSize) {
+            SingleLevelVisitor(List<DebugInfoProvider.DebugLocationInfo> locationInfos, int frameSize) {
                 this.locationInfos = locationInfos;
                 this.frameSize = frameSize;
             }
@@ -549,7 +798,7 @@ public SubstrateDebugLocationInfo process(CompilationResultFrameTree.FrameNode n
         }
 
         private class TopLevelVisitor extends SingleLevelVisitor {
-            TopLevelVisitor(List<DebugLocationInfo> locationInfos, int frameSize) {
+            TopLevelVisitor(List<DebugInfoProvider.DebugLocationInfo> locationInfos, int frameSize) {
                 super(locationInfos, frameSize);
             }
 
@@ -575,7 +824,7 @@ public void apply(CompilationResultFrameTree.FrameNode node, Object... args) {
         }
 
         public class MultiLevelVisitor extends SingleLevelVisitor {
-            MultiLevelVisitor(List<DebugLocationInfo> locationInfos, int frameSize) {
+            MultiLevelVisitor(List<DebugInfoProvider.DebugLocationInfo> locationInfos, int frameSize) {
                 super(locationInfos, frameSize);
             }
 
@@ -839,8 +1088,8 @@ public int getFrameSize() {
         }
 
         @Override
-        public List<DebugFrameSizeChange> getFrameSizeChanges() {
-            List<DebugFrameSizeChange> frameSizeChanges = new LinkedList<>();
+        public List<DebugInfoProvider.DebugFrameSizeChange> getFrameSizeChanges() {
+            List<DebugInfoProvider.DebugFrameSizeChange> frameSizeChanges = new LinkedList<>();
             for (CompilationResult.CodeMark mark : compilation.getMarks()) {
                 /* We only need to observe stack increment or decrement points. */
                 if (mark.id.equals(SubstrateBackend.SubstrateMarkId.PROLOGUE_DECD_RSP)) {
@@ -863,12 +1112,12 @@ public List<DebugFrameSizeChange> getFrameSizeChanges() {
         }
     }
 
-    private class SubstrateDebugLocationInfo extends SubstrateDebugMethodInfo implements DebugLocationInfo {
+    private class SubstrateDebugLocationInfo extends SubstrateDebugMethodInfo implements DebugInfoProvider.DebugLocationInfo {
         private final int bci;
         private long lo;
         private long hi;
-        private DebugLocationInfo callersLocationInfo;
-        private List<DebugLocalValueInfo> localInfoList;
+        private DebugInfoProvider.DebugLocationInfo callersLocationInfo;
+        private List<DebugInfoProvider.DebugLocalValueInfo> localInfoList;
         private boolean isLeaf;
 
         SubstrateDebugLocationInfo(CompilationResultFrameTree.FrameNode frameNode, SubstrateDebugLocationInfo callersLocationInfo, int framesize) {
@@ -890,7 +1139,57 @@ private class SubstrateDebugLocationInfo extends SubstrateDebugMethodInfo implem
             }
         }
 
-        private List<DebugLocalValueInfo> initLocalInfoList(BytecodePosition bcpos, int framesize) {
+        // special constructor for synthetic location info added at start of method
+        SubstrateDebugLocationInfo(ResolvedJavaMethod method, long hi, ParamLocationProducer locProducer) {
+            super(method);
+            // bci is always 0 and lo is always 0.
+            this.bci = 0;
+            this.lo = 0;
+            this.hi = hi;
+            // this is always going to be a top-level leaf range.
+            this.callersLocationInfo = null;
+            // location info is synthesized off the method signature
+            this.localInfoList = initSyntheticInfoList(locProducer);
+            // assume this is a leaf until we find out otherwise
+            this.isLeaf = true;
+        }
+
+        // special constructor for synthetic location info which splits off the initial segment
+        // of the first range to accommodate a stack access prior to the stack extend
+        SubstrateDebugLocationInfo(SubstrateDebugLocationInfo toSplit, int stackDecrement, int frameSize) {
+            super(toSplit.method);
+            this.lo = stackDecrement;
+            this.hi = toSplit.hi;
+            toSplit.hi = this.lo;
+            this.bci = toSplit.bci;
+            this.callersLocationInfo = toSplit.callersLocationInfo;
+            this.isLeaf = toSplit.isLeaf;
+            toSplit.isLeaf = true;
+            this.localInfoList = new ArrayList<>(toSplit.localInfoList.size());
+            for (DebugInfoProvider.DebugLocalValueInfo localInfo : toSplit.localInfoList) {
+                if (localInfo.localKind() == DebugInfoProvider.DebugLocalValueInfo.LocalKind.STACKSLOT) {
+                    // need to redefine the value for this param using a stack slot value
+                    // that allows for the stack being extended by framesize. however we
+                    // also need to remove any adjustment that was made to allow for the
+                    // difference between the caller SP and the pre-extend callee SP
+                    // because of a stacked return address.
+                    int adjustment = frameSize - PRE_EXTEND_FRAME_SIZE;
+                    SubstrateDebugLocalValue value = SubstrateDebugStackValue.create(localInfo, adjustment);
+                    SubstrateDebugLocalValueInfo nativeLocalInfo = (SubstrateDebugLocalValueInfo) localInfo;
+                    SubstrateDebugLocalValueInfo newLocalinfo = new SubstrateDebugLocalValueInfo(nativeLocalInfo.name,
+                            value,
+                            nativeLocalInfo.kind,
+                            nativeLocalInfo.type,
+                            nativeLocalInfo.slot,
+                            nativeLocalInfo.line);
+                    localInfoList.add(newLocalinfo);
+                } else {
+                    localInfoList.add(localInfo);
+                }
+            }
+        }
+
+        private List<DebugInfoProvider.DebugLocalValueInfo> initLocalInfoList(BytecodePosition bcpos, int framesize) {
             if (!(bcpos instanceof BytecodeFrame)) {
                 return null;
             }
@@ -908,7 +1207,7 @@ private List<DebugLocalValueInfo> initLocalInfoList(BytecodePosition bcpos, int
                 return Collections.emptyList();
             }
             int count = Integer.min(localsBySlot.length, frame.numLocals);
-            ArrayList<DebugLocalValueInfo> localInfos = new ArrayList<>(count);
+            ArrayList<DebugInfoProvider.DebugLocalValueInfo> localInfos = new ArrayList<>(count);
             for (int i = 0; i < count; i++) {
                 Local l = localsBySlot[i];
                 if (l != null) {
@@ -937,6 +1236,42 @@ private List<DebugLocalValueInfo> initLocalInfoList(BytecodePosition bcpos, int
             return localInfos;
         }
 
+        private List<DebugInfoProvider.DebugLocalValueInfo> initSyntheticInfoList(ParamLocationProducer locProducer) {
+            Signature signature = method.getSignature();
+            int parameterCount = signature.getParameterCount(false);
+            ArrayList<DebugInfoProvider.DebugLocalValueInfo> localInfos = new ArrayList<>();
+            LocalVariableTable table = method.getLocalVariableTable();
+            LineNumberTable lineNumberTable = method.getLineNumberTable();
+            int firstLine = (lineNumberTable != null ? lineNumberTable.getLineNumber(0) : -1);
+            int slot = 0;
+            int localIdx = 0;
+            ResolvedJavaType ownerType = method.getDeclaringClass();
+            if (!method.isStatic()) {
+                String name = "this";
+                JavaKind kind = ownerType.getJavaKind();
+                assert kind == JavaKind.Object : "must be an object";
+                SubstrateDebugLocalValue value = locProducer.thisLocation();
+                debugContext.log(DebugContext.DETAILED_LEVEL, "locals[%d] %s type %s slot %d", localIdx, name, ownerType.getName(), slot);
+                debugContext.log(DebugContext.DETAILED_LEVEL, "  =>  %s kind %s", value, kind);
+                localInfos.add(new SubstrateDebugLocalValueInfo(name, value, kind, ownerType, slot, firstLine));
+                slot += kind.getSlotCount();
+                localIdx++;
+            }
+            for (int i = 0; i < parameterCount; i++) {
+                Local local = (table == null ? null : table.getLocal(slot, 0));
+                String name = (local != null ? local.getName() : "__" + i);
+                ResolvedJavaType paramType = (ResolvedJavaType) signature.getParameterType(i, ownerType);
+                JavaKind kind = paramType.getJavaKind();
+                SubstrateDebugLocalValue value = locProducer.paramLocation(i);
+                debugContext.log(DebugContext.DETAILED_LEVEL, "locals[%d] %s type %s slot %d", localIdx, name, ownerType.getName(), slot);
+                debugContext.log(DebugContext.DETAILED_LEVEL, "  =>  %s kind %s", value, kind);
+                localInfos.add(new SubstrateDebugLocalValueInfo(name, value, kind, paramType, slot, firstLine));
+                slot += kind.getSlotCount();
+                localIdx++;
+            }
+            return localInfos;
+        }
+
         private static boolean isIntegralKindPromotion(JavaKind promoted, JavaKind original) {
             return (promoted == JavaKind.Int &&
                     (original == JavaKind.Boolean || original == JavaKind.Byte || original == JavaKind.Short || original == JavaKind.Char));
@@ -975,16 +1310,16 @@ public int line() {
         }
 
         @Override
-        public DebugLocationInfo getCaller() {
+        public DebugInfoProvider.DebugLocationInfo getCaller() {
             return callersLocationInfo;
         }
 
         @Override
-        public List<DebugLocalValueInfo> getLocalValueInfo() {
+        public DebugInfoProvider.DebugLocalValueInfo[] getLocalValueInfo() {
             if (localInfoList != null) {
-                return localInfoList;
+                return localInfoList.toArray(new DebugInfoProvider.DebugLocalValueInfo[]{});
             } else {
-                return Collections.emptyList();
+                return new DebugInfoProvider.DebugLocalValueInfo[]{};
             }
         }
 
@@ -995,7 +1330,7 @@ public boolean isLeaf() {
 
         public int depth() {
             int depth = 1;
-            DebugLocationInfo caller = getCaller();
+            DebugInfoProvider.DebugLocationInfo caller = getCaller();
             while (caller != null) {
                 depth++;
                 caller = caller.getCaller();
@@ -1050,6 +1385,12 @@ SubstrateDebugLocationInfo merge(SubstrateDebugLocationInfo that) {
 
             return this;
         }
+
+        public SubstrateDebugLocationInfo split(int stackDecrement, int frameSize) {
+            // this should be for an initial range extending beyond the stack decrement
+            assert lo == 0 && lo < stackDecrement && stackDecrement < hi : "invalid split request";
+            return new SubstrateDebugLocationInfo(this, stackDecrement, frameSize);
+        }
     }
 
     /**
@@ -1058,7 +1399,80 @@ SubstrateDebugLocationInfo merge(SubstrateDebugLocationInfo that) {
      */
     static final int PRE_EXTEND_FRAME_SIZE = ConfigurationValues.getTarget().arch.getReturnAddressSize();
 
-    private class SubstrateDebugLocalInfo implements DebugLocalInfo {
+    class ParamLocationProducer {
+        private final ResolvedJavaMethod method;
+        private final CallingConvention callingConvention;
+        private boolean usesStack;
+
+        SubstrateDebugLocalValue thisLocation() {
+
+            assert !method.isStatic();
+            return unpack(callingConvention.getArgument(0));
+        }
+
+        ParamLocationProducer(ResolvedJavaMethod method) {
+            assert method instanceof SharedMethod;
+            this.method = method;
+            this.callingConvention = getCallingConvention((SharedMethod) method);
+            // assume no stack slots until we find out otherwise
+            this.usesStack = false;
+        }
+
+        SubstrateDebugLocalValue paramLocation(int paramIdx) {
+            assert paramIdx < method.getSignature().getParameterCount(false);
+            int idx = paramIdx;
+            if (!method.isStatic()) {
+                idx++;
+            }
+            return unpack(callingConvention.getArgument(idx));
+        }
+
+        private SubstrateDebugLocalValue unpack(AllocatableValue value) {
+            if (value instanceof RegisterValue) {
+                RegisterValue registerValue = (RegisterValue) value;
+                return SubstrateDebugRegisterValue.create(registerValue);
+            } else {
+                // call argument must be a stack slot if it is not a register
+                StackSlot stackSlot = (StackSlot) value;
+                this.usesStack = true;
+                // the calling convention provides offsets from the SP relative to the current
+                // frame size. At the point of call the frame may or may not include a return
+                // address depending on the architecture.
+                return SubstrateDebugStackValue.create(stackSlot, PRE_EXTEND_FRAME_SIZE);
+            }
+        }
+
+
+        /**
+         * Retrieve details of the native calling convention for a top level compiled method, including
+         * details of which registers or stack slots are used to pass parameters.
+         *
+         * @param method The method whose calling convention is required.
+         * @return The calling convention for the method.
+         */
+        private SubstrateCallingConvention getCallingConvention(SharedMethod method) {
+            SubstrateCallingConventionKind callingConventionKind = method.getCallingConventionKind();
+            ResolvedJavaType declaringClass = method.getDeclaringClass();
+            ResolvedJavaType receiverType = method.isStatic() ? null : declaringClass;
+            var signature = method.getSignature();
+            final SubstrateCallingConventionType type;
+            if (callingConventionKind.isCustom()) {
+                type = method.getCustomCallingConventionType();
+            } else {
+                type = callingConventionKind.toType(false);
+            }
+            Backend backend = runtimeConfiguration.lookupBackend(method);
+            RegisterConfig registerConfig = backend.getCodeCache().getRegisterConfig();
+            assert registerConfig instanceof SubstrateRegisterConfig;
+            return (SubstrateCallingConvention) registerConfig.getCallingConvention(type, signature.getReturnType(null), signature.toParameterTypes(receiverType), backend);
+        }
+
+        public boolean usesStack() {
+            return usesStack;
+        }
+    }
+
+    private class SubstrateDebugLocalInfo implements DebugInfoProvider.DebugLocalInfo {
         protected final String name;
         protected ResolvedJavaType type;
         protected final JavaKind kind;
@@ -1148,23 +1562,23 @@ public String toString() {
         }
     }
 
-    private class SubstrateDebugLocalValueInfo extends SubstrateDebugLocalInfo implements DebugLocalValueInfo {
+    private class SubstrateDebugLocalValueInfo extends SubstrateDebugLocalInfo implements DebugInfoProvider.DebugLocalValueInfo {
         private final SubstrateDebugLocalValue value;
-        private DebugLocalValueInfo.LocalKind localKind;
+        private DebugInfoProvider.DebugLocalValueInfo.LocalKind localKind;
 
         SubstrateDebugLocalValueInfo(String name, JavaValue value, int framesize, JavaKind kind, ResolvedJavaType resolvedType, int slot, int line) {
             super(name, kind, resolvedType, slot, line);
             if (value instanceof RegisterValue) {
-                this.localKind = DebugLocalValueInfo.LocalKind.REGISTER;
+                this.localKind = DebugInfoProvider.DebugLocalValueInfo.LocalKind.REGISTER;
                 this.value = SubstrateDebugRegisterValue.create((RegisterValue) value);
             } else if (value instanceof StackSlot) {
-                this.localKind = DebugLocalValueInfo.LocalKind.STACKSLOT;
+                this.localKind = DebugInfoProvider.DebugLocalValueInfo.LocalKind.STACKSLOT;
                 this.value = SubstrateDebugStackValue.create((StackSlot) value, framesize);
             } else if (value instanceof JavaConstant constant && (constant instanceof PrimitiveConstant || constant.isNull())) {
-                this.localKind = DebugLocalValueInfo.LocalKind.CONSTANT;
+                this.localKind = DebugInfoProvider.DebugLocalValueInfo.LocalKind.CONSTANT;
                 this.value = SubstrateDebugConstantValue.create(constant);
             } else {
-                this.localKind = DebugLocalValueInfo.LocalKind.UNDEFINED;
+                this.localKind = DebugInfoProvider.DebugLocalValueInfo.LocalKind.UNDEFINED;
                 this.value = null;
             }
         }
@@ -1172,13 +1586,13 @@ private class SubstrateDebugLocalValueInfo extends SubstrateDebugLocalInfo imple
         SubstrateDebugLocalValueInfo(String name, SubstrateDebugLocalValue value, JavaKind kind, ResolvedJavaType type, int slot, int line) {
             super(name, kind, type, slot, line);
             if (value == null) {
-                this.localKind = DebugLocalValueInfo.LocalKind.UNDEFINED;
+                this.localKind = DebugInfoProvider.DebugLocalValueInfo.LocalKind.UNDEFINED;
             } else if (value instanceof SubstrateDebugRegisterValue) {
-                this.localKind = DebugLocalValueInfo.LocalKind.REGISTER;
+                this.localKind = DebugInfoProvider.DebugLocalValueInfo.LocalKind.REGISTER;
             } else if (value instanceof SubstrateDebugStackValue) {
-                this.localKind = DebugLocalValueInfo.LocalKind.STACKSLOT;
+                this.localKind = DebugInfoProvider.DebugLocalValueInfo.LocalKind.STACKSLOT;
             } else if (value instanceof SubstrateDebugConstantValue) {
-                this.localKind = DebugLocalValueInfo.LocalKind.CONSTANT;
+                this.localKind = DebugInfoProvider.DebugLocalValueInfo.LocalKind.CONSTANT;
             }
             this.value = value;
         }
@@ -1232,7 +1646,7 @@ public String toString() {
         }
 
         @Override
-        public DebugLocalValueInfo.LocalKind localKind() {
+        public DebugInfoProvider.DebugLocalValueInfo.LocalKind localKind() {
             return localKind;
         }
 
@@ -1328,8 +1742,8 @@ static SubstrateDebugStackValue create(StackSlot value, int framesize) {
             return memoizedCreate(offset);
         }
 
-        static SubstrateDebugStackValue create(DebugLocalValueInfo previous, int adjustment) {
-            assert previous.localKind() == DebugLocalValueInfo.LocalKind.STACKSLOT;
+        static SubstrateDebugStackValue create(DebugInfoProvider.DebugLocalValueInfo previous, int adjustment) {
+            assert previous.localKind() == DebugInfoProvider.DebugLocalValueInfo.LocalKind.STACKSLOT;
             return memoizedCreate(previous.stackSlot() + adjustment);
         }
 
@@ -1422,7 +1836,7 @@ public int hashCode() {
      * Implementation of the DebugFrameSizeChange API interface that allows stack frame size change
      * info to be passed to an ObjectFile when generation of debug info is enabled.
      */
-    private class SubstrateDebugFrameSizeChange implements DebugFrameSizeChange {
+    private class SubstrateDebugFrameSizeChange implements DebugInfoProvider.DebugFrameSizeChange {
         private int offset;
         private Type type;
 
diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateMethod.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateMethod.java
index 995d2fb571ac..036a9263dd4c 100644
--- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateMethod.java
+++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/meta/SubstrateMethod.java
@@ -87,7 +87,6 @@ public class SubstrateMethod implements SharedRuntimeMethod {
     private final String name;
     private final int hashCode;
     private SubstrateType declaringClass;
-    private int encodedGraphStartOffset;
     private LocalVariableTable localVariableTable;
     @UnknownPrimitiveField(availability = ReadyForCompilation.class) private int encodedGraphStartOffset;
     @UnknownPrimitiveField(availability = AfterHeapLayout.class) private int vTableIndex;
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoFeature.java
index 818cb347bde3..41503d50756f 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoFeature.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoFeature.java
@@ -32,6 +32,7 @@
 
 import com.oracle.svm.core.ReservedRegisters;
 import jdk.graal.compiler.word.Word;
+import com.oracle.svm.core.debug.SubstrateBFDNameProvider;
 import org.graalvm.nativeimage.ImageSingletons;
 import org.graalvm.nativeimage.Platform;
 import org.graalvm.word.PointerBase;
@@ -97,7 +98,9 @@ public void afterRegistration(AfterRegistrationAccess access) {
                 // ensure the mangle ignores prefix generation for Graal loaders
                 List<ClassLoader> ignored = List.of(systemLoader, imageLoaderParent, appLoader, imageLoader);
                 bfdNameProvider = new NativeImageBFDNameProvider(ignored);
+                SubstrateBFDNameProvider substrateBFDNameProvider = new SubstrateBFDNameProvider(ignored);
                 ImageSingletons.add(UniqueShortNameProvider.class, bfdNameProvider);
+                ImageSingletons.add(SubstrateBFDNameProvider.class, substrateBFDNameProvider);
             }
         }
     }
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 d2e22544f084..33f848f2bee3 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
@@ -1386,12 +1386,12 @@ public void debugContext(Consumer<DebugContext> action) {
         }
 
         @Override
-        public int addressLo() {
+        public long addressLo() {
             return hostedMethod.getCodeAddressOffset();
         }
 
         @Override
-        public int addressHi() {
+        public long addressHi() {
             return hostedMethod.getCodeAddressOffset() + compilation.getTargetCodeSize();
         }
 
@@ -1470,7 +1470,7 @@ private void updateInitialLocation(List<DebugLocationInfo> locationInfos) {
                 return;
             }
             NativeImageDebugLocationInfo firstLocation = (NativeImageDebugLocationInfo) locationInfos.get(0);
-            int firstLocationOffset = firstLocation.addressLo();
+            long firstLocationOffset = firstLocation.addressLo();
 
             if (firstLocationOffset == 0) {
                 // this is not a normal compiled method so give up
@@ -1859,8 +1859,8 @@ public List<DebugFrameSizeChange> getFrameSizeChanges() {
      */
     private class NativeImageDebugLocationInfo extends NativeImageDebugBaseMethodInfo implements DebugLocationInfo {
         private final int bci;
-        private int lo;
-        private int hi;
+        private long lo;
+        private long hi;
         private DebugLocationInfo callersLocationInfo;
         private List<DebugLocalValueInfo> localInfoList;
         private boolean isLeaf;
@@ -1869,7 +1869,7 @@ private class NativeImageDebugLocationInfo extends NativeImageDebugBaseMethodInf
             this(frameNode.frame, frameNode.getStartPos(), frameNode.getEndPos() + 1, callersLocationInfo, framesize);
         }
 
-        NativeImageDebugLocationInfo(BytecodePosition bcpos, int lo, int hi, NativeImageDebugLocationInfo callersLocationInfo, int framesize) {
+        NativeImageDebugLocationInfo(BytecodePosition bcpos, long lo, long hi, NativeImageDebugLocationInfo callersLocationInfo, int framesize) {
             super(bcpos.getMethod());
             this.bci = bcpos.getBCI();
             this.lo = lo;
@@ -1885,7 +1885,7 @@ private class NativeImageDebugLocationInfo extends NativeImageDebugBaseMethodInf
         }
 
         // special constructor for synthetic location info added at start of method
-        NativeImageDebugLocationInfo(ResolvedJavaMethod method, int hi, ParamLocationProducer locProducer) {
+        NativeImageDebugLocationInfo(ResolvedJavaMethod method, long hi, ParamLocationProducer locProducer) {
             super(method);
             // bci is always 0 and lo is always 0.
             this.bci = 0;
@@ -2033,12 +2033,12 @@ private Local[] getLocalsBySlot() {
         }
 
         @Override
-        public int addressLo() {
+        public long addressLo() {
             return lo;
         }
 
         @Override
-        public int addressHi() {
+        public long addressHi() {
             return hi;
         }
 

From 4edf3b4335b79a8deb4cd59febb26fd8d915d737 Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Mon, 28 Oct 2024 10:15:17 +0100
Subject: [PATCH 06/34] Implement the GDB JIT interface in System Java

---
 .../core/debug/GDBJITInterfaceSystemJava.java | 158 ++++++++++++++++++
 .../debug/SubstrateDebugInfoInstaller.java    |  18 +-
 .../image/NativeImageDebugInfoFeature.java    |  16 ++
 .../include/gdbJITCompilationInterface.h      |   9 +-
 .../src/gdbJITCompilationInterface.c          |  43 +++--
 5 files changed, 209 insertions(+), 35 deletions(-)
 create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterfaceSystemJava.java

diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterfaceSystemJava.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterfaceSystemJava.java
new file mode 100644
index 000000000000..c2067e4cc43a
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterfaceSystemJava.java
@@ -0,0 +1,158 @@
+package com.oracle.svm.core.debug;
+
+import com.oracle.svm.core.SubstrateOptions;
+import com.oracle.svm.core.Uninterruptible;
+import com.oracle.svm.core.c.CGlobalData;
+import com.oracle.svm.core.c.CGlobalDataFactory;
+import com.oracle.svm.core.c.ProjectHeaderFile;
+import org.graalvm.nativeimage.ImageSingletons;
+import org.graalvm.nativeimage.UnmanagedMemory;
+import org.graalvm.nativeimage.c.CContext;
+import org.graalvm.nativeimage.c.constant.CEnum;
+import org.graalvm.nativeimage.c.constant.CEnumValue;
+import org.graalvm.nativeimage.c.function.CFunction;
+import org.graalvm.nativeimage.c.struct.CField;
+import org.graalvm.nativeimage.c.struct.CStruct;
+import org.graalvm.nativeimage.c.struct.SizeOf;
+import org.graalvm.nativeimage.c.type.CCharPointer;
+import org.graalvm.nativeimage.c.type.CUnsigned;
+import org.graalvm.nativeimage.impl.UnmanagedMemorySupport;
+import org.graalvm.word.PointerBase;
+import org.graalvm.word.WordFactory;
+
+import java.util.Collections;
+import java.util.List;
+
+
+@CContext(GDBJITInterfaceSystemJava.GDBJITInterfaceDirectives.class)
+public class GDBJITInterfaceSystemJava {
+
+    public static class GDBJITInterfaceDirectives implements CContext.Directives {
+        @Override
+        public boolean isInConfiguration() {
+            return SubstrateOptions.RuntimeDebugInfo.getValue();
+        }
+
+        @Override
+        public List<String> getHeaderFiles() {
+            return Collections.singletonList(ProjectHeaderFile.resolve("com.oracle.svm.native.debug", "include/gdbJITCompilationInterface.h"));
+        }
+    }
+
+    @CEnum(value = "jit_actions_t")
+    public enum JITActions {
+        JIT_NOACTION,
+        JIT_REGISTER,
+        JIT_UNREGISTER;
+
+        @CEnumValue
+        @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+        public native int getCValue();
+    }
+
+
+    @CStruct(value = "jit_code_entry", addStructKeyword = true)
+    public interface JITCodeEntry extends PointerBase {
+        // struct jit_code_entry *next_entry;
+        @CField("next_entry")
+        JITCodeEntry getNextEntry();
+        @CField("next_entry")
+        void setNextEntry(JITCodeEntry jitCodeEntry);
+
+        // struct jit_code_entry *prev_entry;
+        @CField("prev_entry")
+        JITCodeEntry getPrevEntry();
+        @CField("prev_entry")
+        void setPrevEntry(JITCodeEntry jitCodeEntry);
+
+        // const char *symfile_addr;
+        @CField("symfile_addr")
+        CCharPointer getSymfileAddr();
+        @CField("symfile_addr")
+        void setSymfileAddr(CCharPointer symfileAddr);
+
+        // uint64_t symfile_size;
+        @CField("symfile_size")
+        @CUnsigned long getSymfileSize();
+        @CField("symfile_size")
+        void setSymfileSize(@CUnsigned long symfileSize);
+    }
+
+    @CStruct(value = "jit_descriptor", addStructKeyword = true)
+    public interface JITDescriptor extends PointerBase {
+        // uint32_t version;
+        @CField("version")
+        @CUnsigned int getVersion();
+        @CField("version")
+        void setVersion(@CUnsigned int version);
+
+        // uint32_t action_flag;
+        @CField("action_flag")
+        @CUnsigned int getActionFlag();
+        @CField("action_flag")
+        void setActionFlag(@CUnsigned int actionFlag);
+
+        // struct jit_code_entry *relevant_entry;
+        @CField("relevant_entry")
+        JITCodeEntry getRelevantEntry();
+        @CField("relevant_entry")
+        void setRelevantEntry(JITCodeEntry jitCodeEntry);
+
+        // struct jit_code_entry *first_entry;
+        @CField("first_entry")
+        JITCodeEntry getFirstEntry();
+        @CField("first_entry")
+        void setFirstEntry(JITCodeEntry jitCodeEntry);
+    }
+
+    @CFunction(value = "__jit_debug_register_code", transition = CFunction.Transition.NO_TRANSITION)
+    private static native void jitDebugRegisterCode();
+
+    private static final CGlobalData<JITDescriptor> jitDebugDescriptor = CGlobalDataFactory.forSymbol("__jit_debug_descriptor");
+
+    public static void registerJITCode(CCharPointer addr, @CUnsigned long size, JITCodeEntry entry) {
+        /* Create new jit_code_entry */
+        entry.setSymfileAddr(addr);
+        entry.setSymfileSize(size);
+
+        /* Insert entry at head of the list. */
+        JITCodeEntry nextEntry = jitDebugDescriptor.get().getFirstEntry();
+        entry.setPrevEntry(WordFactory.nullPointer());
+        entry.setNextEntry(nextEntry);
+
+        if (nextEntry.isNonNull()) {
+            nextEntry.setPrevEntry(entry);
+        }
+
+        /* Notify GDB.  */
+        jitDebugDescriptor.get().setActionFlag(JITActions.JIT_REGISTER.getCValue());
+        jitDebugDescriptor.get().setFirstEntry(entry);
+        jitDebugDescriptor.get().setRelevantEntry(entry);
+        jitDebugRegisterCode();
+    }
+
+    @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+    public static void unregisterJITCode(JITCodeEntry entry) {
+        JITCodeEntry prevEntry = entry.getPrevEntry();
+        JITCodeEntry nextEntry = entry.getNextEntry();
+
+        /* Fix prev and next in list */
+        if (nextEntry.isNonNull()) {
+            nextEntry.setPrevEntry(prevEntry);
+        }
+
+        if (prevEntry.isNonNull()) {
+            prevEntry.setNextEntry(nextEntry);
+        } else {
+            assert(jitDebugDescriptor.get().getFirstEntry().equal(entry));
+            jitDebugDescriptor.get().setFirstEntry(nextEntry);
+        }
+
+        /* Notify GDB.  */
+        jitDebugDescriptor.get().setActionFlag(2); // JITActions.JIT_UNREGISTER.getCValue());
+        jitDebugDescriptor.get().setRelevantEntry(entry);
+        jitDebugRegisterCode();
+
+        ImageSingletons.lookup(UnmanagedMemorySupport.class).free(entry);
+    }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
index 228b5549ab8d..63744a90bb41 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
@@ -56,10 +56,10 @@ private interface Handle extends InstalledCodeObserverHandle {
         int RELEASED = 2;
 
         @RawField
-        GDBJITInterface.JITCodeEntry getRawHandle();
+        GDBJITInterfaceSystemJava.JITCodeEntry getRawHandle();
 
         @RawField
-        void setRawHandle(GDBJITInterface.JITCodeEntry value);
+        void setRawHandle(GDBJITInterfaceSystemJava.JITCodeEntry value);
 
         @RawField
         NonmovableArray<Byte> getDebugInfoData();
@@ -78,8 +78,9 @@ static final class Accessor implements InstalledCodeObserverHandleAccessor {
 
         static Handle createHandle(NonmovableArray<Byte> debugInfoData) {
             Handle handle = UnmanagedMemory.malloc(SizeOf.get(Handle.class));
+            GDBJITInterfaceSystemJava.JITCodeEntry entry = UnmanagedMemory.calloc(SizeOf.get(GDBJITInterfaceSystemJava.JITCodeEntry.class));
             handle.setAccessor(ImageSingletons.lookup(Accessor.class));
-            handle.setRawHandle(WordFactory.nullPointer());
+            handle.setRawHandle(entry);
             handle.setDebugInfoData(debugInfoData);
             handle.setState(Handle.INITIALIZED);
             return handle;
@@ -94,8 +95,7 @@ public void activate(InstalledCodeObserverHandle installedCodeObserverHandle) {
             NonmovableArray<Byte> debugInfoData = handle.getDebugInfoData();
             CCharPointer address = NonmovableArrays.addressOf(debugInfoData, 0);
             int size = NonmovableArrays.lengthOf(debugInfoData);
-            GDBJITInterface.JITCodeEntry entry = GDBJITInterface.registerJITCode(address, size);
-            handle.setRawHandle(entry);
+            GDBJITInterfaceSystemJava.registerJITCode(address, size, handle.getRawHandle());
 
             handle.setState(Handle.ACTIVATED);
         }
@@ -106,8 +106,8 @@ public void release(InstalledCodeObserverHandle installedCodeObserverHandle) {
             VMOperation.guaranteeInProgress("SubstrateDebugInfoInstaller.Accessor.release must run in a VMOperation");
             VMError.guarantee(handle.getState() == Handle.ACTIVATED);
 
-            GDBJITInterface.JITCodeEntry entry = handle.getRawHandle();
-            GDBJITInterface.unregisterJITCode(entry);
+            GDBJITInterfaceSystemJava.JITCodeEntry entry = handle.getRawHandle();
+            GDBJITInterfaceSystemJava.unregisterJITCode(entry);
 
             handle.setState(Handle.RELEASED);
             NonmovableArrays.releaseUnmanagedArray(handle.getDebugInfoData());
@@ -131,8 +131,8 @@ public void attachToCurrentIsolate(InstalledCodeObserverHandle installedCodeObse
         public void releaseOnTearDown(InstalledCodeObserverHandle installedCodeObserverHandle) {
             Handle handle = (Handle) installedCodeObserverHandle;
             if (handle.getState() == Handle.ACTIVATED) {
-                GDBJITInterface.JITCodeEntry entry = handle.getRawHandle();
-                GDBJITInterface.unregisterJITCode(entry);
+                GDBJITInterfaceSystemJava.JITCodeEntry entry = handle.getRawHandle();
+                GDBJITInterfaceSystemJava.unregisterJITCode(entry);
                 handle.setState(Handle.RELEASED);
             }
             NonmovableArrays.releaseUnmanagedArray(handle.getDebugInfoData());
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoFeature.java
index 41503d50756f..3758e2e7a8e5 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoFeature.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoFeature.java
@@ -24,6 +24,7 @@
  */
 package com.oracle.svm.hosted.image;
 
+import java.nio.ByteBuffer;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.List;
@@ -32,9 +33,13 @@
 
 import com.oracle.svm.core.ReservedRegisters;
 import jdk.graal.compiler.word.Word;
+import com.oracle.svm.core.debug.GDBJITInterfaceSystemJava;
 import com.oracle.svm.core.debug.SubstrateBFDNameProvider;
+import com.oracle.svm.core.ReservedRegisters;
+import jdk.vm.ci.code.Architecture;
 import org.graalvm.nativeimage.ImageSingletons;
 import org.graalvm.nativeimage.Platform;
+import org.graalvm.nativeimage.c.struct.SizeOf;
 import org.graalvm.word.PointerBase;
 
 import com.oracle.graal.pointsto.util.GraalAccess;
@@ -131,6 +136,17 @@ public void beforeAnalysis(BeforeAnalysisAccess access) {
         CGlobalDataFeature.singleton().registerWithGlobalHiddenSymbol(reservedBitsMask);
         CGlobalDataFeature.singleton().registerWithGlobalHiddenSymbol(objectAlignment);
         CGlobalDataFeature.singleton().registerWithGlobalHiddenSymbol(heapBaseRegnum);
+
+        if (SubstrateOptions.RuntimeDebugInfo.getValue()) {
+            Architecture arch = ConfigurationValues.getTarget().arch;
+            ByteBuffer buffer = ByteBuffer.allocate(SizeOf.get(GDBJITInterfaceSystemJava.JITDescriptor.class)).order(arch.getByteOrder());
+            buffer.putInt(1);  // version 1
+            buffer.putInt(0);  // action flag 0
+            buffer.putLong(0);  // relevant entry nullptr
+            buffer.putLong(0);  // first entry nullptr
+
+            CGlobalDataFeature.singleton().registerWithGlobalSymbol(CGlobalDataFactory.createBytes(buffer::array, "__jit_debug_descriptor"));
+        }
     }
 
     @Override
diff --git a/substratevm/src/com.oracle.svm.native.debug/include/gdbJITCompilationInterface.h b/substratevm/src/com.oracle.svm.native.debug/include/gdbJITCompilationInterface.h
index b61d31757dc8..cee76091b3d5 100644
--- a/substratevm/src/com.oracle.svm.native.debug/include/gdbJITCompilationInterface.h
+++ b/substratevm/src/com.oracle.svm.native.debug/include/gdbJITCompilationInterface.h
@@ -11,8 +11,8 @@
 typedef enum
 {
   JIT_NOACTION = 0,
-  JIT_REGISTER_FN,
-  JIT_UNREGISTER_FN
+  JIT_REGISTER,
+  JIT_UNREGISTER
 } jit_actions_t;
 
 struct jit_code_entry
@@ -33,6 +33,11 @@ struct jit_descriptor
   struct jit_code_entry *first_entry;
 };
 
+
+/* Make sure to specify the version statically, because the
+   debugger may check the version before we can set it.  */
+// struct jit_descriptor __jit_debug_descriptor = { 1, 0, 0, 0 };
+
 /* GDB puts a breakpoint in this function.  */
 void __attribute__((noinline)) __jit_debug_register_code() { };
 
diff --git a/substratevm/src/com.oracle.svm.native.debug/src/gdbJITCompilationInterface.c b/substratevm/src/com.oracle.svm.native.debug/src/gdbJITCompilationInterface.c
index 27f8a78ded60..80fe68983afc 100644
--- a/substratevm/src/com.oracle.svm.native.debug/src/gdbJITCompilationInterface.c
+++ b/substratevm/src/com.oracle.svm.native.debug/src/gdbJITCompilationInterface.c
@@ -8,33 +8,28 @@
 #include <stdlib.h>
 #include <assert.h>
 
-
-/* Make sure to specify the version statically, because the
-   debugger may check the version before we can set it.  */
-struct jit_descriptor __jit_debug_descriptor = { 1, 0, 0, 0 };
-
-
+/*
 struct jit_code_entry *register_jit_code(const char *addr, uint64_t size)
 {
-    /* Create new jit_code_entry */
-    struct jit_code_entry *const entry = malloc(sizeof(*entry));
+    /* Create new jit_code_entry /
+    struct jit_code_entry *const entry = calloc(1, sizeof(*entry));
     entry->symfile_addr = addr;
     entry->symfile_size = size;
 
-	/* Insert entry at head of the list. */
-	struct jit_code_entry *const next_entry = __jit_debug_descriptor.first_entry;
-	entry->prev_entry = NULL;
-	entry->next_entry = next_entry;
+    /* Insert entry at head of the list. /
+    struct jit_code_entry *const next_entry = __jit_debug_descriptor.first_entry;
+    entry->prev_entry = NULL;
+    entry->next_entry = next_entry;
 
-	if (next_entry != NULL) {
-	    next_entry->prev_entry = entry;
-        }
+    if (next_entry != NULL) {
+        next_entry->prev_entry = entry;
+    }
 
-	/* Notify GDB.  */
-	__jit_debug_descriptor.action_flag = JIT_REGISTER_FN;
-	__jit_debug_descriptor.first_entry = entry;
-	__jit_debug_descriptor.relevant_entry = entry;
-	__jit_debug_register_code();
+    /* Notify GDB.  /
+    __jit_debug_descriptor.action_flag = JIT_REGISTER;
+    __jit_debug_descriptor.first_entry = entry;
+    __jit_debug_descriptor.relevant_entry = entry;
+    __jit_debug_register_code();
 
     return entry;
 }
@@ -45,7 +40,7 @@ void unregister_jit_code(struct jit_code_entry *const entry)
     struct jit_code_entry *const prev_entry = entry->prev_entry;
     struct jit_code_entry *const next_entry = entry->next_entry;
 
-    /* Fix prev and next in list */
+    /* Fix prev and next in list /
     if (next_entry != NULL) {
 	next_entry->prev_entry = prev_entry;
     }
@@ -57,10 +52,10 @@ void unregister_jit_code(struct jit_code_entry *const entry)
 	__jit_debug_descriptor.first_entry = next_entry;
     }
 
-    /* Notify GDB.  */
-    __jit_debug_descriptor.action_flag = JIT_UNREGISTER_FN;
+    /* Notify GDB.  /
+    __jit_debug_descriptor.action_flag = JIT_UNREGISTER;
     __jit_debug_descriptor.relevant_entry = entry;
     __jit_debug_register_code();
 
     free(entry);
-}
+}*/

From 8f34784a298703f80e1a210b1c8033f99dac3195 Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Tue, 29 Oct 2024 17:57:01 +0100
Subject: [PATCH 07/34] Refactor and rework gdb-debughelpers

---
 substratevm/debug/gdbpy/gdb-debughelpers.py   | 338 ++++++++++++++----
 .../oracle/svm/core/code/CodeInfoDecoder.java |   2 +-
 .../image/NativeImageDebugInfoFeature.java    |   4 +-
 3 files changed, 279 insertions(+), 65 deletions(-)

diff --git a/substratevm/debug/gdbpy/gdb-debughelpers.py b/substratevm/debug/gdbpy/gdb-debughelpers.py
index 77c70eced249..f4366c3fee32 100644
--- a/substratevm/debug/gdbpy/gdb-debughelpers.py
+++ b/substratevm/debug/gdbpy/gdb-debughelpers.py
@@ -67,17 +67,6 @@ def trace(msg: str) -> None:
         svm_debug_tracing.tracefile.flush()
 
 
-def adr(obj: gdb.Value) -> int:
-    # use null as fallback if we cannot find the address value
-    adr_val = 0
-    if obj.type.code == gdb.TYPE_CODE_PTR:
-        if int(obj) != 0:
-            adr_val = int(obj.dereference().address)
-    elif obj.address is not None:
-        adr_val = int(obj.address)
-    return adr_val
-
-
 def try_or_else(success, failure, *exceptions):
     try:
         return success()
@@ -104,7 +93,9 @@ class SVMUtil:
     reserved_bits_mask = try_or_else(lambda: int(gdb.parse_and_eval('(int)__svm_reserved_bits_mask')), 0, gdb.error)
     object_alignment = try_or_else(lambda: int(gdb.parse_and_eval('(int)__svm_object_alignment')), 0, gdb.error)
     heap_base_regnum = try_or_else(lambda: int(gdb.parse_and_eval('(int)__svm_heap_base_regnum')), 0, gdb.error)
+    frame_size_status_mask = try_or_else(lambda: int(gdb.parse_and_eval('(int)__svm_frame_size_status_mask')), 0, gdb.error)
 
+    stack_type = gdb.lookup_type("long")
     string_type = gdb.lookup_type("java.lang.String")
     enum_type = gdb.lookup_type("java.lang.Enum")
     object_type = gdb.lookup_type("java.lang.Object")
@@ -124,6 +115,14 @@ class SVMUtil:
     hlreps = dict()
     deopt_stub_adr = 0
 
+    # AMD64 registers
+    AMD64_RSP = 7
+    AMD64_RIP = 16
+
+    # aarch64 registers
+    AARCH64_RSP = 30
+    AARCH64_RIP = 31
+
     @classmethod
     def get_heap_base(cls) -> gdb.Value:
         try:
@@ -132,9 +131,41 @@ def get_heap_base(cls) -> gdb.Value:
             # no frame available
             return cls.null
 
+    @classmethod
+    def get_rsp(cls) -> int:
+        arch = gdb.selected_frame().architecture().name()
+        if "x86-64" in arch:
+            return cls.AMD64_RSP
+        elif "aarch64" in arch:
+            return cls.AARCH64_RSP
+        return 0
+
+    @classmethod
+    def get_rip(cls) -> int:
+        arch = gdb.selected_frame().architecture().name()
+        if "x86-64" in arch:
+            return cls.AMD64_RIP
+        elif "aarch64" in arch:
+            return cls.AARCH64_RIP
+        return 0
+
+    @classmethod
+    def get_adr(cls, obj: gdb.Value) -> int:
+        # use null as fallback if we cannot find the address value or obj is null
+        adr_val = 0
+        if obj.type.code == gdb.TYPE_CODE_PTR:
+            if int(obj) == 0 or (cls.use_heap_base and int(obj) == int(cls.get_heap_base())):
+                # obj is null
+                pass
+            else:
+                adr_val = int(obj.dereference().address)
+        elif obj.address is not None:
+            adr_val = int(obj.address)
+        return adr_val
+
     @classmethod
     def is_null(cls, obj: gdb.Value) -> bool:
-        return adr(obj) == 0 or (cls.use_heap_base and adr(obj) == int(cls.get_heap_base()))
+        return cls.get_adr(obj) == 0
 
     @classmethod
     def get_uncompressed_type(cls, t: gdb.Type) -> gdb.Type:
@@ -145,6 +176,7 @@ def get_uncompressed_type(cls, t: gdb.Type) -> gdb.Type:
         trace(f'<SVMUtil> - get_uncompressed_type({t}) = {result}')
         return result
 
+    # returns the compressed variant of t if available, otherwise returns the basic type of t (without pointers)
     @classmethod
     def get_compressed_type(cls, t: gdb.Type) -> gdb.Type:
         t = cls.get_basic_type(t)
@@ -161,8 +193,15 @@ def get_compressed_type(cls, t: gdb.Type) -> gdb.Type:
         else:
             type_name = cls.compressed_ref_prefix + type_name
 
-        trace(f'<SVMUtil> - get_compressed_type({t}) = {type_name}')
-        return gdb.lookup_type(type_name)
+        try:
+            result_type = gdb.lookup_type(type_name)
+            trace(f'<SVMUtil> - could not find compressed type "{type_name}" using uncompressed type')
+        except gdb.error as ex:
+            trace(ex)
+            result_type = t
+
+        trace(f'<SVMUtil> - get_compressed_type({t}) = {t.name}')
+        return result_type
 
     @classmethod
     def get_compressed_oop(cls, obj: gdb.Value) -> int:
@@ -170,7 +209,7 @@ def get_compressed_oop(cls, obj: gdb.Value) -> int:
         if obj.type.code == gdb.TYPE_CODE_PTR and cls.is_compressed(obj.type):
             return int(obj)
 
-        obj_adr = adr(obj)
+        obj_adr = cls.get_adr(obj)
         if obj_adr == 0:
             return obj_adr
 
@@ -183,7 +222,7 @@ def get_compressed_oop(cls, obj: gdb.Value) -> int:
         num_alignment_bits = int.bit_count(cls.object_alignment - 1)
         compressed_oop = obj_adr
         if cls.use_heap_base:
-            compressed_oop -= int(SVMUtil.get_heap_base())
+            compressed_oop -= int(cls.get_heap_base())
             assert compression_shift >= 0
             compressed_oop = compressed_oop >> compression_shift
         if is_hub and num_reserved_bits != 0:
@@ -219,8 +258,8 @@ def adr_str(cls, obj: gdb.Value) -> str:
         if not svm_print_address.absolute_adr and cls.is_compressed(obj.type):
             result = f' @z({hex(cls.get_compressed_oop(obj))})'
         else:
-            result = f' @({hex(adr(obj))})'
-        trace(f'<SVMUtil> - adr_str({hex(adr(obj))}) = {result}')
+            result = f' @({hex(cls.get_adr(obj))})'
+        trace(f'<SVMUtil> - adr_str({hex(cls.get_adr(obj))}) = {result}')
         return result
 
     @classmethod
@@ -234,8 +273,8 @@ def prompt_hook(cls, current_prompt: str = None):
     def is_selfref(cls, obj: gdb.Value) -> bool:
         result = (svm_check_selfref.value and
                   not cls.is_primitive(obj.type) and
-                  adr(obj) in cls.selfref_cycles)
-        trace(f'<SVMUtil> - is_selfref({hex(adr(obj))}) = {result}')
+                  cls.get_adr(obj) in cls.selfref_cycles)
+        trace(f'<SVMUtil> - is_selfref({hex(cls.get_adr(obj))}) = {result}')
         return result
 
     @classmethod
@@ -244,8 +283,8 @@ def add_selfref(cls, parent: gdb.Value, child: gdb.Value) -> gdb.Value:
         if (child.type.code == gdb.TYPE_CODE_PTR and cls.is_null(child)) or cls.is_primitive(child.type):
             return child
 
-        child_adr = adr(child)
-        parent_adr = adr(parent)
+        child_adr = cls.get_adr(child)
+        parent_adr = cls.get_adr(parent)
         trace(f'<SVMUtil> - add_selfref(parent={hex(parent_adr)}, child={hex(child_adr)})')
         if svm_check_selfref.value and cls.is_reachable(child_adr, parent_adr):
             trace(f' <add selfref {child_adr}>')
@@ -276,7 +315,7 @@ def get_java_string(cls, obj: gdb.Value, gdb_output_string: bool = False) -> str
         if cls.is_null(obj):
             return ""
 
-        trace(f'<SVMUtil> - get_java_string({hex(adr(obj))})')
+        trace(f'<SVMUtil> - get_java_string({hex(cls.get_adr(obj))})')
         coder = cls.get_int_field(obj, 'coder', None)
         if coder is None:
             codec = 'utf-16'  # Java 8 has a char[] with utf-16
@@ -310,7 +349,7 @@ def get_java_string(cls, obj: gdb.Value, gdb_output_string: bool = False) -> str
         if gdb_output_string and 0 < svm_print_string_limit.value < value_length:
             result += "..."
 
-        trace(f'<SVMUtil> - get_java_string({hex(adr(obj))}) = {result}')
+        trace(f'<SVMUtil> - get_java_string({hex(cls.get_adr(obj))}) = {result}')
         return result
 
     @classmethod
@@ -410,7 +449,7 @@ def get_rtt(cls, obj: gdb.Value) -> gdb.Type:
         if cls.is_compressed(obj.type) and not cls.is_compressed(rtt):
             rtt = cls.get_compressed_type(rtt)
 
-        trace(f'<SVMUtil> - get_rtt({hex(adr(obj))}) = {rtt_name}')
+        trace(f'<SVMUtil> - get_rtt({hex(cls.get_adr(obj))}) = {rtt_name}')
         return rtt
 
     @classmethod
@@ -422,13 +461,13 @@ def cast_to(cls, obj: gdb.Value, t: gdb.Type) -> gdb.Value:
         if cls.is_compressed(t):
             obj_oop = cls.get_compressed_oop(obj)
         else:
-            obj_oop = adr(obj)
+            obj_oop = cls.get_adr(obj)
 
-        trace(f'<SVMUtil> - cast_to({hex(adr(obj))}, {t})')
+        trace(f'<SVMUtil> - cast_to({hex(cls.get_adr(obj))}, {t})')
         if t.code != gdb.TYPE_CODE_PTR:
             t = t.pointer()
 
-        trace(f'<SVMUtil> - cast_to({hex(adr(obj))}, {t}) returned')
+        trace(f'<SVMUtil> - cast_to({hex(cls.get_adr(obj))}, {t}) returned')
         # just use the raw pointer value and cast it instead the obj
         # casting the obj directly results in issues with compressed oops
         return obj if t == obj.type else gdb.Value(obj_oop).cast(t)
@@ -510,7 +549,7 @@ def get_all_member_functions(cls, t: gdb.Type, include_static: bool, include_con
         try:
             basic_type = cls.get_basic_type(t)
             type_name = basic_type.name
-            members = SVMUtil.execout(f"ptype '{type_name}'")
+            members = cls.execout(f"ptype '{type_name}'")
             for member in members.split('\n'):
                 parts = member.strip().split(' ')
                 is_static = parts[0] == 'static'
@@ -539,7 +578,7 @@ def is_java_type(cls, t: gdb.Type) -> bool:
         # a java class is represented by a struct, interfaces are represented by a union
         # only structs contain a "hub" field, thus just checking for a hub field does not work for interfaces
         result = ((t.code == gdb.TYPE_CODE_UNION and gdb.lookup_global_symbol(t.name + '.class', gdb.SYMBOL_VAR_DOMAIN) is not None) or
-                  (t.code == gdb.TYPE_CODE_STRUCT and gdb.types.has_field(t, SVMUtil.hub_field_name)))
+                  (t.code == gdb.TYPE_CODE_STRUCT and gdb.types.has_field(t, cls.hub_field_name)))
 
         trace(f'<SVMUtil> - is_java_obj({t}) = {result}')
         return result
@@ -547,7 +586,7 @@ def is_java_type(cls, t: gdb.Type) -> bool:
 
 class SVMPPString:
     def __init__(self, obj: gdb.Value, java: bool = True):
-        trace(f'<SVMPPString> - __init__({hex(adr(obj))})')
+        trace(f'<SVMPPString> - __init__({hex(SVMUtil.get_adr(obj))})')
         self.__obj = obj
         self.__java = java
 
@@ -569,7 +608,7 @@ def to_string(self) -> str:
 
 class SVMPPArray:
     def __init__(self, obj: gdb.Value, java_array: bool = True):
-        trace(f'<SVMPPArray> - __init__(obj={obj.type} @ {hex(adr(obj))}, java_array={java_array})')
+        trace(f'<SVMPPArray> - __init__(obj={obj.type} @ {hex(SVMUtil.get_adr(obj))}, java_array={java_array})')
         if java_array:
             self.__length = SVMUtil.get_int_field(obj, 'len')
             self.__array = SVMUtil.get_obj_field(obj, 'data', None)
@@ -625,7 +664,7 @@ def children(self) -> Iterable[object]:
 
 class SVMPPClass:
     def __init__(self, obj: gdb.Value, java_class: bool = True):
-        trace(f'<SVMPPClass> - __init__({obj.type} @ {hex(adr(obj))})')
+        trace(f'<SVMPPClass> - __init__({obj.type} @ {hex(SVMUtil.get_adr(obj))})')
         self.__obj = obj
         self.__java_class = java_class
         self.__skip_children = SVMUtil.is_selfref(obj) or 0 <= svm_print_depth_limit.value <= SVMUtil.current_print_depth
@@ -675,7 +714,7 @@ def children(self) -> Iterable[object]:
 
 class SVMPPEnum:
     def __init__(self, obj: gdb.Value):
-        trace(f'<SVMPPEnum> - __init__({hex(adr(obj))})')
+        trace(f'<SVMPPEnum> - __init__({hex(SVMUtil.get_adr(obj))})')
         self.__obj = obj
         self.__name = SVMUtil.get_obj_field(self.__obj, 'name', "")
         self.__ordinal = SVMUtil.get_int_field(self.__obj, 'ordinal', None)
@@ -690,7 +729,7 @@ def to_string(self) -> str:
 
 class SVMPPBoxedPrimitive:
     def __init__(self, obj: gdb.Value):
-        trace(f'<SVMPPBoxedPrimitive> - __init__({obj.type} @ {hex(adr(obj))})')
+        trace(f'<SVMPPBoxedPrimitive> - __init__({obj.type} @ {hex(SVMUtil.get_adr(obj))})')
         self.__obj = obj
         self.__value = SVMUtil.get_obj_field(self.__obj, 'value', obj.type.name)
 
@@ -718,7 +757,7 @@ def __init__(self):
         super().__init__(SVMUtil.pretty_printer_name)
 
     def __call__(self, obj: gdb.Value):
-        trace(f'<SVMPrettyPrinter> - __call__({obj.type} @ {hex(adr(obj))})')
+        trace(f'<SVMPrettyPrinter> - __call__({obj.type} @ {hex(SVMUtil.get_adr(obj))})')
 
         if not SVMUtil.is_primitive(obj.type) and SVMUtil.is_java_type(obj.type):
             # Filter out references to the null literal
@@ -793,7 +832,7 @@ class ArrayList:
     target_type = 'java.util.ArrayList'
 
     def __init__(self, obj: gdb.Value):
-        trace(f'<ArrayList> - __init__({obj.type} @ {hex(adr(obj))})')
+        trace(f'<ArrayList> - __init__({obj.type} @ {hex(SVMUtil.get_adr(obj))})')
         self.__size = SVMUtil.get_int_field(obj, 'size')
         element_data = SVMUtil.get_obj_field(obj, 'elementData')
         if SVMUtil.is_null(element_data):
@@ -844,14 +883,14 @@ def __iter__(self) -> gdb.Value:
                 yield self.__data[i]
 
     def children(self) -> Iterable[object]:
-        trace(f'<ArrayList> - children({self.__obj.type} @ {hex(adr(self.__obj))})')
+        trace(f'<ArrayList> - children({self.__obj.type} @ {hex(SVMUtil.get_adr(self.__obj))})')
         if self.__skip_children:
             return
         for index, elem in enumerate(self):
             if 0 <= svm_print_element_limit.value <= index:
                 yield str(index), '...'
                 return
-            trace(f'<ArrayList> - children({self.__obj.type} @ {hex(adr(self.__obj))})[{index}]')
+            trace(f'<ArrayList> - children({self.__obj.type} @ {hex(SVMUtil.get_adr(self.__obj))})[{index}]')
             yield str(index), SVMUtil.add_selfref(self.__obj, elem)
         SVMUtil.current_print_depth -= 1
 
@@ -861,7 +900,7 @@ class HashMap:
     target_type = 'java.util.HashMap'
 
     def __init__(self, obj: gdb.Value):
-        trace(f'<HashMap> - __init__({obj.type} @ {hex(adr(obj))})')
+        trace(f'<HashMap> - __init__({obj.type} @ {hex(SVMUtil.get_adr(obj))})')
 
         self.__size = SVMUtil.get_int_field(obj, 'size')
         table = SVMUtil.get_obj_field(obj, 'table')
@@ -928,14 +967,101 @@ def __iter__(self) -> tuple:  # (gdb.Value, gdb.Value):
                 obj = SVMUtil.get_obj_field(obj, 'next')
 
     def children(self) -> Iterable[object]:
-        trace(f'<HashMap> - children({self.__obj.type} @ {hex(adr(self.__obj))})')
+        trace(f'<HashMap> - children({self.__obj.type} @ {hex(SVMUtil.get_adr(self.__obj))})')
         if self.__skip_children:
             return
         for index, (key, value) in enumerate(self):
             if 0 <= svm_print_element_limit.value <= index:
                 yield str(index), '...'
                 return
-            trace(f'<HashMap> - children({self.__obj.type} @ {hex(adr(self.__obj))})[{index}]')
+            trace(f'<HashMap> - children({self.__obj.type} @ {hex(SVMUtil.get_adr(self.__obj))})[{index}]')
+            yield f"key{index}", SVMUtil.add_selfref(self.__obj, key)
+            yield f"value{index}", SVMUtil.add_selfref(self.__obj, value)
+        SVMUtil.current_print_depth -= 1
+
+
+@HLRep
+class EconomicMapImpl:
+    target_type = 'org.graalvm.collections.EconomicMapImpl'
+
+    def __init__(self, obj: gdb.Value):
+        trace(f'<EconomicMapImpl> - __init__({obj.type} @ {hex(SVMUtil.get_adr(obj))})')
+
+        self.__size = SVMUtil.get_int_field(obj, 'totalEntries') - SVMUtil.get_int_field(obj, 'deletedEntries')
+        entries = SVMUtil.get_obj_field(obj, 'entries')
+        if SVMUtil.is_null(entries):
+            self.__data = None
+            self.__array_len = 0
+        else:
+            self.__data = SVMUtil.get_obj_field(entries, 'data', None)
+            if self.__data is not None and SVMUtil.is_null(self.__data):
+                self.__data = None
+            self.__array_len = SVMUtil.get_int_field(entries, 'len')
+
+        self.__obj = obj
+        self.__skip_children = SVMUtil.is_selfref(obj) or 0 <= svm_print_depth_limit.value <= SVMUtil.current_print_depth
+        if not self.__skip_children:
+            SVMUtil.current_print_depth += 1
+
+    def to_string(self) -> str:
+        trace('<EconomicMapImpl> - to_string')
+        res = self.target_type
+        if svm_infer_generics.value != 0:
+            key_type, value_type = self.infer_generic_types()
+            res += f"<{key_type}, {value_type}>"
+        res += f'({self.__size})'
+        if self.__skip_children:
+            res += ' = {...}'
+        if svm_print_address.with_adr:
+            res += SVMUtil.adr_str(self.__obj)
+        trace(f'<EconomicMapImpl> - to_string = {res}')
+        return res
+
+    def infer_generic_types(self) -> tuple:  # (str, str):
+        key_type: list = []  # list[gdb.Type]
+        value_type: list = []  # list[gdb.Type]
+
+        for i, kv in enumerate(self, 1):
+            key, value = kv
+            # if len(*_type) = 1 we could just infer the type java.lang.Object, ignore null values
+            if not SVMUtil.is_null(key) and (len(key_type) == 0 or key_type[0] != SVMUtil.object_type):
+                key_type = SVMUtil.find_shared_types(key_type, SVMUtil.get_rtt(key))
+            if not SVMUtil.is_null(value) and (len(value_type) == 0 or value_type[0] != SVMUtil.object_type):
+                value_type = SVMUtil.find_shared_types(value_type, SVMUtil.get_rtt(value))
+            if (0 <= svm_infer_generics.value <= i) or (len(key_type) > 0 and key_type[0] == SVMUtil.object_type and
+                                                        len(value_type) > 0 and value_type[0] == SVMUtil.object_type):
+                break
+
+        key_type_name = '?' if len(key_type) == 0 else SVMUtil.get_unqualified_type_name(key_type[0].name)
+        value_type_name = '?' if len(value_type) == 0 else SVMUtil.get_unqualified_type_name(value_type[0].name)
+
+        return key_type_name, value_type_name
+
+    def display_hint(self) -> str:
+        trace('<EconomicMapImpl> - display_hint = map')
+        return "map"
+
+    def __iter__(self) -> tuple:  # (gdb.Value, gdb.Value):
+        trace('<EconomicMapImpl> - __iter__')
+        key = SVMUtil.null
+        for i in range(self.__array_len):
+            if i % 2 == 0:
+                if SVMUtil.is_null(self.__data[i]):
+                    break
+                key = self.__data[i]
+            else:
+                value = self.__data[i]
+                yield key, value
+
+    def children(self) -> Iterable[object]:
+        trace(f'<EconomicMapImpl> - children({self.__obj.type} @ {hex(SVMUtil.get_adr(self.__obj))})')
+        if self.__skip_children:
+            return
+        for index, (key, value) in enumerate(self):
+            if 0 <= svm_print_element_limit.value <= index:
+                yield str(index), '...'
+                return
+            trace(f'<EconomicMapImpl> - children({self.__obj.type} @ {hex(SVMUtil.get_adr(self.__obj))})[{index}]')
             yield f"key{index}", SVMUtil.add_selfref(self.__obj, key)
             yield f"value{index}", SVMUtil.add_selfref(self.__obj, value)
         SVMUtil.current_print_depth -= 1
@@ -1216,7 +1342,7 @@ def cast_to_rtt(obj: gdb.Value, obj_str: str) -> tuple:  # tuple[gdb.Value, str]
         if static_type.name == rtt.name:
             return obj, obj_str
         else:
-            obj_oop = SVMUtil.get_compressed_oop(obj) if SVMUtil.is_compressed(rtt) else adr(obj)
+            obj_oop = SVMUtil.get_compressed_oop(obj) if SVMUtil.is_compressed(rtt) else SVMUtil.get_adr(obj)
             return obj, f"(('{rtt.name}' *)({obj_oop}))"
 
     # Define the token specifications
@@ -1414,7 +1540,7 @@ def params(self, completion: bool = False) -> str:
             obj = gdb.parse_and_eval(obj_str)  # check if gdb can handle the current param
             if SVMUtil.is_java_type(obj.type) and SVMUtil.is_compressed(obj.type):
                 # uncompress compressed java params
-                obj_str = f"(('{SVMUtil.get_uncompressed_type(SVMUtil.get_basic_type(obj.type)).name}' *)({adr(obj)}))"
+                obj_str = f"(('{SVMUtil.get_uncompressed_type(SVMUtil.get_basic_type(obj.type)).name}' *)({SVMUtil.get_adr(obj)}))"
             param_str += obj_str
             if self.sym == "COMMA":
                 self.scan()
@@ -1488,37 +1614,37 @@ def invoke(self, arg: str, from_tty: bool) -> None:
 
 
 class SVMFrameUnwinder(gdb.unwinder.Unwinder):
-    AMD64_RBP = 6
-    AMD64_RSP = 7
-    AMD64_RIP = 16
 
     def __init__(self):
         super().__init__('SubstrateVM FrameUnwinder')
-        self.stack_type = gdb.lookup_type('long')
-        self.deopt_frame_type = gdb.lookup_type('com.oracle.svm.core.deopt.DeoptimizedFrame')
 
-    def __call__(self, pending_frame):
+    def __call__(self, pending_frame: gdb.Frame):
         if SVMUtil.deopt_stub_adr == 0:
             # find deopt stub after its properly loaded
             SVMUtil.deopt_stub_adr = gdb.lookup_global_symbol('com.oracle.svm.core.deopt.Deoptimizer::deoptStub',
                                                               gdb.SYMBOL_VAR_DOMAIN).value().address
 
+        rsp = 0
         try:
             rsp = pending_frame.read_register('sp')
             rip = pending_frame.read_register('pc')
             if int(rip) == SVMUtil.deopt_stub_adr:
-                deopt_frame_stack_slot = rsp.cast(self.stack_type.pointer()).dereference()
-                deopt_frame = deopt_frame_stack_slot.cast(self.deopt_frame_type.pointer())
-                source_frame_size = deopt_frame['sourceTotalFrameSize']
+                deopt_frame_stack_slot = rsp.cast(SVMUtil.stack_type.pointer()).dereference()
+                deopt_frame = deopt_frame_stack_slot.cast(SVMUtil.get_compressed_type(SVMUtil.object_type).pointer())
+                rtt = SVMUtil.get_rtt(deopt_frame)
+                deopt_frame = SVMUtil.cast_to(deopt_frame, rtt)
+                encoded_frame_size = SVMUtil.get_int_field(deopt_frame, 'sourceEncodedFrameSize')
+                source_frame_size = encoded_frame_size & ~SVMUtil.frame_size_status_mask
                 # Now find the register-values for the caller frame
                 unwind_info = pending_frame.create_unwind_info(gdb.unwinder.FrameId(rsp, rip))
                 caller_rsp = rsp + int(source_frame_size)
-                unwind_info.add_saved_register(self.AMD64_RSP, gdb.Value(caller_rsp))
-                caller_rip = gdb.Value(caller_rsp - 8).cast(self.stack_type.pointer()).dereference()
-                unwind_info.add_saved_register(self.AMD64_RIP, gdb.Value(caller_rip))
+                unwind_info.add_saved_register(SVMUtil.get_rsp(), gdb.Value(caller_rsp))
+                caller_rip = gdb.Value(caller_rsp - 8).cast(SVMUtil.stack_type.pointer()).dereference()
+                unwind_info.add_saved_register(SVMUtil.get_rip(), gdb.Value(caller_rip))
                 return unwind_info
-        except Exception as e:
-            print(e)
+        except Exception as ex:
+            trace(f'<SVMFrameUnwinder> - Failed to unwind frame at {hex(rsp)}')
+            trace(ex)
             # Fallback to default frame unwinding via debug_frame (dwarf)
 
         return None
@@ -1530,7 +1656,7 @@ def __init__(self):
         self.priority = 100
         self.enabled = True
 
-    def filter(self, frame_iter):
+    def filter(self, frame_iter: Iterable) -> FrameDecorator:
         for frame in frame_iter:
             frame = frame.inferior_frame()
             if SVMUtil.deopt_stub_adr and frame.pc() == SVMUtil.deopt_stub_adr:
@@ -1540,7 +1666,7 @@ def filter(self, frame_iter):
 
 
 class SVMFrame(FrameDecorator):
-    def function(self):
+    def function(self) -> str:
         frame = self.inferior_frame()
         if not frame.name():
             return 'Unknown Frame at ' + hex(int(frame.read_register('sp')))
@@ -1560,12 +1686,98 @@ def function(self):
         return func_name + eclipse_filename
 
 
+class SymValueWrapper():
+
+    def __init__(self, symbol, value):
+        self.sym = symbol
+        self.val = value
+
+    def value(self):
+        return self.val
+
+    def symbol(self):
+        return self.sym
+
+
 class SVMFrameDeopt(SVMFrame):
-    def function(self):
-        return '[DEOPT FRAMES ...]'
+
+    def __init__(self, frame: gdb.Frame):
+        super().__init__(frame)
+
+        # fetch deoptimized frame from stack
+        rsp = frame.read_register('sp')
+        assert SVMUtil.deopt_stub_adr and frame.pc() == SVMUtil.deopt_stub_adr
+        deopt_frame_stack_slot = rsp.cast(SVMUtil.stack_type.pointer()).dereference()
+        deopt_frame = deopt_frame_stack_slot.cast(SVMUtil.get_compressed_type(SVMUtil.object_type).pointer())
+        rtt = SVMUtil.get_rtt(deopt_frame)
+        deopt_frame = SVMUtil.cast_to(deopt_frame, rtt)
+        self.__virtual_frame = SVMUtil.get_obj_field(deopt_frame, 'topFrame')
+        self.__frame_info = SVMUtil.get_obj_field(self.__virtual_frame, 'frameInfo')
+
+    def function(self) -> str:
+        if self.__frame_info is None or SVMUtil.is_null(self.__frame_info):
+            # we have no more information about the frame
+            return '[DEOPT FRAME ...]'
+
+        # read from deoptimized frame
+        source_class = SVMUtil.get_obj_field(self.__frame_info, 'sourceClass')
+        if SVMUtil.is_null(source_class):
+            source_class_name = ''
+        else:
+            source_class_name = str(SVMUtil.get_obj_field(source_class, 'name'))[1:-1]
+            if len(source_class_name) > 0:
+                source_class_name = source_class_name + '::'
+
+        source_file_name = self.filename()
+        if source_file_name is None or len(source_file_name) == 0:
+            source_file_name = ''
+        else:
+            line = self.line()
+            if line is not None and line != 0:
+                source_file_name = source_file_name + ':' + str(line)
+            source_file_name = '(' + source_file_name + ')'
+
+        func_name = str(SVMUtil.get_obj_field(self.__frame_info, 'sourceMethodName'))[1:-1]
+
+        return '[DEOPT FRAME] ' + source_class_name + func_name + source_file_name
+
+    def filename(self):
+        if self.__frame_info is None or SVMUtil.is_null(self.__frame_info):
+            return None
+
+        source_class = SVMUtil.get_obj_field(self.__frame_info, 'sourceClass')
+
+        if SVMUtil.is_null(source_class):
+            source_file_name = ''
+        else:
+            source_file_name = str(SVMUtil.get_obj_field(source_class, 'sourceFileName'))[1:-1]
+
+        return source_file_name
+
+    def line(self):
+        if self.__frame_info is None or SVMUtil.is_null(self.__frame_info):
+            return None
+        return SVMUtil.get_int_field(self.__frame_info, 'sourceLineNumber')
 
     def frame_args(self):
-        return None
+        if self.__frame_info is None or SVMUtil.is_null(self.__frame_info):
+            return None
+
+        values = SVMUtil.get_obj_field(self.__virtual_frame, 'values')
+        data = SVMUtil.get_obj_field(values, 'data')
+        length = SVMUtil.get_int_field(values, 'len')
+        args = [SymValueWrapper('deoptFrameValues', length)]
+        if SVMUtil.is_null(data) or length == 0:
+            return args
+
+        for i in range(length):
+            elem = data[i]
+            rtt = SVMUtil.get_rtt(elem)
+            elem = SVMUtil.cast_to(elem, rtt)
+            value = SVMUtil.get_obj_field(elem, 'value')
+            args.append(SymValueWrapper(f'__{i}', value))
+
+        return args
 
     def frame_locals(self):
         return None
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoDecoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoDecoder.java
index f82497f7e3cc..fd16011867dc 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoDecoder.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoDecoder.java
@@ -347,7 +347,7 @@ private static long loadReferenceMapIndex(CodeInfo info, long entryOffset, int e
     static final int FRAME_SIZE_ENTRY_POINT = 0b010;
     static final int FRAME_SIZE_HAS_CALLEE_SAVED_REGISTERS = 0b100;
 
-    static final int FRAME_SIZE_STATUS_MASK = FRAME_SIZE_METHOD_START | FRAME_SIZE_ENTRY_POINT | FRAME_SIZE_HAS_CALLEE_SAVED_REGISTERS;
+    public static final int FRAME_SIZE_STATUS_MASK = FRAME_SIZE_METHOD_START | FRAME_SIZE_ENTRY_POINT | FRAME_SIZE_HAS_CALLEE_SAVED_REGISTERS;
 
     @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
     public static boolean decodeIsEntryPoint(long sizeEncoding) {
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoFeature.java
index 3758e2e7a8e5..23d822604f97 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoFeature.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoFeature.java
@@ -33,9 +33,9 @@
 
 import com.oracle.svm.core.ReservedRegisters;
 import jdk.graal.compiler.word.Word;
+import com.oracle.svm.core.code.CodeInfoDecoder;
 import com.oracle.svm.core.debug.GDBJITInterfaceSystemJava;
 import com.oracle.svm.core.debug.SubstrateBFDNameProvider;
-import com.oracle.svm.core.ReservedRegisters;
 import jdk.vm.ci.code.Architecture;
 import org.graalvm.nativeimage.ImageSingletons;
 import org.graalvm.nativeimage.Platform;
@@ -130,11 +130,13 @@ public void beforeAnalysis(BeforeAnalysisAccess access) {
         CGlobalData<PointerBase> useHeapBase = CGlobalDataFactory.createWord(Word.unsigned(compressEncoding.hasBase() ? 1 : 0), "__svm_use_heap_base");
         CGlobalData<PointerBase> reservedBitsMask = CGlobalDataFactory.createWord(Word.unsigned(Heap.getHeap().getObjectHeader().getReservedBitsMask()), "__svm_reserved_bits_mask");
         CGlobalData<PointerBase> objectAlignment = CGlobalDataFactory.createWord(Word.unsigned(ConfigurationValues.getObjectLayout().getAlignment()), "__svm_object_alignment");
+        CGlobalData<PointerBase> frameSizeStatusMask = CGlobalDataFactory.createWord(Word.unsigned(CodeInfoDecoder.FRAME_SIZE_STATUS_MASK), "__svm_frame_size_status_mask");
         CGlobalData<PointerBase> heapBaseRegnum = CGlobalDataFactory.createWord(Word.unsigned(ReservedRegisters.singleton().getHeapBaseRegister().number), "__svm_heap_base_regnum");
         CGlobalDataFeature.singleton().registerWithGlobalHiddenSymbol(compressionShift);
         CGlobalDataFeature.singleton().registerWithGlobalHiddenSymbol(useHeapBase);
         CGlobalDataFeature.singleton().registerWithGlobalHiddenSymbol(reservedBitsMask);
         CGlobalDataFeature.singleton().registerWithGlobalHiddenSymbol(objectAlignment);
+        CGlobalDataFeature.singleton().registerWithGlobalHiddenSymbol(frameSizeStatusMask);
         CGlobalDataFeature.singleton().registerWithGlobalHiddenSymbol(heapBaseRegnum);
 
         if (SubstrateOptions.RuntimeDebugInfo.getValue()) {

From 46480243aa6afa14c48583ca493306824e8c0f1e Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Wed, 20 Nov 2024 15:06:03 +0100
Subject: [PATCH 08/34] Rework Debug Info Generation to create a Shared base
 for AOT and Runtime Debug Info

Rework debug entries

Remove separate Runtime Debuginfo sections
---
 substratevm/mx.substratevm/suite.py           |    3 +-
 .../src/com/oracle/objectfile/ObjectFile.java |   24 +-
 .../objectfile/debugentry/ArrayTypeEntry.java |   50 +-
 .../objectfile/debugentry/ClassEntry.java     |  342 +-
 .../debugentry/CompiledMethodEntry.java       |  133 +-
 .../objectfile/debugentry/DebugInfoBase.java  |  542 +--
 .../objectfile/debugentry/DirEntry.java       |   13 +-
 .../objectfile/debugentry/EnumClassEntry.java |   21 +-
 .../objectfile/debugentry/FieldEntry.java     |    7 +-
 .../objectfile/debugentry/FileEntry.java      |   36 +-
 .../debugentry/ForeignTypeEntry.java          |  130 +-
 .../debugentry/FrameSizeChangeEntry.java      |   13 +
 .../debugentry/HeaderTypeEntry.java           |   22 +-
 .../debugentry/InterfaceClassEntry.java       |   36 +-
 .../objectfile/debugentry/LoaderEntry.java    |   13 +-
 .../objectfile/debugentry/LocalEntry.java     |   67 +
 .../debugentry/LocalValueEntry.java           |   18 +
 .../objectfile/debugentry/MemberEntry.java    |   25 +-
 .../objectfile/debugentry/MethodEntry.java    |  338 +-
 .../debugentry/PrimitiveTypeEntry.java        |   75 +-
 .../objectfile/debugentry/StringEntry.java    |   16 +-
 .../objectfile/debugentry/StringTable.java    |   35 +-
 .../debugentry/StructureTypeEntry.java        |   68 +-
 .../objectfile/debugentry/TypeEntry.java      |   69 +-
 .../debugentry/range/CallRange.java           |   66 +-
 .../debugentry/range/LeafRange.java           |   15 +-
 .../debugentry/range/PrimaryRange.java        |   72 +-
 .../objectfile/debugentry/range/Range.java    |  311 +-
 .../objectfile/debugentry/range/SubRange.java |  129 -
 .../debuginfo/DebugInfoProvider.java          |  398 +--
 .../oracle/objectfile/elf/ELFObjectFile.java  |   61 +-
 .../elf/dwarf/DwarfARangesSectionImpl.java    |   14 +-
 .../elf/dwarf/DwarfAbbrevSectionImpl.java     |  140 +-
 .../objectfile/elf/dwarf/DwarfDebugInfo.java  |   77 +-
 .../elf/dwarf/DwarfFrameSectionImpl.java      |   17 +-
 .../dwarf/DwarfFrameSectionImplAArch64.java   |   10 +-
 .../dwarf/DwarfFrameSectionImplX86_64.java    |   10 +-
 .../elf/dwarf/DwarfInfoSectionImpl.java       |  299 +-
 .../elf/dwarf/DwarfLineSectionImpl.java       |   70 +-
 .../elf/dwarf/DwarfLocSectionImpl.java        |   53 +-
 .../elf/dwarf/DwarfRangesSectionImpl.java     |   16 +-
 .../elf/dwarf/DwarfSectionImpl.java           |  137 +-
 .../elf/dwarf/DwarfStrSectionImpl.java        |   18 +-
 .../objectfile/pecoff/cv/CVDebugInfo.java     |    1 +
 .../pecoff/cv/CVLineRecordBuilder.java        |   11 +-
 .../oracle/objectfile/pecoff/cv/CVNames.java  |    4 +-
 .../pecoff/cv/CVSymbolSubrecord.java          |    4 +-
 .../pecoff/cv/CVSymbolSubsectionBuilder.java  |    8 +-
 .../pecoff/cv/CVTypeSectionBuilder.java       |   61 +-
 .../runtime/RuntimeDebugInfoBase.java         |  494 ---
 .../runtime/RuntimeDebugInfoProvider.java     |   84 -
 .../dwarf/RuntimeDwarfAbbrevSectionImpl.java  |  399 ---
 .../runtime/dwarf/RuntimeDwarfDebugInfo.java  |  524 ---
 .../dwarf/RuntimeDwarfFrameSectionImpl.java   |  303 --
 .../RuntimeDwarfFrameSectionImplAArch64.java  |  113 -
 .../RuntimeDwarfFrameSectionImplX86_64.java   |  112 -
 .../dwarf/RuntimeDwarfInfoSectionImpl.java    |  729 ----
 .../dwarf/RuntimeDwarfLineSectionImpl.java    |  827 -----
 .../dwarf/RuntimeDwarfLocSectionImpl.java     |  648 ----
 .../dwarf/RuntimeDwarfSectionImpl.java        |  952 ------
 .../dwarf/RuntimeDwarfStrSectionImpl.java     |   79 -
 .../core/debug/SharedDebugInfoProvider.java   | 1078 ++++++
 .../debug/SubstrateDebugInfoInstaller.java    |   78 +-
 .../debug/SubstrateDebugInfoProvider.java     | 1915 +----------
 .../image/NativeImageDebugInfoProvider.java   | 2932 +++--------------
 65 files changed, 2977 insertions(+), 12388 deletions(-)
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FrameSizeChangeEntry.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalEntry.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalValueEntry.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/SubRange.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/RuntimeDebugInfoBase.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/RuntimeDebugInfoProvider.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfAbbrevSectionImpl.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfDebugInfo.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImpl.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImplAArch64.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImplX86_64.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfInfoSectionImpl.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfLineSectionImpl.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfLocSectionImpl.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfSectionImpl.java
 delete mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfStrSectionImpl.java
 create mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SharedDebugInfoProvider.java

diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py
index b718d19d0ef3..56dc42b6dd8c 100644
--- a/substratevm/mx.substratevm/suite.py
+++ b/substratevm/mx.substratevm/suite.py
@@ -1891,8 +1891,9 @@
                 "com.oracle.objectfile",
                 "com.oracle.objectfile.io",
                 "com.oracle.objectfile.debuginfo",
+                "com.oracle.objectfile.debugentry",
+                "com.oracle.objectfile.debugentry.range",
                 "com.oracle.objectfile.macho",
-                "com.oracle.objectfile.runtime",
               ],
 
               "requiresConcealed" : {
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/ObjectFile.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/ObjectFile.java
index d9709bf4bb04..23853be51e0b 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/ObjectFile.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/ObjectFile.java
@@ -46,7 +46,6 @@
 import java.util.function.Consumer;
 import java.util.stream.StreamSupport;
 
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider;
 import com.oracle.objectfile.debuginfo.DebugInfoProvider;
 import com.oracle.objectfile.elf.ELFObjectFile;
 import com.oracle.objectfile.macho.MachOObjectFile;
@@ -220,16 +219,12 @@ public static Format getNativeFormat() {
     }
 
     private static ObjectFile getNativeObjectFile(int pageSize, boolean runtimeDebugInfoGeneration) {
-        switch (ObjectFile.getNativeFormat()) {
-            case ELF:
-                return new ELFObjectFile(pageSize, runtimeDebugInfoGeneration);
-            case MACH_O:
-                return new MachOObjectFile(pageSize);
-            case PECOFF:
-                return new PECoffObjectFile(pageSize);
-            default:
-                throw new AssertionError("Unreachable");
-        }
+        return switch (ObjectFile.getNativeFormat()) {
+            case ELF -> new ELFObjectFile(pageSize, runtimeDebugInfoGeneration);
+            case MACH_O -> new MachOObjectFile(pageSize);
+            case PECOFF -> new PECoffObjectFile(pageSize);
+            default -> throw new AssertionError("Unreachable");
+        };
     }
 
     public static ObjectFile getNativeObjectFile(int pageSize) {
@@ -1181,11 +1176,6 @@ public void installDebugInfo(@SuppressWarnings("unused") DebugInfoProvider debug
         // do nothing by default
     }
 
-
-    public void installRuntimeDebugInfo(@SuppressWarnings("unused") RuntimeDebugInfoProvider runtimeDebugInfoProvider) {
-        // do nothing by default
-    }
-
     protected static Iterable<LayoutDecision> allDecisions(final Map<Element, LayoutDecisionMap> decisions) {
         return () -> StreamSupport.stream(decisions.values().spliterator(), false)
                         .flatMap(layoutDecisionMap -> StreamSupport.stream(layoutDecisionMap.spliterator(), false)).iterator();
@@ -1834,7 +1824,7 @@ public final SymbolTable getOrCreateSymbolTable() {
      * Temporary storage for a debug context installed in a nested scope under a call. to
      * {@link #withDebugContext}
      */
-    private DebugContext debugContext = DebugContext.disabled(null);
+    protected DebugContext debugContext = DebugContext.disabled(null);
 
     /**
      * Allows a task to be executed with a debug context in a named subscope bound to the object
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ArrayTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ArrayTypeEntry.java
index 114c585637ea..70346824800c 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ArrayTypeEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ArrayTypeEntry.java
@@ -26,40 +26,21 @@
 
 package com.oracle.objectfile.debugentry;
 
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugArrayTypeInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind;
-
-import jdk.graal.compiler.debug.DebugContext;
-import jdk.vm.ci.meta.ResolvedJavaType;
-
 public class ArrayTypeEntry extends StructureTypeEntry {
-    private TypeEntry elementType;
-    private int baseSize;
-    private int lengthOffset;
-
-    public ArrayTypeEntry(String typeName, int size) {
-        super(typeName, size);
-    }
-
-    @Override
-    public DebugTypeKind typeKind() {
-        return DebugTypeKind.ARRAY;
+    private final TypeEntry elementType;
+    private final LoaderEntry loader;
+
+    public ArrayTypeEntry(String typeName, int size, long classOffset, long typeSignature,
+                          long compressedTypeSignature, long layoutTypeSignature, long compressedLayoutTypeSignature,
+                          TypeEntry elementType, LoaderEntry loader) {
+        super(typeName, size, classOffset, typeSignature, compressedTypeSignature, layoutTypeSignature, compressedLayoutTypeSignature);
+        this.elementType = elementType;
+        this.loader = loader;
     }
 
     @Override
-    public void addDebugInfo(DebugInfoBase debugInfoBase, DebugTypeInfo debugTypeInfo, DebugContext debugContext) {
-        super.addDebugInfo(debugInfoBase, debugTypeInfo, debugContext);
-        DebugArrayTypeInfo debugArrayTypeInfo = (DebugArrayTypeInfo) debugTypeInfo;
-        ResolvedJavaType eltType = debugArrayTypeInfo.elementType();
-        this.elementType = debugInfoBase.lookupTypeEntry(eltType);
-        this.baseSize = debugArrayTypeInfo.baseSize();
-        this.lengthOffset = debugArrayTypeInfo.lengthOffset();
-        /* Add details of fields and field types */
-        debugArrayTypeInfo.fieldInfoProvider().forEach(debugFieldInfo -> this.processField(debugFieldInfo, debugInfoBase, debugContext));
-        if (debugContext.isLogEnabled()) {
-            debugContext.log("typename %s element type %s base size %d length offset %d%n", typeName, this.elementType.getTypeName(), baseSize, lengthOffset);
-        }
+    public boolean isArray() {
+        return true;
     }
 
     public TypeEntry getElementType() {
@@ -67,13 +48,6 @@ public TypeEntry getElementType() {
     }
 
     public String getLoaderId() {
-        TypeEntry type = elementType;
-        while (type.isArray()) {
-            type = ((ArrayTypeEntry) type).elementType;
-        }
-        if (type.isClass()) {
-            return ((ClassEntry) type).getLoaderId();
-        }
-        return "";
+        return (loader != null ? loader.loaderId() : "");
     }
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
index 1bfc0981a334..d23f3afdf961 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
@@ -26,27 +26,11 @@
 
 package com.oracle.objectfile.debugentry;
 
+import com.oracle.objectfile.debugentry.range.Range;
+
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
-import java.util.stream.Stream;
-
-import org.graalvm.collections.EconomicMap;
-
-import com.oracle.objectfile.debugentry.range.PrimaryRange;
-import com.oracle.objectfile.debugentry.range.Range;
-import com.oracle.objectfile.debugentry.range.SubRange;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFieldInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFrameSizeChange;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugInstanceTypeInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugMethodInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugRangeInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind;
-
-import jdk.graal.compiler.debug.DebugContext;
-import jdk.vm.ci.meta.ResolvedJavaMethod;
-import jdk.vm.ci.meta.ResolvedJavaType;
 
 /**
  * Track debug info associated with a Java class.
@@ -55,11 +39,7 @@ public class ClassEntry extends StructureTypeEntry {
     /**
      * Details of this class's superclass.
      */
-    protected ClassEntry superClass;
-    /**
-     * Details of this class's interfaces.
-     */
-    protected final List<InterfaceClassEntry> interfaces = new ArrayList<>();
+    private final ClassEntry superClass;
     /**
      * Details of the associated file.
      */
@@ -67,116 +47,91 @@ public class ClassEntry extends StructureTypeEntry {
     /**
      * Details of the associated loader.
      */
-    private LoaderEntry loader;
+    private final LoaderEntry loader;
     /**
      * Details of methods located in this instance.
      */
-    protected final List<MethodEntry> methods = new ArrayList<>();
-    /**
-     * An index of all currently known methods keyed by the unique, associated, identifying
-     * ResolvedJavaMethod.
-     */
-    private final EconomicMap<ResolvedJavaMethod, MethodEntry> methodsIndex = EconomicMap.create();
+    private final List<MethodEntry> methods;
     /**
      * A list recording details of all normal compiled methods included in this class sorted by
      * ascending address range. Note that the associated address ranges are disjoint and contiguous.
      */
-    private final List<CompiledMethodEntry> compiledEntries = new ArrayList<>();
-    /**
-     * An index identifying ranges for compiled method which have already been encountered.
-     */
-    private final EconomicMap<Range, CompiledMethodEntry> compiledMethodIndex = EconomicMap.create();
+    private final List<CompiledMethodEntry> compiledMethods;
 
     /**
      * A list of all files referenced from info associated with this class, including info detailing
      * inline method ranges.
      */
-    private final ArrayList<FileEntry> files;
+    private final List<FileEntry> files;
     /**
      * A list of all directories referenced from info associated with this class, including info
      * detailing inline method ranges.
      */
-    private final ArrayList<DirEntry> dirs;
-    /**
-     * An index identifying the file table position of every file referenced from info associated
-     * with this class, including info detailing inline method ranges.
-     */
-    private EconomicMap<FileEntry, Integer> fileIndex;
-    /**
-     * An index identifying the dir table position of every directory referenced from info
-     * associated with this class, including info detailing inline method ranges.
-     */
-    private EconomicMap<DirEntry, Integer> dirIndex;
+    private final List<DirEntry> dirs;
 
-    public ClassEntry(String className, FileEntry fileEntry, int size) {
-        super(className, size);
+    public ClassEntry(String typeName, int size, long classOffset, long typeSignature,
+                      long compressedTypeSignature, long layoutTypeSignature, long compressedLayoutTypeSignature,
+                      ClassEntry superClass, FileEntry fileEntry, LoaderEntry loader) {
+        super(typeName, size, classOffset, typeSignature, compressedTypeSignature, layoutTypeSignature, compressedLayoutTypeSignature);
+        this.superClass = superClass;
         this.fileEntry = fileEntry;
-        this.loader = null;
-        // file and dir lists/indexes are populated after all DebugInfo API input has
-        // been received and are only created on demand
-        files = new ArrayList<>();
-        dirs = new ArrayList<>();
-        // create these on demand using the size of the file and dir lists
-        this.fileIndex = null;
-        this.dirIndex = null;
+        this.loader = loader;
+
+        this.methods = new ArrayList<>();
+        this.compiledMethods = new ArrayList<>();
+        this.files = new ArrayList<>();
+        this.dirs = new ArrayList<>();
+
+        // add own file to file/dir entries
+        if (fileEntry != null && !fileEntry.fileName().isEmpty()) {
+            files.add(fileEntry);
+            DirEntry dirEntry = fileEntry.dirEntry();
+            if (dirEntry != null && !dirEntry.getPathString().isEmpty()) {
+                dirs.add(dirEntry);
+            }
+        }
     }
 
     @Override
-    public DebugTypeKind typeKind() {
-        return DebugTypeKind.INSTANCE;
+    public void addField(FieldEntry field) {
+        addFile(field.getFileEntry());
+        super.addField(field);
     }
 
-    @Override
-    public void addDebugInfo(DebugInfoBase debugInfoBase, DebugTypeInfo debugTypeInfo, DebugContext debugContext) {
-        super.addDebugInfo(debugInfoBase, debugTypeInfo, debugContext);
-        assert debugTypeInfo.typeName().equals(typeName);
-        DebugInstanceTypeInfo debugInstanceTypeInfo = (DebugInstanceTypeInfo) debugTypeInfo;
-        /* Add details of super and interface classes */
-        ResolvedJavaType superType = debugInstanceTypeInfo.superClass();
-        if (debugContext.isLogEnabled()) {
-            debugContext.log("typename %s adding super %s%n", typeName, superType != null ? superType.toJavaName() : "");
-        }
-        if (superType != null) {
-            this.superClass = debugInfoBase.lookupClassEntry(superType);
+    public void addMethod(MethodEntry methodEntry) {
+        if (!methods.contains(methodEntry)) {
+            addFile(methodEntry.getFileEntry());
+            methods.add(methodEntry);
         }
-        String loaderName = debugInstanceTypeInfo.loaderName();
-        if (!loaderName.isEmpty()) {
-            this.loader = debugInfoBase.ensureLoaderEntry(loaderName);
-        }
-        debugInstanceTypeInfo.interfaces().forEach(interfaceType -> processInterface(interfaceType, debugInfoBase, debugContext));
-        /* Add details of fields and field types */
-        debugInstanceTypeInfo.fieldInfoProvider().forEach(debugFieldInfo -> this.processField(debugFieldInfo, debugInfoBase, debugContext));
-        /* Add details of methods and method types */
-        debugInstanceTypeInfo.methodInfoProvider().forEach(debugMethodInfo -> this.processMethod(debugMethodInfo, debugInfoBase, debugContext));
     }
 
-    public CompiledMethodEntry indexPrimary(PrimaryRange primary, List<DebugFrameSizeChange> frameSizeInfos, int frameSize) {
-        assert compiledMethodIndex.get(primary) == null : "repeat of primary range [0x%x, 0x%x]!".formatted(primary.getLo(), primary.getHi());
-        CompiledMethodEntry compiledEntry = new CompiledMethodEntry(primary, frameSizeInfos, frameSize, this);
-        compiledMethodIndex.put(primary, compiledEntry);
-        compiledEntries.add(compiledEntry);
-        return compiledEntry;
+    public void addCompiledMethod(CompiledMethodEntry compiledMethodEntry) {
+        addFile(compiledMethodEntry.primary().getFileEntry());
+        compiledMethodEntry.topDownRangeStream().forEach(subRange -> addFile(subRange.getFileEntry()));
+        compiledMethods.add(compiledMethodEntry);
     }
 
-    public void indexSubRange(SubRange subrange) {
-        Range primary = subrange.getPrimary();
-        /* The subrange should belong to a primary range. */
-        assert primary != null;
-        CompiledMethodEntry compiledEntry = compiledMethodIndex.get(primary);
-        /* We should already have seen the primary range. */
-        assert compiledEntry != null;
-        assert compiledEntry.getClassEntry() == this;
+    public void addFile(FileEntry fileEntry) {
+        if (fileEntry != null && !fileEntry.fileName().isEmpty() && !files.contains(fileEntry)) {
+            files.add(fileEntry);
+            addDir(fileEntry.dirEntry());
+        }
+    }
+
+    public void addDir(DirEntry dirEntry) {
+        if (dirEntry != null && !dirEntry.getPathString().isEmpty() && !dirs.contains(dirEntry)) {
+            dirs.add(dirEntry);
+        }
     }
 
-    private void indexMethodEntry(MethodEntry methodEntry, ResolvedJavaMethod idMethod) {
-        assert methodsIndex.get(idMethod) == null : methodEntry.getSymbolName();
-        methods.add(methodEntry);
-        methodsIndex.put(idMethod, methodEntry);
+    @Override
+    public boolean isInstance() {
+        return true;
     }
 
     public String getFileName() {
         if (fileEntry != null) {
-            return fileEntry.getFileName();
+            return fileEntry.fileName();
         } else {
             return "";
         }
@@ -208,17 +163,17 @@ public int getFileIdx() {
     }
 
     public int getFileIdx(FileEntry file) {
-        if (file == null || fileIndex == null) {
+        if (file == null || files.isEmpty()) {
             return 0;
         }
-        return fileIndex.get(file);
+        return files.indexOf(file) + 1;
     }
 
     public DirEntry getDirEntry(FileEntry file) {
         if (file == null) {
             return null;
         }
-        return file.getDirEntry();
+        return file.dirEntry();
     }
 
     public int getDirIdx(FileEntry file) {
@@ -227,126 +182,37 @@ public int getDirIdx(FileEntry file) {
     }
 
     public int getDirIdx(DirEntry dir) {
-        if (dir == null || dir.getPathString().isEmpty() || dirIndex == null) {
+        if (dir == null || dir.getPathString().isEmpty() || dirs.isEmpty()) {
             return 0;
         }
-        return dirIndex.get(dir);
+        return dirs.indexOf(dir) + 1;
     }
 
     public String getLoaderId() {
-        return (loader != null ? loader.getLoaderId() : "");
+        return (loader != null ? loader.loaderId() : "");
     }
 
     /**
-     * Retrieve a stream of all compiled method entries for this class.
+     * Retrieve a list of all compiled method entries for this class.
      *
-     * @return a stream of all compiled method entries for this class.
+     * @return a list of all compiled method entries for this class.
      */
-    public Stream<CompiledMethodEntry> compiledEntries() {
-        return compiledEntries.stream();
+    public List<CompiledMethodEntry> compiledMethods() {
+        return Collections.unmodifiableList(compiledMethods);
     }
 
-    protected void processInterface(ResolvedJavaType interfaceType, DebugInfoBase debugInfoBase, DebugContext debugContext) {
-        if (debugContext.isLogEnabled()) {
-            debugContext.log("typename %s adding interface %s%n", typeName, interfaceType.toJavaName());
-        }
-        ClassEntry entry = debugInfoBase.lookupClassEntry(interfaceType);
-        assert entry instanceof InterfaceClassEntry || (entry instanceof ForeignTypeEntry && this instanceof ForeignTypeEntry);
-        InterfaceClassEntry interfaceClassEntry = (InterfaceClassEntry) entry;
-        interfaces.add(interfaceClassEntry);
-        interfaceClassEntry.addImplementor(this, debugContext);
-    }
-
-    protected MethodEntry processMethod(DebugMethodInfo debugMethodInfo, DebugInfoBase debugInfoBase, DebugContext debugContext) {
-        String methodName = debugMethodInfo.name();
-        int line = debugMethodInfo.line();
-        ResolvedJavaType resultType = debugMethodInfo.valueType();
-        int modifiers = debugMethodInfo.modifiers();
-        DebugLocalInfo[] paramInfos = debugMethodInfo.getParamInfo();
-        DebugLocalInfo thisParam = debugMethodInfo.getThisParamInfo();
-        int paramCount = paramInfos.length;
-        if (debugContext.isLogEnabled()) {
-            String resultTypeName = resultType.toJavaName();
-            debugContext.log("typename %s adding %s method %s %s(%s)%n",
-                    typeName, memberModifiers(modifiers), resultTypeName, methodName, formatParams(paramInfos));
-        }
-        TypeEntry resultTypeEntry = debugInfoBase.lookupTypeEntry(resultType);
-        TypeEntry[] typeEntries = new TypeEntry[paramCount];
-        for (int i = 0; i < paramCount; i++) {
-            typeEntries[i] = debugInfoBase.lookupTypeEntry(paramInfos[i].valueType());
-        }
-        /*
-         * n.b. the method file may differ from the owning class file when the method is a
-         * substitution
-         */
-        FileEntry methodFileEntry = debugInfoBase.ensureFileEntry(debugMethodInfo);
-        MethodEntry methodEntry = new MethodEntry(debugInfoBase, debugMethodInfo, methodFileEntry, line, methodName,
-                this, resultTypeEntry, typeEntries, paramInfos, thisParam);
-        indexMethodEntry(methodEntry, debugMethodInfo.idMethod());
-
-        return methodEntry;
-    }
-
-    @Override
-    protected FieldEntry addField(DebugFieldInfo debugFieldInfo, DebugInfoBase debugInfoBase, DebugContext debugContext) {
-        FieldEntry fieldEntry = super.addField(debugFieldInfo, debugInfoBase, debugContext);
-        return fieldEntry;
-    }
-
-    private static String formatParams(DebugLocalInfo[] paramInfo) {
-        if (paramInfo.length == 0) {
-            return "";
-        }
-        StringBuilder builder = new StringBuilder();
-        for (int i = 0; i < paramInfo.length; i++) {
-            if (i > 0) {
-                builder.append(", ");
-            }
-            builder.append(paramInfo[i].typeName());
-            builder.append(' ');
-            builder.append(paramInfo[i].name());
-        }
-
-        return builder.toString();
-    }
-
-    public int compiledEntryCount() {
-        return compiledEntries.size();
-    }
-
-    public boolean hasCompiledEntries() {
-        return compiledEntryCount() != 0;
-    }
-
-    public long compiledEntriesBase() {
-        assert hasCompiledEntries();
-        return compiledEntries.get(0).getPrimary().getLo();
+    public boolean hasCompiledMethods() {
+        return !compiledMethods.isEmpty();
     }
 
     public ClassEntry getSuperClass() {
         return superClass;
     }
 
-    public MethodEntry ensureMethodEntryForDebugRangeInfo(DebugRangeInfo debugRangeInfo, DebugInfoBase debugInfoBase, DebugContext debugContext) {
-
-        MethodEntry methodEntry = methodsIndex.get(debugRangeInfo.idMethod());
-        if (methodEntry == null) {
-            methodEntry = processMethod(debugRangeInfo, debugInfoBase, debugContext);
-        } else {
-            methodEntry.updateRangeInfo(debugInfoBase, debugRangeInfo);
-        }
-        return methodEntry;
-    }
-
     public List<MethodEntry> getMethods() {
-        return methods;
+        return Collections.unmodifiableList(methods);
     }
 
-    /*
-     * Accessors for lo and hi bounds of this class's compiled method code ranges. See comments in
-     * class DebugInfoBase for an explanation of the layout of compiled method code.
-     */
-
     /**
      * Retrieve the lowest code section offset for compiled method code belonging to this class. It
      * is an error to call this for a class entry which has no compiled methods.
@@ -354,8 +220,8 @@ public List<MethodEntry> getMethods() {
      * @return the lowest code section offset for compiled method code belonging to this class
      */
     public long lowpc() {
-        assert hasCompiledEntries();
-        return compiledEntries.get(0).getPrimary().getLo();
+        assert hasCompiledMethods();
+        return compiledMethods.stream().map(CompiledMethodEntry::primary).mapToLong(Range::getLo).min().orElse(0);
     }
 
     /**
@@ -366,56 +232,8 @@ public long lowpc() {
      * @return the highest code section offset for compiled method code belonging to this class
      */
     public long hipc() {
-        assert hasCompiledEntries();
-        return compiledEntries.get(compiledEntries.size() - 1).getPrimary().getHi();
-    }
-
-    /**
-     * Add a file to the list of files referenced from info associated with this class.
-     *
-     * @param file The file to be added.
-     */
-    public void includeFile(FileEntry file) {
-        assert !files.contains(file) : "caller should ensure file is only included once";
-        assert fileIndex == null : "cannot include files after index has been created";
-        files.add(file);
-    }
-
-    /**
-     * Add a directory to the list of firectories referenced from info associated with this class.
-     *
-     * @param dirEntry The directory to be added.
-     */
-    public void includeDir(DirEntry dirEntry) {
-        assert !dirs.contains(dirEntry) : "caller should ensure dir is only included once";
-        assert dirIndex == null : "cannot include dirs after index has been created";
-        dirs.add(dirEntry);
-    }
-
-    /**
-     * Populate the file and directory indexes that track positions in the file and dir tables for
-     * this class's line info section.
-     */
-    public void buildFileAndDirIndexes() {
-        // this is a one-off operation
-        assert fileIndex == null && dirIndex == null : "file and indexes can only be generated once";
-        if (files.isEmpty()) {
-            assert dirs.isEmpty() : "should not have included any dirs if we have no files";
-        }
-        int idx = 1;
-        fileIndex = EconomicMap.create(files.size());
-        for (FileEntry file : files) {
-            fileIndex.put(file, idx++);
-        }
-        dirIndex = EconomicMap.create(dirs.size());
-        idx = 1;
-        for (DirEntry dir : dirs) {
-            if (!dir.getPathString().isEmpty()) {
-                dirIndex.put(dir, idx++);
-            } else {
-                assert idx == 1;
-            }
-        }
+        assert hasCompiledMethods();
+        return compiledMethods.stream().map(CompiledMethodEntry::primary).mapToLong(Range::getHi).max().orElse(0);
     }
 
     /**
@@ -424,12 +242,8 @@ public void buildFileAndDirIndexes() {
      *
      * @return a stream of all referenced files
      */
-    public Stream<FileEntry> fileStream() {
-        if (!files.isEmpty()) {
-            return files.stream();
-        } else {
-            return Stream.empty();
-        }
+    public List<FileEntry> getFiles() {
+        return Collections.unmodifiableList(files);
     }
 
     /**
@@ -438,11 +252,7 @@ public Stream<FileEntry> fileStream() {
      *
      * @return a stream of all referenced directories
      */
-    public Stream<DirEntry> dirStream() {
-        if (!dirs.isEmpty()) {
-            return dirs.stream();
-        } else {
-            return Stream.empty();
-        }
+    public List<DirEntry> getDirs() {
+        return Collections.unmodifiableList(dirs);
     }
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/CompiledMethodEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/CompiledMethodEntry.java
index b8d34393677a..ef8e74d6f430 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/CompiledMethodEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/CompiledMethodEntry.java
@@ -27,138 +27,41 @@
 package com.oracle.objectfile.debugentry;
 
 import com.oracle.objectfile.debugentry.range.PrimaryRange;
-import com.oracle.objectfile.debugentry.range.SubRange;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFrameSizeChange;
+import com.oracle.objectfile.debugentry.range.Range;
 
-import java.util.ArrayDeque;
-import java.util.Iterator;
 import java.util.List;
+import java.util.stream.Stream;
 
 /**
  * Tracks debug info associated with a top level compiled method.
+ *
+ * @param primary        The primary range detailed by this object.
+ * @param classEntry     Details of the class owning this range.
+ * @param frameSizeInfos Details of compiled method frame size changes.
+ * @param frameSize      Size of compiled method frame.
  */
-public class CompiledMethodEntry {
-    /**
-     * The primary range detailed by this object.
-     */
-    private final PrimaryRange primary;
-    /**
-     * Details of the class owning this range.
-     */
-    private final ClassEntry classEntry;
-    /**
-     * Details of of compiled method frame size changes.
-     */
-    private final List<DebugFrameSizeChange> frameSizeInfos;
-    /**
-     * Size of compiled method frame.
-     */
-    private final int frameSize;
-
-    public CompiledMethodEntry(PrimaryRange primary, List<DebugFrameSizeChange> frameSizeInfos, int frameSize, ClassEntry classEntry) {
-        this.primary = primary;
-        this.classEntry = classEntry;
-        this.frameSizeInfos = frameSizeInfos;
-        this.frameSize = frameSize;
-    }
-
-    public PrimaryRange getPrimary() {
-        return primary;
-    }
-
-    public ClassEntry getClassEntry() {
-        return classEntry;
-    }
+public record CompiledMethodEntry(PrimaryRange primary, List<FrameSizeChangeEntry> frameSizeInfos, int frameSize,
+                                  ClassEntry classEntry) {
 
     /**
-     * Returns an iterator that traverses all the callees of the method associated with this entry.
-     * The iterator performs a depth-first pre-order traversal of the call tree.
+     * Returns a stream that traverses all the callees of the method associated with this entry.
+     * The stream performs a depth-first pre-order traversal of the call tree.
      *
      * @return the iterator
      */
-    public Iterator<SubRange> topDownRangeIterator() {
-        return new Iterator<>() {
-            final ArrayDeque<SubRange> workStack = new ArrayDeque<>();
-            SubRange current = primary.getFirstCallee();
-
-            @Override
-            public boolean hasNext() {
-                return current != null;
-            }
-
-            @Override
-            public SubRange next() {
-                assert hasNext();
-                SubRange result = current;
-                forward();
-                return result;
-            }
-
-            private void forward() {
-                SubRange sibling = current.getSiblingCallee();
-                assert sibling == null || (current.getHi() <= sibling.getLo()) : current.getHi() + " > " + sibling.getLo();
-                if (!current.isLeaf()) {
-                    /* save next sibling while we process the children */
-                    if (sibling != null) {
-                        workStack.push(sibling);
-                    }
-                    current = current.getFirstCallee();
-                } else if (sibling != null) {
-                    current = sibling;
-                } else {
-                    /*
-                     * Return back up to parents' siblings, use pollFirst instead of pop to return
-                     * null in case the work stack is empty
-                     */
-                    current = workStack.pollFirst();
-                }
-            }
-        };
+    public Stream<Range> topDownRangeStream() {
+        // skip the root of the range stream which is the primary range
+        return primary.rangeStream().skip(1);
     }
 
     /**
-     * Returns an iterator that traverses the callees of the method associated with this entry and
-     * returns only the leafs. The iterator performs a depth-first pre-order traversal of the call
+     * Returns a stream that traverses the callees of the method associated with this entry and
+     * returns only the leafs. The stream performs a depth-first pre-order traversal of the call
      * tree returning only ranges with no callees.
      *
      * @return the iterator
      */
-    public Iterator<SubRange> leafRangeIterator() {
-        final Iterator<SubRange> iter = topDownRangeIterator();
-        return new Iterator<>() {
-            SubRange current = forwardLeaf(iter);
-
-            @Override
-            public boolean hasNext() {
-                return current != null;
-            }
-
-            @Override
-            public SubRange next() {
-                assert hasNext();
-                SubRange result = current;
-                current = forwardLeaf(iter);
-                return result;
-            }
-
-            private SubRange forwardLeaf(Iterator<SubRange> t) {
-                if (t.hasNext()) {
-                    SubRange next = t.next();
-                    while (next != null && !next.isLeaf()) {
-                        next = t.next();
-                    }
-                    return next;
-                }
-                return null;
-            }
-        };
-    }
-
-    public List<DebugFrameSizeChange> getFrameSizeInfos() {
-        return frameSizeInfos;
-    }
-
-    public int getFrameSize() {
-        return frameSize;
+    public Stream<Range> leafRangeStream() {
+        return topDownRangeStream().filter(Range::isLeaf);
     }
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java
index d22dedee11f7..72d2fb58a731 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java
@@ -27,29 +27,12 @@
 package com.oracle.objectfile.debugentry;
 
 import java.nio.ByteOrder;
-import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
-import org.graalvm.collections.EconomicMap;
-import org.graalvm.collections.EconomicSet;
-
-import com.oracle.objectfile.debugentry.range.PrimaryRange;
-import com.oracle.objectfile.debugentry.range.Range;
-import com.oracle.objectfile.debugentry.range.SubRange;
 import com.oracle.objectfile.debuginfo.DebugInfoProvider;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugCodeInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFileInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalValueInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocationInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind;
-import com.oracle.objectfile.elf.dwarf.DwarfDebugInfo;
 
 import jdk.graal.compiler.debug.DebugContext;
-import jdk.vm.ci.meta.ResolvedJavaType;
 
 /**
  * An abstract class which indexes the information presented by the DebugInfoProvider in an
@@ -93,36 +76,26 @@ public abstract class DebugInfoBase {
      * A table listing all known strings, some of which may be marked for insertion into the
      * debug_str section.
      */
-    private final StringTable stringTable = new StringTable();
-    /**
-     * List of dirs in which files are found to reside.
-     */
-    private final List<DirEntry> dirs = new ArrayList<>();
-    /**
-     * Index of all dirs in which files are found to reside either as part of substrate/compiler or
-     * user code.
-     */
-    private final EconomicMap<Path, DirEntry> dirsIndex = EconomicMap.create();
+    private StringTable stringTable;
 
     /**
      * List of all types present in the native image including instance classes, array classes,
      * primitive types and the one-off Java header struct.
      */
     private final List<TypeEntry> types = new ArrayList<>();
-    /**
-     * Index of already seen types keyed by the unique, associated, identifying ResolvedJavaType or,
-     * in the single special case of the TypeEntry for the Java header structure, by key null.
-     */
-    private final Map<ResolvedJavaType, TypeEntry> typesIndex = new HashMap<>();
     /**
      * List of all instance classes found in debug info. These classes do not necessarily have top
      * level or inline compiled methods. This list includes interfaces and enum types.
      */
     private final List<ClassEntry> instanceClasses = new ArrayList<>();
-    /**
-     * Index of already seen classes.
-     */
-    private final EconomicMap<ResolvedJavaType, ClassEntry> instanceClassesIndex = EconomicMap.create();
+
+    private final List<ClassEntry> instanceClassesWithCompilation = new ArrayList<>();
+
+
+    private final List<PrimitiveTypeEntry> primitiveTypes = new ArrayList<>();
+
+
+    private final List<ArrayTypeEntry> arrayTypes = new ArrayList<>();
     /**
      * Handle on type entry for header structure.
      */
@@ -140,30 +113,14 @@ public abstract class DebugInfoBase {
      * debug info API in ascending address range order.
      */
     private final List<CompiledMethodEntry> compiledMethods = new ArrayList<>();
-    /**
-     * List of of files which contain primary or secondary ranges.
-     */
-    private final List<FileEntry> files = new ArrayList<>();
-    /**
-     * Index of files which contain primary or secondary ranges keyed by path.
-     */
-    private final EconomicMap<Path, FileEntry> filesIndex = EconomicMap.create();
-
-    /**
-     * List of all loaders associated with classes included in the image.
-     */
-    private final List<LoaderEntry> loaders = new ArrayList<>();
-
-    /**
-     * Index of all loaders associated with classes included in the image.
-     */
-    private final EconomicMap<String, LoaderEntry> loaderIndex = EconomicMap.create();
 
     /**
      * Flag set to true if heap references are stored as addresses relative to a heap base register
      * otherwise false.
      */
     private boolean useHeapBase;
+
+    private boolean isRuntimeCompilation;
     /**
      * Number of bits oops are left shifted by when using compressed oops.
      */
@@ -207,7 +164,23 @@ public abstract class DebugInfoBase {
      */
     private ClassEntry hubClassEntry;
 
-    @SuppressWarnings("this-escape")
+    /*
+     * A prefix used to label indirect types used to ensure gdb performs oop reference --> raw
+     * address translation
+     */
+    public static final String INDIRECT_PREFIX = "_z_.";
+    /*
+     * A prefix used for type signature generation to generate unique type signatures for type
+     * layout type units
+     */
+    public static final String LAYOUT_PREFIX = "_layout_.";
+    /*
+     * The name of the type for header field hub which needs special case processing to remove tag
+     * bits
+     */
+    public static final String HUB_TYPE_NAME = "java.lang.Class";
+
+
     public DebugInfoBase(ByteOrder byteOrder) {
         this.byteOrder = byteOrder;
         this.useHeapBase = true;
@@ -220,8 +193,6 @@ public DebugInfoBase(ByteOrder byteOrder) {
         this.numAlignmentBits = 0;
         this.hubClassEntry = null;
         this.compiledCodeMax = 0;
-        // create and index an empty dir with index 0.
-        ensureDirEntry(EMPTY_PATH);
     }
 
     public int compiledCodeMax() {
@@ -230,23 +201,18 @@ public int compiledCodeMax() {
 
     /**
      * Entry point allowing ELFObjectFile to pass on information about types, code and heap data.
-     *
-     * @param debugInfoProvider provider instance passed by ObjectFile client.
      */
     @SuppressWarnings("try")
     public void installDebugInfo(DebugInfoProvider debugInfoProvider) {
-        /*
-         * This will be needed once we add support for type info:
-         *
-         * DebugTypeInfoProvider typeInfoProvider = debugInfoProvider.typeInfoProvider(); for
-         * (DebugTypeInfo debugTypeInfo : typeInfoProvider) { install types }
-         */
+        debugInfoProvider.installDebugInfo();
 
         /*
          * Track whether we need to use a heap base register.
          */
         useHeapBase = debugInfoProvider.useHeapBase();
 
+        this.isRuntimeCompilation = debugInfoProvider.isRuntimeCompilation();
+
         /*
          * Save count of low order tag bits that may appear in references.
          */
@@ -281,184 +247,35 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) {
         /* retrieve limit for Java code address range */
         compiledCodeMax = debugInfoProvider.compiledCodeMax();
 
-        /* Ensure we have a null string and cachePath in the string section. */
-        String uniqueNullString = stringTable.uniqueDebugString("");
-        if (debugInfoProvider.getCachePath() != null) {
-            cachePath = stringTable.uniqueDebugString(debugInfoProvider.getCachePath().toString());
-        } else {
-            cachePath = uniqueNullString; // fall back to null string
-        }
-
-        /* Create all the types. */
-        debugInfoProvider.typeInfoProvider().forEach(debugTypeInfo -> debugTypeInfo.debugContext((debugContext) -> {
-            ResolvedJavaType idType = debugTypeInfo.idType();
-            String typeName = debugTypeInfo.typeName();
-            typeName = stringTable.uniqueDebugString(typeName);
-            DebugTypeKind typeKind = debugTypeInfo.typeKind();
-            int byteSize = debugTypeInfo.size();
-
-            if (debugContext.isLogEnabled(DebugContext.INFO_LEVEL)) {
-                debugContext.log(DebugContext.INFO_LEVEL, "Register %s type %s ", typeKind.toString(), typeName);
-            }
-            String fileName = debugTypeInfo.fileName();
-            Path filePath = debugTypeInfo.filePath();
-            addTypeEntry(idType, typeName, fileName, filePath, byteSize, typeKind);
-        }));
-        debugInfoProvider.recordActivity();
-
-        /* Now we can cross reference static and instance field details. */
-        debugInfoProvider.typeInfoProvider().forEach(debugTypeInfo -> debugTypeInfo.debugContext((debugContext) -> {
-            ResolvedJavaType idType = debugTypeInfo.idType();
-            String typeName = debugTypeInfo.typeName();
-            DebugTypeKind typeKind = debugTypeInfo.typeKind();
-
-            if (debugContext.isLogEnabled(DebugContext.INFO_LEVEL)) {
-                debugContext.log(DebugContext.INFO_LEVEL, "Process %s type %s ", typeKind.toString(), typeName);
-            }
-            TypeEntry typeEntry = (idType != null ? lookupTypeEntry(idType) : lookupHeaderType());
-            typeEntry.addDebugInfo(this, debugTypeInfo, debugContext);
-        }));
-        debugInfoProvider.recordActivity();
-
-        debugInfoProvider.codeInfoProvider().forEach(debugCodeInfo -> debugCodeInfo.debugContext((debugContext) -> {
-            /*
-             * Primary file name and full method name need to be written to the debug_str section.
-             */
-            String fileName = debugCodeInfo.fileName();
-            Path filePath = debugCodeInfo.filePath();
-            ResolvedJavaType ownerType = debugCodeInfo.ownerType();
-            String methodName = debugCodeInfo.name();
-            int lo = debugCodeInfo.addressLo();
-            int hi = debugCodeInfo.addressHi();
-            int primaryLine = debugCodeInfo.line();
-
-            /* Search for a method defining this primary range. */
-            ClassEntry classEntry = lookupClassEntry(ownerType);
-            MethodEntry methodEntry = classEntry.ensureMethodEntryForDebugRangeInfo(debugCodeInfo, this, debugContext);
-            PrimaryRange primaryRange = Range.createPrimary(methodEntry, lo, hi, primaryLine);
-            if (debugContext.isLogEnabled(DebugContext.INFO_LEVEL)) {
-                debugContext.log(DebugContext.INFO_LEVEL, "PrimaryRange %s.%s %s %s:%d [0x%x, 0x%x]", ownerType.toJavaName(), methodName, filePath, fileName, primaryLine, lo, hi);
-            }
-            addPrimaryRange(primaryRange, debugCodeInfo, classEntry);
-            /*
-             * Record all subranges even if they have no line or file so we at least get a symbol
-             * for them and don't see a break in the address range.
-             */
-            EconomicMap<DebugLocationInfo, SubRange> subRangeIndex = EconomicMap.create();
-            debugCodeInfo.locationInfoProvider().forEach(debugLocationInfo -> addSubrange(debugLocationInfo, primaryRange, classEntry, subRangeIndex, debugContext));
-            debugInfoProvider.recordActivity();
-        }));
-
-        debugInfoProvider.dataInfoProvider().forEach(debugDataInfo -> debugDataInfo.debugContext((debugContext) -> {
-            if (debugContext.isLogEnabled(DebugContext.INFO_LEVEL)) {
-                String provenance = debugDataInfo.getProvenance();
-                String typeName = debugDataInfo.getTypeName();
-                String partitionName = debugDataInfo.getPartition();
-                /* Offset is relative to heap-base register. */
-                long offset = debugDataInfo.getOffset();
-                long size = debugDataInfo.getSize();
-                debugContext.log(DebugContext.INFO_LEVEL, "Data: offset 0x%x size 0x%x type %s partition %s provenance %s ", offset, size, typeName, partitionName, provenance);
-            }
-        }));
-        // populate a file and dir list and associated index for each class entry
-        getInstanceClasses().forEach(classEntry -> {
-            collectFilesAndDirs(classEntry);
-        });
-    }
+        stringTable = debugInfoProvider.getStringTable();
 
-    private TypeEntry createTypeEntry(String typeName, String fileName, Path filePath, int size, DebugTypeKind typeKind) {
-        TypeEntry typeEntry = null;
-        switch (typeKind) {
-            case INSTANCE: {
-                FileEntry fileEntry = addFileEntry(fileName, filePath);
-                typeEntry = new ClassEntry(typeName, fileEntry, size);
-                if (typeEntry.getTypeName().equals(DwarfDebugInfo.HUB_TYPE_NAME)) {
-                    hubClassEntry = (ClassEntry) typeEntry;
-                }
-                break;
-            }
-            case INTERFACE: {
-                FileEntry fileEntry = addFileEntry(fileName, filePath);
-                typeEntry = new InterfaceClassEntry(typeName, fileEntry, size);
-                break;
-            }
-            case ENUM: {
-                FileEntry fileEntry = addFileEntry(fileName, filePath);
-                typeEntry = new EnumClassEntry(typeName, fileEntry, size);
-                break;
-            }
-            case PRIMITIVE:
-                assert fileName.length() == 0;
-                assert filePath == null;
-                typeEntry = new PrimitiveTypeEntry(typeName, size);
-                break;
-            case ARRAY:
-                assert fileName.length() == 0;
-                assert filePath == null;
-                typeEntry = new ArrayTypeEntry(typeName, size);
-                break;
-            case HEADER:
-                assert fileName.length() == 0;
-                assert filePath == null;
-                typeEntry = new HeaderTypeEntry(typeName, size);
-                break;
-            case FOREIGN: {
-                FileEntry fileEntry = addFileEntry(fileName, filePath);
-                typeEntry = new ForeignTypeEntry(typeName, fileEntry, size);
-                break;
-            }
-        }
-        return typeEntry;
-    }
+        cachePath = debugInfoProvider.cachePath();
 
-    private TypeEntry addTypeEntry(ResolvedJavaType idType, String typeName, String fileName, Path filePath, int size, DebugTypeKind typeKind) {
-        TypeEntry typeEntry = (idType != null ? typesIndex.get(idType) : null);
-        if (typeEntry == null) {
-            typeEntry = createTypeEntry(typeName, fileName, filePath, size, typeKind);
+        compiledMethods.addAll(debugInfoProvider.compiledMethodEntries());
+        debugInfoProvider.typeEntries().forEach(typeEntry -> {
             types.add(typeEntry);
-            if (idType != null) {
-                typesIndex.put(idType, typeEntry);
-            }
-            // track object type and header struct
-            if (idType == null) {
-                headerType = (HeaderTypeEntry) typeEntry;
-            }
-            if (typeName.equals("java.lang.Object")) {
-                objectClass = (ClassEntry) typeEntry;
-            }
-            if (typeName.equals("void")) {
+            if (typeEntry.getTypeName().equals("void")) {
                 voidType = typeEntry;
             }
-            if (typeEntry instanceof ClassEntry) {
-                indexInstanceClass(idType, (ClassEntry) typeEntry);
-            }
-        } else {
-            if (!(typeEntry.isClass())) {
-                assert ((ClassEntry) typeEntry).getFileName().equals(fileName);
+            switch (typeEntry) {
+                case ArrayTypeEntry arrayTypeEntry -> arrayTypes.add(arrayTypeEntry);
+                case PrimitiveTypeEntry primitiveTypeEntry -> primitiveTypes.add(primitiveTypeEntry);
+                case HeaderTypeEntry headerTypeEntry -> headerType = headerTypeEntry;
+                case ClassEntry classEntry -> {
+                    instanceClasses.add(classEntry);
+                    if (classEntry.hasCompiledMethods()) {
+                        instanceClassesWithCompilation.add(classEntry);
+                    }
+                    if (classEntry.getTypeName().equals("java.lang.Object")) {
+                        objectClass = classEntry;
+                    } else if (classEntry.getTypeName().equals(HUB_TYPE_NAME)) {
+                        hubClassEntry = classEntry;
+                    }
+                }
+                default -> {
+                }
             }
-        }
-        return typeEntry;
-    }
-
-    public TypeEntry lookupTypeEntry(ResolvedJavaType type) {
-        TypeEntry typeEntry = typesIndex.get(type);
-        if (typeEntry == null) {
-            throw new RuntimeException("Type entry not found " + type.getName());
-        }
-        return typeEntry;
-    }
-
-    ClassEntry lookupClassEntry(ResolvedJavaType type) {
-        // lookup key should advertise itself as a resolved instance class or interface
-        assert type.isInstanceClass() || type.isInterface();
-        // lookup target should already be included in the index
-        ClassEntry classEntry = instanceClassesIndex.get(type);
-        if (classEntry == null || !(classEntry.isClass())) {
-            throw new RuntimeException("Class entry not found " + type.getName());
-        }
-        // lookup target should also be indexed in the types index
-        assert typesIndex.get(type) != null;
-        return classEntry;
+        });
     }
 
     public HeaderTypeEntry lookupHeaderType() {
@@ -479,165 +296,6 @@ public ClassEntry lookupObjectClass() {
         return objectClass;
     }
 
-    private void addPrimaryRange(PrimaryRange primaryRange, DebugCodeInfo debugCodeInfo, ClassEntry classEntry) {
-        CompiledMethodEntry compiledMethod = classEntry.indexPrimary(primaryRange, debugCodeInfo.getFrameSizeChanges(), debugCodeInfo.getFrameSize());
-        indexCompiledMethod(compiledMethod);
-    }
-
-    /**
-     * Recursively creates subranges based on DebugLocationInfo including, and appropriately
-     * linking, nested inline subranges.
-     *
-     * @param locationInfo
-     * @param primaryRange
-     * @param classEntry
-     * @param subRangeIndex
-     * @param debugContext
-     * @return the subrange for {@code locationInfo} linked with all its caller subranges up to the
-     *         primaryRange
-     */
-    @SuppressWarnings("try")
-    private Range addSubrange(DebugLocationInfo locationInfo, PrimaryRange primaryRange, ClassEntry classEntry, EconomicMap<DebugLocationInfo, SubRange> subRangeIndex, DebugContext debugContext) {
-        /*
-         * We still insert subranges for the primary method but they don't actually count as inline.
-         * we only need a range so that subranges for inline code can refer to the top level line
-         * number.
-         */
-        DebugLocationInfo callerLocationInfo = locationInfo.getCaller();
-        boolean isTopLevel = callerLocationInfo == null;
-        assert (!isTopLevel || (locationInfo.name().equals(primaryRange.getMethodName()) &&
-                locationInfo.ownerType().toJavaName().equals(primaryRange.getClassName())));
-        Range caller = (isTopLevel ? primaryRange : subRangeIndex.get(callerLocationInfo));
-        // the frame tree is walked topdown so inline ranges should always have a caller range
-        assert caller != null;
-
-        final String fileName = locationInfo.fileName();
-        final Path filePath = locationInfo.filePath();
-        final String fullPath = (filePath == null ? "" : filePath.toString() + "/") + fileName;
-        final ResolvedJavaType ownerType = locationInfo.ownerType();
-        final String methodName = locationInfo.name();
-        final int loOff = locationInfo.addressLo();
-        final int hiOff = locationInfo.addressHi() - 1;
-        final int lo = primaryRange.getLo() + locationInfo.addressLo();
-        final int hi = primaryRange.getLo() + locationInfo.addressHi();
-        final int line = locationInfo.line();
-        ClassEntry subRangeClassEntry = lookupClassEntry(ownerType);
-        MethodEntry subRangeMethodEntry = subRangeClassEntry.ensureMethodEntryForDebugRangeInfo(locationInfo, this, debugContext);
-        SubRange subRange = Range.createSubrange(subRangeMethodEntry, lo, hi, line, primaryRange, caller, locationInfo.isLeaf());
-        classEntry.indexSubRange(subRange);
-        subRangeIndex.put(locationInfo, subRange);
-        if (debugContext.isLogEnabled(DebugContext.DETAILED_LEVEL)) {
-            debugContext.log(DebugContext.DETAILED_LEVEL, "SubRange %s.%s %d %s:%d [0x%x, 0x%x] (%d, %d)",
-                    ownerType.toJavaName(), methodName, subRange.getDepth(), fullPath, line, lo, hi, loOff, hiOff);
-        }
-        assert (callerLocationInfo == null || (callerLocationInfo.addressLo() <= loOff && callerLocationInfo.addressHi() >= hiOff)) : "parent range should enclose subrange!";
-        DebugLocalValueInfo[] localValueInfos = locationInfo.getLocalValueInfo();
-        for (int i = 0; i < localValueInfos.length; i++) {
-            DebugLocalValueInfo localValueInfo = localValueInfos[i];
-            if (debugContext.isLogEnabled(DebugContext.DETAILED_LEVEL)) {
-                debugContext.log(DebugContext.DETAILED_LEVEL, "  locals[%d] %s:%s = %s", localValueInfo.slot(), localValueInfo.name(), localValueInfo.typeName(), localValueInfo);
-            }
-        }
-        subRange.setLocalValueInfo(localValueInfos);
-        return subRange;
-    }
-
-    private void indexInstanceClass(ResolvedJavaType idType, ClassEntry classEntry) {
-        instanceClasses.add(classEntry);
-        instanceClassesIndex.put(idType, classEntry);
-    }
-
-    private void indexCompiledMethod(CompiledMethodEntry compiledMethod) {
-        assert verifyMethodOrder(compiledMethod);
-        compiledMethods.add(compiledMethod);
-    }
-
-    private boolean verifyMethodOrder(CompiledMethodEntry next) {
-        int size = compiledMethods.size();
-        if (size > 0) {
-            CompiledMethodEntry last = compiledMethods.get(size - 1);
-            PrimaryRange lastRange = last.getPrimary();
-            PrimaryRange nextRange = next.getPrimary();
-            if (lastRange.getHi() > nextRange.getLo()) {
-                assert false : "methods %s [0x%x, 0x%x] and %s [0x%x, 0x%x] presented out of order".formatted(lastRange.getFullMethodName(), lastRange.getLo(), lastRange.getHi(),
-                        nextRange.getFullMethodName(), nextRange.getLo(), nextRange.getHi());
-                return false;
-            }
-        }
-        return true;
-    }
-
-    static final Path EMPTY_PATH = Paths.get("");
-
-    private FileEntry addFileEntry(String fileName, Path filePath) {
-        assert fileName != null;
-        Path dirPath = filePath;
-        Path fileAsPath;
-        if (filePath != null) {
-            fileAsPath = dirPath.resolve(fileName);
-        } else {
-            fileAsPath = Paths.get(fileName);
-            dirPath = EMPTY_PATH;
-        }
-        FileEntry fileEntry = filesIndex.get(fileAsPath);
-        if (fileEntry == null) {
-            DirEntry dirEntry = ensureDirEntry(dirPath);
-            /* Ensure file and cachepath are added to the debug_str section. */
-            uniqueDebugString(fileName);
-            uniqueDebugString(cachePath);
-            fileEntry = new FileEntry(fileName, dirEntry);
-            files.add(fileEntry);
-            /* Index the file entry by file path. */
-            filesIndex.put(fileAsPath, fileEntry);
-        } else {
-            assert fileEntry.getDirEntry().getPath().equals(dirPath);
-        }
-        return fileEntry;
-    }
-
-    protected FileEntry ensureFileEntry(DebugFileInfo debugFileInfo) {
-        String fileName = debugFileInfo.fileName();
-        if (fileName == null || fileName.length() == 0) {
-            return null;
-        }
-        Path filePath = debugFileInfo.filePath();
-        Path fileAsPath;
-        if (filePath == null) {
-            fileAsPath = Paths.get(fileName);
-        } else {
-            fileAsPath = filePath.resolve(fileName);
-        }
-        /* Reuse any existing entry. */
-        FileEntry fileEntry = findFile(fileAsPath);
-        if (fileEntry == null) {
-            fileEntry = addFileEntry(fileName, filePath);
-        }
-        return fileEntry;
-    }
-
-    private DirEntry ensureDirEntry(Path filePath) {
-        if (filePath == null) {
-            return null;
-        }
-        DirEntry dirEntry = dirsIndex.get(filePath);
-        if (dirEntry == null) {
-            /* Ensure dir path is entered into the debug_str section. */
-            uniqueDebugString(filePath.toString());
-            dirEntry = new DirEntry(filePath);
-            dirsIndex.put(filePath, dirEntry);
-            dirs.add(dirEntry);
-        }
-        return dirEntry;
-    }
-
-    protected LoaderEntry ensureLoaderEntry(String loaderId) {
-        LoaderEntry loaderEntry = loaderIndex.get(loaderId);
-        if (loaderEntry == null) {
-            loaderEntry = new LoaderEntry(uniqueDebugString(loaderId));
-            loaderIndex.put(loaderEntry.getLoaderId(), loaderEntry);
-        }
-        return loaderEntry;
-    }
 
     /* Accessors to query the debug info model. */
     public ByteOrder getByteOrder() {
@@ -648,34 +306,24 @@ public List<TypeEntry> getTypes() {
         return types;
     }
 
-    public List<ClassEntry> getInstanceClasses() {
-        return instanceClasses;
+    public List<ArrayTypeEntry> getArrayTypes() {
+        return arrayTypes;
     }
 
-    public List<CompiledMethodEntry> getCompiledMethods() {
-        return compiledMethods;
+    public List<PrimitiveTypeEntry> getPrimitiveTypes() {
+        return primitiveTypes;
     }
 
-    public List<FileEntry> getFiles() {
-        return files;
-    }
-
-    public List<DirEntry> getDirs() {
-        return dirs;
-    }
-
-    @SuppressWarnings("unused")
-    public FileEntry findFile(Path fullFileName) {
-        return filesIndex.get(fullFileName);
+    public List<ClassEntry> getInstanceClasses() {
+        return instanceClasses;
     }
 
-    public List<LoaderEntry> getLoaders() {
-        return loaders;
+    public List<ClassEntry> getInstanceClassesWithCompilation() {
+        return instanceClassesWithCompilation;
     }
 
-    @SuppressWarnings("unused")
-    public LoaderEntry findLoader(String id) {
-        return loaderIndex.get(id);
+    public List<CompiledMethodEntry> getCompiledMethods() {
+        return compiledMethods;
     }
 
     public StringTable getStringTable() {
@@ -705,6 +353,10 @@ public boolean useHeapBase() {
         return useHeapBase;
     }
 
+    public boolean isRuntimeCompilation() {
+        return isRuntimeCompilation;
+    }
+
     public int reservedBitsMask() {
         return reservedBitsMask;
     }
@@ -738,67 +390,11 @@ public String getCachePath() {
     }
 
     public boolean isHubClassEntry(StructureTypeEntry typeEntry) {
-        return typeEntry.getTypeName().equals(DwarfDebugInfo.HUB_TYPE_NAME);
+        return typeEntry.getTypeName().equals(DebugInfoBase.HUB_TYPE_NAME);
     }
 
     public ClassEntry getHubClassEntry() {
         return hubClassEntry;
     }
 
-    private static void collectFilesAndDirs(ClassEntry classEntry) {
-        // track files and dirs we have already seen so that we only add them once
-        EconomicSet<FileEntry> visitedFiles = EconomicSet.create();
-        EconomicSet<DirEntry> visitedDirs = EconomicSet.create();
-        // add the class's file and dir
-        includeOnce(classEntry, classEntry.getFileEntry(), visitedFiles, visitedDirs);
-        // add files for fields (may differ from class file if we have a substitution)
-        for (FieldEntry fieldEntry : classEntry.fields) {
-            includeOnce(classEntry, fieldEntry.getFileEntry(), visitedFiles, visitedDirs);
-        }
-        // add files for declared methods (may differ from class file if we have a substitution)
-        for (MethodEntry methodEntry : classEntry.getMethods()) {
-            includeOnce(classEntry, methodEntry.getFileEntry(), visitedFiles, visitedDirs);
-        }
-        // add files for top level compiled and inline methods
-        classEntry.compiledEntries().forEachOrdered(compiledMethodEntry -> {
-            includeOnce(classEntry, compiledMethodEntry.getPrimary().getFileEntry(), visitedFiles, visitedDirs);
-            // we need files for leaf ranges and for inline caller ranges
-            //
-            // add leaf range files first because they get searched for linearly
-            // during line info processing
-            compiledMethodEntry.leafRangeIterator().forEachRemaining(subRange -> {
-                includeOnce(classEntry, subRange.getFileEntry(), visitedFiles, visitedDirs);
-            });
-            // now the non-leaf range files
-            compiledMethodEntry.topDownRangeIterator().forEachRemaining(subRange -> {
-                if (!subRange.isLeaf()) {
-                    includeOnce(classEntry, subRange.getFileEntry(), visitedFiles, visitedDirs);
-                }
-            });
-        });
-        // now all files and dirs are known build an index for them
-        classEntry.buildFileAndDirIndexes();
-    }
-
-    /**
-     * Ensure the supplied file entry and associated directory entry are included, but only once, in
-     * a class entry's file and dir list.
-     *
-     * @param classEntry the class entry whose file and dir list may need to be updated
-     * @param fileEntry a file entry which may need to be added to the class entry's file list or
-     *            whose dir may need adding to the class entry's dir list
-     * @param visitedFiles a set tracking current file list entries, updated if a file is added
-     * @param visitedDirs a set tracking current dir list entries, updated if a dir is added
-     */
-    private static void includeOnce(ClassEntry classEntry, FileEntry fileEntry, EconomicSet<FileEntry> visitedFiles, EconomicSet<DirEntry> visitedDirs) {
-        if (fileEntry != null && !visitedFiles.contains(fileEntry)) {
-            visitedFiles.add(fileEntry);
-            classEntry.includeFile(fileEntry);
-            DirEntry dirEntry = fileEntry.getDirEntry();
-            if (dirEntry != null && !dirEntry.getPathString().isEmpty() && !visitedDirs.contains(dirEntry)) {
-                visitedDirs.add(dirEntry);
-                classEntry.includeDir(dirEntry);
-            }
-        }
-    }
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DirEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DirEntry.java
index 619e37d26cba..3a0023d55fab 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DirEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DirEntry.java
@@ -30,21 +30,12 @@
 
 /**
  * Tracks the directory associated with one or more source files.
- *
+ * <p>
  * This is identified separately from each FileEntry identifying files that reside in the directory.
  * That is necessary because the line info generator needs to collect and write out directory names
  * into directory tables once only rather than once per file.
  */
-public class DirEntry {
-    private final Path path;
-
-    public DirEntry(Path path) {
-        this.path = path;
-    }
-
-    public Path getPath() {
-        return path;
-    }
+public record DirEntry(Path path) {
 
     public String getPathString() {
         return path.toString();
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/EnumClassEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/EnumClassEntry.java
index f46378d57486..898d62d6c4af 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/EnumClassEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/EnumClassEntry.java
@@ -26,25 +26,20 @@
 
 package com.oracle.objectfile.debugentry;
 
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugEnumTypeInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind;
-
-import jdk.graal.compiler.debug.DebugContext;
-
 public class EnumClassEntry extends ClassEntry {
-    public EnumClassEntry(String typeName, FileEntry fileEntry, int size) {
-        super(typeName, fileEntry, size);
+    public EnumClassEntry(String typeName, int size, long classOffset, long typeSignature,
+                          long compressedTypeSignature, long layoutTypeSignature, long compressedLayoutTypeSignature,
+                          ClassEntry superClass, FileEntry fileEntry, LoaderEntry loader) {
+        super(typeName, size, classOffset, typeSignature, compressedTypeSignature, layoutTypeSignature, compressedLayoutTypeSignature, superClass, fileEntry, loader);
     }
 
     @Override
-    public DebugTypeKind typeKind() {
-        return DebugTypeKind.ENUM;
+    public boolean isEnum() {
+        return true;
     }
 
     @Override
-    public void addDebugInfo(DebugInfoBase debugInfoBase, DebugTypeInfo debugTypeInfo, DebugContext debugContext) {
-        assert debugTypeInfo instanceof DebugEnumTypeInfo;
-        super.addDebugInfo(debugInfoBase, debugTypeInfo, debugContext);
+    public boolean isInstance() {
+        return false;
     }
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FieldEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FieldEntry.java
index f6e7c6a0cd40..24f725b5cdfa 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FieldEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FieldEntry.java
@@ -27,12 +27,13 @@
 package com.oracle.objectfile.debugentry;
 
 public class FieldEntry extends MemberEntry {
+
     private final int size;
     private final int offset;
-
     private final boolean isEmbedded;
 
-    public FieldEntry(FileEntry fileEntry, String fieldName, StructureTypeEntry ownerType, TypeEntry valueType, int size, int offset, boolean isEmbedded, int modifiers) {
+    public FieldEntry(FileEntry fileEntry, String fieldName, StructureTypeEntry ownerType,
+                      TypeEntry valueType, int size, int offset, boolean isEmbedded, int modifiers) {
         super(fileEntry, fieldName, ownerType, valueType, modifiers);
         this.size = size;
         this.offset = offset;
@@ -40,7 +41,7 @@ public FieldEntry(FileEntry fileEntry, String fieldName, StructureTypeEntry owne
     }
 
     public String fieldName() {
-        return memberName;
+        return getMemberName();
     }
 
     public int getSize() {
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FileEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FileEntry.java
index 3ae8f0a4832e..f8d9a7f4b5e7 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FileEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FileEntry.java
@@ -28,26 +28,13 @@
 
 /**
  * Tracks debug info associated with a Java source file.
+ *
+ * @param fileName The name of the associated file excluding path elements.
+ * @param dirEntry The directory entry associated with this file entry.
  */
-public class FileEntry {
-    private final String fileName;
-    private final DirEntry dirEntry;
-
-    public FileEntry(String fileName, DirEntry dirEntry) {
-        this.fileName = fileName;
-        this.dirEntry = dirEntry;
-    }
-
-    /**
-     * The name of the associated file excluding path elements.
-     */
-    public String getFileName() {
-        return fileName;
-    }
+public record FileEntry(String fileName, DirEntry dirEntry) {
 
     public String getPathName() {
-        @SuppressWarnings("hiding")
-        DirEntry dirEntry = getDirEntry();
         if (dirEntry == null) {
             return "";
         } else {
@@ -59,22 +46,15 @@ public String getFullName() {
         if (dirEntry == null) {
             return fileName;
         } else {
-            return dirEntry.getPath().resolve(getFileName()).toString();
+            return dirEntry.path().resolve(fileName).toString();
         }
     }
 
-    /**
-     * The directory entry associated with this file entry.
-     */
-    public DirEntry getDirEntry() {
-        return dirEntry;
-    }
-
     @Override
     public String toString() {
-        if (getDirEntry() == null) {
-            return getFileName() == null ? "-" : getFileName();
-        } else if (getFileName() == null) {
+        if (dirEntry == null) {
+            return fileName == null ? "-" : fileName;
+        } else if (fileName == null) {
             return "--";
         }
         return String.format("FileEntry(%s)", getFullName());
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignTypeEntry.java
index 15612764d54d..953a843dedc5 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignTypeEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignTypeEntry.java
@@ -26,89 +26,41 @@
 
 package com.oracle.objectfile.debugentry;
 
-import com.oracle.objectfile.debuginfo.DebugInfoProvider;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugForeignTypeInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo;
-
-import jdk.graal.compiler.debug.DebugContext;
-import jdk.vm.ci.meta.ResolvedJavaType;
-
 public class ForeignTypeEntry extends ClassEntry {
-    private static final int FLAG_WORD = 1 << 0;
-    private static final int FLAG_STRUCT = 1 << 1;
-    private static final int FLAG_POINTER = 1 << 2;
-    private static final int FLAG_INTEGRAL = 1 << 3;
-    private static final int FLAG_SIGNED = 1 << 4;
-    private static final int FLAG_FLOAT = 1 << 5;
-    private String typedefName;
-    private ForeignTypeEntry parent;
-    private TypeEntry pointerTo;
-    private int flags;
-
-    public ForeignTypeEntry(String className, FileEntry fileEntry, int size) {
-        super(className, fileEntry, size);
-        typedefName = null;
-        parent = null;
-        pointerTo = null;
-        flags = 0;
+    private final String typedefName;
+    private final ForeignTypeEntry parent;
+    private final TypeEntry pointerTo;
+    private final boolean isWord;
+    private final boolean isStruct;
+    private final boolean isPointer;
+    private final boolean isInteger;
+    private final boolean isSigned;
+    private final boolean isFloat;
+
+    public ForeignTypeEntry(String typeName, int size, long classOffset, long typeSignature,
+                            long layoutTypeSignature, ClassEntry superClass, FileEntry fileEntry, LoaderEntry loader,
+                            String typedefName, ForeignTypeEntry parent, TypeEntry pointerTo, boolean isWord,
+                            boolean isStruct, boolean isPointer, boolean isInteger, boolean isSigned, boolean isFloat) {
+        super(typeName, size, classOffset, typeSignature, typeSignature, layoutTypeSignature, layoutTypeSignature, superClass, fileEntry, loader);
+        this.typedefName = typedefName;
+        this.parent = parent;
+        this.pointerTo = pointerTo;
+        this.isWord = isWord;
+        this.isStruct = isStruct;
+        this.isPointer = isPointer;
+        this.isInteger = isInteger;
+        this.isSigned = isSigned;
+        this.isFloat = isFloat;
     }
 
     @Override
-    public DebugInfoProvider.DebugTypeInfo.DebugTypeKind typeKind() {
-        return DebugInfoProvider.DebugTypeInfo.DebugTypeKind.FOREIGN;
-    }
-
-    /*
-     * When we process a foreign pointer type for the .debug_info section we want to reuse its type
-     * signature. After sizing the .debug_info the layout type signature contains the type signature
-     * of the type this foreign pointer points to.
-     */
-    public void setLayoutTypeSignature(long typeSignature) {
-        this.layoutTypeSignature = typeSignature;
+    public boolean isForeign() {
+        return true;
     }
 
     @Override
-    public void addDebugInfo(DebugInfoBase debugInfoBase, DebugTypeInfo debugTypeInfo, DebugContext debugContext) {
-        assert debugTypeInfo instanceof DebugForeignTypeInfo;
-        super.addDebugInfo(debugInfoBase, debugTypeInfo, debugContext);
-
-        // foreign pointers are never stored compressed so don't need a separate indirect type
-        this.indirectTypeSignature = typeSignature;
-        this.indirectLayoutTypeSignature = layoutTypeSignature;
-
-        DebugForeignTypeInfo debugForeignTypeInfo = (DebugForeignTypeInfo) debugTypeInfo;
-        this.typedefName = debugForeignTypeInfo.typedefName();
-        if (debugForeignTypeInfo.isWord()) {
-            flags = FLAG_WORD;
-        } else if (debugForeignTypeInfo.isStruct()) {
-            flags = FLAG_STRUCT;
-            ResolvedJavaType parentIdType = debugForeignTypeInfo.parent();
-            if (parentIdType != null) {
-                TypeEntry parentTypeEntry = debugInfoBase.lookupClassEntry(parentIdType);
-                assert parentTypeEntry instanceof ForeignTypeEntry;
-                parent = (ForeignTypeEntry) parentTypeEntry;
-            }
-        } else if (debugForeignTypeInfo.isPointer()) {
-            flags = FLAG_POINTER;
-            ResolvedJavaType referent = debugForeignTypeInfo.pointerTo();
-            if (referent != null) {
-                pointerTo = debugInfoBase.lookupTypeEntry(referent);
-            }
-        } else if (debugForeignTypeInfo.isIntegral()) {
-            flags = FLAG_INTEGRAL;
-        } else if (debugForeignTypeInfo.isFloat()) {
-            flags = FLAG_FLOAT;
-        }
-        if (debugForeignTypeInfo.isSigned()) {
-            flags |= FLAG_SIGNED;
-        }
-        if (debugContext.isLogEnabled()) {
-            if (isPointer() && pointerTo != null) {
-                debugContext.log("foreign type %s flags 0x%x referent %s ", typeName, flags, pointerTo.getTypeName());
-            } else {
-                debugContext.log("foreign type %s flags 0x%x", typeName, flags);
-            }
-        }
+    public boolean isInstance() {
+        return false;
     }
 
     public String getTypedefName() {
@@ -124,38 +76,26 @@ public TypeEntry getPointerTo() {
     }
 
     public boolean isWord() {
-        return (flags & FLAG_WORD) != 0;
+        return isWord;
     }
 
     public boolean isStruct() {
-        return (flags & FLAG_STRUCT) != 0;
+        return isStruct;
     }
 
     public boolean isPointer() {
-        return (flags & FLAG_POINTER) != 0;
+        return isPointer;
     }
 
-    public boolean isIntegral() {
-        return (flags & FLAG_INTEGRAL) != 0;
+    public boolean isInteger() {
+        return isInteger;
     }
 
     public boolean isSigned() {
-        return (flags & FLAG_SIGNED) != 0;
+        return isSigned;
     }
 
     public boolean isFloat() {
-        return (flags & FLAG_FLOAT) != 0;
-    }
-
-    @Override
-    protected void processInterface(ResolvedJavaType interfaceType, DebugInfoBase debugInfoBase, DebugContext debugContext) {
-        ClassEntry parentEntry = debugInfoBase.lookupClassEntry(interfaceType);
-        // don't model the interface relationship when the Java interface actually identifies a
-        // foreign type
-        if (parentEntry instanceof InterfaceClassEntry) {
-            super.processInterface(interfaceType, debugInfoBase, debugContext);
-        } else {
-            assert parentEntry instanceof ForeignTypeEntry : "was only expecting an interface or a foreign type";
-        }
+        return isFloat;
     }
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FrameSizeChangeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FrameSizeChangeEntry.java
new file mode 100644
index 000000000000..6b0b98cab0ba
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FrameSizeChangeEntry.java
@@ -0,0 +1,13 @@
+package com.oracle.objectfile.debugentry;
+
+import com.oracle.objectfile.debuginfo.DebugInfoProvider.FrameSizeChangeType;
+
+public record FrameSizeChangeEntry(int offset, FrameSizeChangeType type) {
+    public boolean isExtend() {
+        return type == FrameSizeChangeType.EXTEND;
+    }
+
+    public boolean isContract() {
+        return type == FrameSizeChangeType.CONTRACT;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/HeaderTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/HeaderTypeEntry.java
index 4ececbccd8bf..165f0de77f7b 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/HeaderTypeEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/HeaderTypeEntry.java
@@ -26,28 +26,14 @@
 
 package com.oracle.objectfile.debugentry;
 
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugHeaderTypeInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind;
-
-import jdk.graal.compiler.debug.DebugContext;
-
 public class HeaderTypeEntry extends StructureTypeEntry {
 
-    public HeaderTypeEntry(String typeName, int size) {
-        super(typeName, size);
-    }
-
-    @Override
-    public DebugTypeKind typeKind() {
-        return DebugTypeKind.HEADER;
+    public HeaderTypeEntry(String typeName, int size, long typeSignature) {
+        super(typeName, size, -1, typeSignature, typeSignature, typeSignature, typeSignature);
     }
 
     @Override
-    public void addDebugInfo(DebugInfoBase debugInfoBase, DebugTypeInfo debugTypeInfo, DebugContext debugContext) {
-        super.addDebugInfo(debugInfoBase, debugTypeInfo, debugContext);
-        assert debugTypeInfo.typeName().equals(typeName);
-        DebugHeaderTypeInfo debugHeaderTypeInfo = (DebugHeaderTypeInfo) debugTypeInfo;
-        debugHeaderTypeInfo.fieldInfoProvider().forEach(debugFieldInfo -> this.processField(debugFieldInfo, debugInfoBase, debugContext));
+    public boolean isHeader() {
+        return true;
     }
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/InterfaceClassEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/InterfaceClassEntry.java
index 42b2e7ac2d03..5e1a684f8baf 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/InterfaceClassEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/InterfaceClassEntry.java
@@ -27,43 +27,35 @@
 package com.oracle.objectfile.debugentry;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
-import java.util.stream.Stream;
-
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugInterfaceTypeInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind;
-
-import jdk.graal.compiler.debug.DebugContext;
 
 public class InterfaceClassEntry extends ClassEntry {
     private final List<ClassEntry> implementors;
 
-    public InterfaceClassEntry(String className, FileEntry fileEntry, int size) {
-        super(className, fileEntry, size);
-        implementors = new ArrayList<>();
+    public InterfaceClassEntry(String typeName, int size, long classOffset, long typeSignature,
+                               long compressedTypeSignature, long layoutTypeSignature, long compressedLayoutTypeSignature,
+                               ClassEntry superClass, FileEntry fileEntry, LoaderEntry loader) {
+        super(typeName, size, classOffset, typeSignature, compressedTypeSignature, layoutTypeSignature, compressedLayoutTypeSignature, superClass, fileEntry, loader);
+        this.implementors = new ArrayList<>();
     }
 
     @Override
-    public DebugTypeKind typeKind() {
-        return DebugTypeKind.INTERFACE;
+    public boolean isInterface() {
+        return true;
     }
 
     @Override
-    public void addDebugInfo(DebugInfoBase debugInfoBase, DebugTypeInfo debugTypeInfo, DebugContext debugContext) {
-        assert debugTypeInfo instanceof DebugInterfaceTypeInfo;
-        super.addDebugInfo(debugInfoBase, debugTypeInfo, debugContext);
+    public boolean isInstance() {
+        return false;
     }
 
-    public void addImplementor(ClassEntry classEntry, DebugContext debugContext) {
+    public void addImplementor(ClassEntry classEntry) {
         implementors.add(classEntry);
-        if (debugContext.isLogEnabled()) {
-            debugContext.log("typename %s add implementor %s%n", typeName, classEntry.getTypeName());
-        }
     }
 
-    public Stream<ClassEntry> implementors() {
-        return implementors.stream();
+    public List<ClassEntry> getImplementors() {
+        return Collections.unmodifiableList(implementors);
     }
 
     @Override
@@ -75,7 +67,7 @@ public int getSize() {
          * size of the wrapper class that handles address translation for values embedded in object
          * fields.
          */
-        int maxSize = super.size;
+        int maxSize = super.getSize();
         for (ClassEntry implementor : implementors) {
             int nextSize = implementor.getSize();
 
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LoaderEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LoaderEntry.java
index f3390e29e802..1ab62ddc6d94 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LoaderEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LoaderEntry.java
@@ -26,19 +26,10 @@
 package com.oracle.objectfile.debugentry;
 
 /**
- * A LoaderEntry is used to record a unique id string for the class loader asscoiated with classes
+ * A LoaderEntry is used to record a unique id string for the class loader associated with classes
  * that can be susceptible to repeat definitions. In such cases the loader id can be used when
  * constructing a unique linker symbol for methods and static fields of the class. That same id may
  * need to be embedded in debug info that identifies class and method names.
  */
-public class LoaderEntry {
-    private final String loaderId;
-
-    public LoaderEntry(String id) {
-        loaderId = id;
-    }
-
-    public String getLoaderId() {
-        return loaderId;
-    }
+public record LoaderEntry(String loaderId) {
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalEntry.java
new file mode 100644
index 000000000000..341d04f21ed7
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalEntry.java
@@ -0,0 +1,67 @@
+package com.oracle.objectfile.debugentry;
+
+import jdk.vm.ci.meta.JavaKind;
+
+import java.util.Objects;
+
+public final class LocalEntry {
+    private final String name;
+    private final TypeEntry type;
+    private final JavaKind kind;
+    private final int slot;
+    private int line;
+
+    public LocalEntry(String name, TypeEntry type, JavaKind kind, int slot, int line) {
+        this.name = name;
+        this.type = type;
+        this.kind = kind;
+        this.slot = slot;
+        this.line = line;
+    }
+
+    public String name() {
+        return name;
+    }
+
+    public TypeEntry type() {
+        return type;
+    }
+
+    public JavaKind kind() {
+        return kind;
+    }
+
+    public int slot() {
+        return slot;
+    }
+
+    public int line() {
+        return line;
+    }
+
+    public void setLine(int line) {
+        this.line = line;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) return true;
+        if (obj == null || obj.getClass() != this.getClass()) return false;
+        var that = (LocalEntry) obj;
+        return Objects.equals(this.name, that.name) &&
+                Objects.equals(this.type, that.type) &&
+                Objects.equals(this.kind, that.kind) &&
+                this.slot == that.slot &&
+                this.line == that.line;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(name, type, kind, slot, line);
+    }
+
+    @Override
+    public String toString() {
+        return String.format("Local(%s type=%s slot=%d line=%d)", name, type.getTypeName(), slot, line);
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalValueEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalValueEntry.java
new file mode 100644
index 000000000000..e177f45669f7
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalValueEntry.java
@@ -0,0 +1,18 @@
+package com.oracle.objectfile.debugentry;
+
+import com.oracle.objectfile.debuginfo.DebugInfoProvider.LocalValueKind;
+import jdk.vm.ci.meta.JavaConstant;
+
+public record LocalValueEntry(int line, String name, TypeEntry type, int regIndex, int stackSlot, long heapOffset, JavaConstant constant, LocalValueKind localKind, LocalEntry local) {
+
+    @Override
+    public String toString() {
+        return switch (localKind) {
+            case REGISTER -> "reg[" + regIndex + "]";
+            case STACK -> "stack[" + stackSlot + "]";
+            case CONSTANT -> "constant[" + (constant != null ? constant.toValueString() : "null") + "]";
+            default -> "-";
+        };
+    }
+
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MemberEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MemberEntry.java
index 48a5bf55ffca..223544569f67 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MemberEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MemberEntry.java
@@ -31,12 +31,12 @@
  * shared by both field and method entries.
  */
 public abstract class MemberEntry {
-    protected FileEntry fileEntry;
-    protected final int line;
-    protected final String memberName;
-    protected final StructureTypeEntry ownerType;
-    protected final TypeEntry valueType;
-    protected final int modifiers;
+    private final FileEntry fileEntry;
+    private final int line;
+    private final String memberName;
+    private final StructureTypeEntry ownerType;
+    private final TypeEntry valueType;
+    private final int modifiers;
 
     public MemberEntry(FileEntry fileEntry, String memberName, StructureTypeEntry ownerType, TypeEntry valueType, int modifiers) {
         this(fileEntry, 0, memberName, ownerType, valueType, modifiers);
@@ -54,7 +54,7 @@ public MemberEntry(FileEntry fileEntry, int line, String memberName, StructureTy
 
     public String getFileName() {
         if (fileEntry != null) {
-            return fileEntry.getFileName();
+            return fileEntry.fileName();
         } else {
             return "";
         }
@@ -90,11 +90,15 @@ public int getFileIdx() {
         return 1;
     }
 
+    public String getMemberName() {
+        return memberName;
+    }
+
     public int getLine() {
         return line;
     }
 
-    public StructureTypeEntry ownerType() {
+    public StructureTypeEntry getOwnerType() {
         return ownerType;
     }
 
@@ -109,4 +113,9 @@ public int getModifiers() {
     public String getModifiersString() {
         return ownerType.memberModifiers(modifiers);
     }
+
+    @Override
+    public String toString() {
+        return String.format("Member(%s %s %s owner=%s %s:%d)", getModifiersString(), valueType.getTypeName(), memberName, ownerType.getTypeName(), fileEntry.getFullName(), line);
+    }
 }
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 b9dba7245613..ec17d2e4f724 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
@@ -26,112 +26,93 @@
 
 package com.oracle.objectfile.debugentry;
 
-import java.util.ArrayList;
-import java.util.ListIterator;
-
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugCodeInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalValueInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocationInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugMethodInfo;
-
 import jdk.vm.ci.meta.JavaKind;
-import jdk.vm.ci.meta.ResolvedJavaType;
+
+import java.util.ArrayList;
+import java.util.List;
 
 public class MethodEntry extends MemberEntry {
-    private final TypeEntry[] paramTypes;
-    private final DebugLocalInfo thisParam;
-    private final DebugLocalInfo[] paramInfos;
+    private final LocalEntry thisParam;
+    private final List<LocalEntry> paramInfos;
     private final int firstLocalSlot;
     // local vars are accumulated as they are referenced sorted by slot, then name, then
     // type name. we don't currently deal handle references to locals with no slot.
-    private final ArrayList<DebugLocalInfo> locals;
-    static final int DEOPT = 1 << 0;
-    static final int IN_RANGE = 1 << 1;
-    static final int INLINED = 1 << 2;
-    static final int IS_OVERRIDE = 1 << 3;
-    static final int IS_CONSTRUCTOR = 1 << 4;
-    private int flags;
+    private final List<LocalEntry> locals;
+    private final boolean isDeopt;
+    private boolean isInRange;
+    private boolean isInlined;
+    private final boolean isOverride;
+    private final boolean isConstructor;
     private final int vtableOffset;
     private final String symbolName;
 
     @SuppressWarnings("this-escape")
-    public MethodEntry(DebugInfoBase debugInfoBase, DebugMethodInfo debugMethodInfo,
-                       FileEntry fileEntry, int line, String methodName, ClassEntry ownerType,
-                       TypeEntry valueType, TypeEntry[] paramTypes, DebugLocalInfo[] paramInfos, DebugLocalInfo thisParam) {
-        super(fileEntry, line, methodName, ownerType, valueType, debugMethodInfo.modifiers());
-        this.paramTypes = paramTypes;
+    public MethodEntry(FileEntry fileEntry, int line, String methodName, StructureTypeEntry ownerType,
+                       TypeEntry valueType, int modifiers, List<LocalEntry> paramInfos, LocalEntry thisParam,
+                       String symbolName, boolean isDeopt, boolean isOverride, boolean isConstructor, int vtableOffset,
+                       int firstLocalSlot) {
+        super(fileEntry, line, methodName, ownerType, valueType, modifiers);
         this.paramInfos = paramInfos;
         this.thisParam = thisParam;
-        this.symbolName = debugMethodInfo.symbolNameForMethod();
-        this.flags = 0;
-        if (debugMethodInfo.isDeoptTarget()) {
-            setIsDeopt();
-        }
-        if (debugMethodInfo.isConstructor()) {
-            setIsConstructor();
-        }
-        if (debugMethodInfo.isOverride()) {
-            setIsOverride();
-        }
-        vtableOffset = debugMethodInfo.vtableOffset();
-        int paramCount = paramInfos.length;
-        if (paramCount > 0) {
-            DebugLocalInfo lastParam = paramInfos[paramCount - 1];
-            firstLocalSlot = lastParam.slot() + lastParam.slotCount();
-        } else {
-            firstLocalSlot = (thisParam == null ? 0 : thisParam.slotCount());
-        }
-        locals = new ArrayList<>();
-        updateRangeInfo(debugInfoBase, debugMethodInfo);
+        this.symbolName = symbolName;
+        this.isDeopt = isDeopt;
+        this.isOverride = isOverride;
+        this.isConstructor = isConstructor;
+        this.vtableOffset = vtableOffset;
+        this.firstLocalSlot = firstLocalSlot;
+
+        this.locals = new ArrayList<>();
+        this.isInRange = false;
+        this.isInlined = false;
     }
 
-    public String methodName() {
-        return memberName;
+    public String getMethodName() {
+        return getMemberName();
     }
 
     @Override
-    public ClassEntry ownerType() {
+    public ClassEntry getOwnerType() {
+        StructureTypeEntry ownerType = super.getOwnerType();
         assert ownerType instanceof ClassEntry;
         return (ClassEntry) ownerType;
     }
 
     public int getParamCount() {
-        return paramInfos.length;
+        return paramInfos.size();
     }
 
     public TypeEntry getParamType(int idx) {
-        assert idx < paramInfos.length;
-        return paramTypes[idx];
+        assert idx < paramInfos.size();
+        return paramInfos.get(idx).type();
     }
 
-    public TypeEntry[] getParamTypes() {
-        return paramTypes;
+    public List<TypeEntry> getParamTypes() {
+        return paramInfos.stream().map(LocalEntry::type).toList();
     }
 
     public String getParamTypeName(int idx) {
-        assert idx < paramTypes.length;
-        return paramTypes[idx].getTypeName();
+        assert idx < paramInfos.size();
+        return paramInfos.get(idx).type().getTypeName();
     }
 
     public String getParamName(int idx) {
-        assert idx < paramInfos.length;
+        assert idx < paramInfos.size();
         /* N.b. param names may be null. */
-        return paramInfos[idx].name();
+        return paramInfos.get(idx).name();
     }
 
     public int getParamLine(int idx) {
-        assert idx < paramInfos.length;
+        assert idx < paramInfos.size();
         /* N.b. param names may be null. */
-        return paramInfos[idx].line();
+        return paramInfos.get(idx).line();
     }
 
-    public DebugLocalInfo getParam(int i) {
-        assert i >= 0 && i < paramInfos.length : "bad param index";
-        return paramInfos[i];
+    public LocalEntry getParam(int i) {
+        assert i >= 0 && i < paramInfos.size() : "bad param index";
+        return paramInfos.get(i);
     }
 
-    public DebugLocalInfo getThisParam() {
+    public LocalEntry getThisParam() {
         return thisParam;
     }
 
@@ -139,85 +120,37 @@ public int getLocalCount() {
         return locals.size();
     }
 
-    public DebugLocalInfo getLocal(int i) {
+    public LocalEntry getLocal(int i) {
         assert i >= 0 && i < locals.size() : "bad param index";
         return locals.get(i);
     }
 
-    private void setIsDeopt() {
-        flags |= DEOPT;
-    }
-
     public boolean isDeopt() {
-        return (flags & DEOPT) != 0;
-    }
-
-    private void setIsInRange() {
-        flags |= IN_RANGE;
+        return isDeopt;
     }
 
     public boolean isInRange() {
-        return (flags & IN_RANGE) != 0;
+        return isInRange;
     }
 
-    private void setIsInlined() {
-        flags |= INLINED;
+    public void setInRange() {
+        isInRange = true;
     }
 
     public boolean isInlined() {
-        return (flags & INLINED) != 0;
+        return isInlined;
     }
 
-    private void setIsOverride() {
-        flags |= IS_OVERRIDE;
+    public void setInlined() {
+        isInlined = true;
     }
 
     public boolean isOverride() {
-        return (flags & IS_OVERRIDE) != 0;
-    }
-
-    private void setIsConstructor() {
-        flags |= IS_CONSTRUCTOR;
+        return isOverride;
     }
 
     public boolean isConstructor() {
-        return (flags & IS_CONSTRUCTOR) != 0;
-    }
-
-    /**
-     * Sets {@code isInRange} and ensures that the {@code fileEntry} is up to date. If the
-     * MethodEntry was added by traversing the DeclaredMethods of a Class its fileEntry will point
-     * to the original source file, thus it will be wrong for substituted methods. As a result when
-     * setting a MethodEntry as isInRange we also make sure that its fileEntry reflects the file
-     * info associated with the corresponding Range.
-     *
-     * @param debugInfoBase
-     * @param debugMethodInfo
-     */
-    public void updateRangeInfo(DebugInfoBase debugInfoBase, DebugMethodInfo debugMethodInfo) {
-        if (debugMethodInfo instanceof DebugLocationInfo) {
-            DebugLocationInfo locationInfo = (DebugLocationInfo) debugMethodInfo;
-            if (locationInfo.getCaller() != null) {
-                /* this is a real inlined method */
-                setIsInlined();
-            }
-        } else if (debugMethodInfo instanceof DebugCodeInfo) {
-            /* this method is being notified as a top level compiled method */
-            if (isInRange()) {
-                /* it has already been seen -- just check for consistency */
-                assert fileEntry == debugInfoBase.ensureFileEntry(debugMethodInfo);
-            } else {
-                /*
-                 * If the MethodEntry was added by traversing the DeclaredMethods of a Class its
-                 * fileEntry may point to the original source file, which will be wrong for
-                 * substituted methods. As a result when setting a MethodEntry as isInRange we also
-                 * make sure that its fileEntry reflects the file info associated with the
-                 * corresponding Range.
-                 */
-                setIsInRange();
-                fileEntry = debugInfoBase.ensureFileEntry(debugMethodInfo);
-            }
-        }
+        return isConstructor;
     }
 
     public boolean isVirtual() {
@@ -232,148 +165,37 @@ public String getSymbolName() {
         return symbolName;
     }
 
-    /**
-     * Return a unique local or parameter variable associated with the value, optionally recording
-     * it as a new local variable or fail, returning null, when the local value does not conform
-     * with existing recorded parameter or local variables. Values with invalid (negative) slots
-     * always fail. Values whose slot is associated with a parameter only conform if their name and
-     * type equal those of the parameter. Values whose slot is in the local range will always
-     * succeed,. either by matchign the slot and name of an existing local or by being recorded as a
-     * new local variable.
-     *
-     * @param localValueInfo
-     * @return the unique local variable with which this local value can be legitimately associated
-     *         otherwise null.
-     */
-    public DebugLocalInfo recordLocal(DebugLocalValueInfo localValueInfo) {
-        int slot = localValueInfo.slot();
+    public LocalEntry lookupLocalEntry(String name, int slot, TypeEntry type, JavaKind kind, int line) {
         if (slot < 0) {
             return null;
-        } else {
-            if (slot < firstLocalSlot) {
-                return matchParam(localValueInfo);
-            } else {
-                return matchLocal(localValueInfo);
-            }
-        }
-    }
-
-    private DebugLocalInfo matchParam(DebugLocalValueInfo localValueInfo) {
-        if (thisParam != null) {
-            if (checkMatch(thisParam, localValueInfo)) {
-                return thisParam;
+        } else if (slot < firstLocalSlot) {
+            if (thisParam != null) {
+                if (thisParam.slot() == slot && thisParam.name().equals(name) && thisParam.type() == type) {
+                    return thisParam;
+                }
             }
-        }
-        for (int i = 0; i < paramInfos.length; i++) {
-            DebugLocalInfo paramInfo = paramInfos[i];
-            if (checkMatch(paramInfo, localValueInfo)) {
-                return paramInfo;
+            for (LocalEntry param : paramInfos) {
+                if (param.slot() == slot && param.name().equals(name) && param.type() == type) {
+                    return param;
+                }
             }
-        }
-        return null;
-    }
-
-    /**
-     * wrapper class for a local value that stands in as a unique identifier for the associated
-     * local variable while allowing its line to be adjusted when earlier occurrences of the same
-     * local are identified.
-     */
-    private static class DebugLocalInfoWrapper implements DebugLocalInfo {
-        DebugLocalValueInfo value;
-        int line;
-
-        DebugLocalInfoWrapper(DebugLocalValueInfo value) {
-            this.value = value;
-            this.line = value.line();
-        }
-
-        @Override
-        public ResolvedJavaType valueType() {
-            return value.valueType();
-        }
-
-        @Override
-        public String name() {
-            return value.name();
-        }
-
-        @Override
-        public String typeName() {
-            return value.typeName();
-        }
-
-        @Override
-        public int slot() {
-            return value.slot();
-        }
-
-        @Override
-        public int slotCount() {
-            return value.slotCount();
-        }
-
-        @Override
-        public JavaKind javaKind() {
-            return value.javaKind();
-        }
-
-        @Override
-        public int line() {
-            return line;
-        }
-
-        public void setLine(int line) {
-            this.line = line;
-        }
-    }
-
-    private DebugLocalInfo matchLocal(DebugLocalValueInfo localValueInfo) {
-        ListIterator<DebugLocalInfo> listIterator = locals.listIterator();
-        while (listIterator.hasNext()) {
-            DebugLocalInfoWrapper next = (DebugLocalInfoWrapper) listIterator.next();
-            if (checkMatch(next, localValueInfo)) {
-                int currentLine = next.line();
-                int newLine = localValueInfo.line();
-                if ((currentLine < 0 && newLine >= 0) ||
-                        (newLine >= 0 && newLine < currentLine)) {
-                    next.setLine(newLine);
+            return null;
+        } else {
+            for (LocalEntry local : locals) {
+                if (local.slot() == slot && local.name().equals(name) && local.type() == type) {
+                    if (line >= 0 && (local.line() < 0 || line < local.line())) {
+                        local.setLine(line);
+                    }
+                    return local;
+                } else if (local.slot() > slot) {
+                    LocalEntry newLocal = new LocalEntry(name, type, kind, slot, line);
+                    locals.add(locals.indexOf(local), newLocal);
+                    return newLocal;
                 }
-                return next;
-            } else if (next.slot() > localValueInfo.slot()) {
-                // we have iterated just beyond the insertion point
-                // so wind cursor back one element
-                listIterator.previous();
-                break;
             }
+            LocalEntry newLocal = new LocalEntry(name, type, kind, slot, line);
+            locals.add(newLocal);
+            return newLocal;
         }
-        DebugLocalInfoWrapper newLocal = new DebugLocalInfoWrapper(localValueInfo);
-        // add at the current cursor position
-        listIterator.add(newLocal);
-        return newLocal;
-    }
-
-    boolean checkMatch(DebugLocalInfo local, DebugLocalValueInfo value) {
-        boolean isMatch = (local.slot() == value.slot() &&
-                local.name().equals(value.name()) &&
-                local.typeName().equals(value.typeName()));
-        assert !isMatch || verifyMatch(local, value) : "failed to verify matched var and value";
-        return isMatch;
-    }
-
-    private static boolean verifyMatch(DebugLocalInfo local, DebugLocalValueInfo value) {
-        // slot counts are normally expected to match
-        if (local.slotCount() == value.slotCount()) {
-            return true;
-        }
-        // we can have a zero count for the local or value if it is undefined
-        if (local.slotCount() == 0 || value.slotCount() == 0) {
-            return true;
-        }
-        // pseudo-object locals can appear as longs
-        if (local.javaKind() == JavaKind.Object && value.javaKind() == JavaKind.Long) {
-            return true;
-        }
-        // something is wrong
-        return false;
     }
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PrimitiveTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PrimitiveTypeEntry.java
index a60ed3b1b1fd..9631fab6bfca 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PrimitiveTypeEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PrimitiveTypeEntry.java
@@ -26,77 +26,40 @@
 
 package com.oracle.objectfile.debugentry;
 
-import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugPrimitiveTypeInfo.FLAG_INTEGRAL;
-import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugPrimitiveTypeInfo.FLAG_NUMERIC;
-import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugPrimitiveTypeInfo.FLAG_SIGNED;
-
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugPrimitiveTypeInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind;
-
-import jdk.graal.compiler.debug.DebugContext;
+import jdk.vm.ci.meta.JavaKind;
 
 public class PrimitiveTypeEntry extends TypeEntry {
-    private char typeChar;
-    private int flags;
-    private int bitCount;
 
-    public PrimitiveTypeEntry(String typeName, int size) {
-        super(typeName, size);
-        typeChar = '#';
-        flags = 0;
-        bitCount = 0;
+    private final JavaKind kind;
+
+    public PrimitiveTypeEntry(String typeName, int size, long classOffset, long typeSignature,
+                              JavaKind kind) {
+        super(typeName, size, classOffset, typeSignature, typeSignature);
+        this.kind = kind;
     }
 
     @Override
-    public DebugTypeKind typeKind() {
-        return DebugTypeKind.PRIMITIVE;
+    public boolean isPrimitive() {
+        return true;
     }
 
-    @Override
-    public void addDebugInfo(DebugInfoBase debugInfoBase, DebugTypeInfo debugTypeInfo, DebugContext debugContext) {
-        super.addDebugInfo(debugInfoBase, debugTypeInfo, debugContext);
-        DebugPrimitiveTypeInfo debugPrimitiveTypeInfo = (DebugPrimitiveTypeInfo) debugTypeInfo;
-        flags = debugPrimitiveTypeInfo.flags();
-        typeChar = debugPrimitiveTypeInfo.typeChar();
-        bitCount = debugPrimitiveTypeInfo.bitCount();
-        if (debugContext.isLogEnabled()) {
-            debugContext.log("typename %s %s (%d bits)%n", typeName, decodeFlags(), bitCount);
-        }
+    public char getTypeChar() {
+        return kind.getTypeChar();
     }
 
-    private String decodeFlags() {
-        StringBuilder builder = new StringBuilder();
-        if ((flags & FLAG_NUMERIC) != 0) {
-            if ((flags & FLAG_INTEGRAL) != 0) {
-                if ((flags & FLAG_SIGNED) != 0) {
-                    builder.append("SIGNED ");
-                } else {
-                    builder.append("UNSIGNED ");
-                }
-                builder.append("INTEGRAL");
-            } else {
-                builder.append("FLOATING");
-            }
-        } else {
-            if (bitCount > 0) {
-                builder.append("LOGICAL");
-            } else {
-                builder.append("VOID");
-            }
-        }
-        return builder.toString();
+    public int getBitCount() {
+        return (kind == JavaKind.Void ? 0 : kind.getBitCount());
     }
 
-    public char getTypeChar() {
-        return typeChar;
+    public boolean isNumericInteger() {
+        return kind.isNumericInteger();
     }
 
-    public int getBitCount() {
-        return bitCount;
+    public boolean isNumericFloat() {
+        return kind.isNumericFloat();
     }
 
-    public int getFlags() {
-        return flags;
+    public boolean isUnsigned() {
+        return kind.isUnsigned();
     }
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringEntry.java
index 01eb6fb9de38..340634db123c 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringEntry.java
@@ -33,7 +33,6 @@
 public class StringEntry {
     private final String string;
     private int offset;
-    private boolean addToStrSection;
 
     StringEntry(String string) {
         this.string = string;
@@ -55,20 +54,11 @@ public void setOffset(int offset) {
         this.offset = offset;
     }
 
-    public boolean isAddToStrSection() {
-        return addToStrSection;
-    }
-
-    public void setAddToStrSection() {
-        this.addToStrSection = true;
-    }
-
     @Override
     public boolean equals(Object object) {
-        if (object == null || !(object instanceof StringEntry)) {
+        if (!(object instanceof StringEntry other)) {
             return false;
         } else {
-            StringEntry other = (StringEntry) object;
             return this == other || string.equals(other.string);
         }
     }
@@ -80,10 +70,10 @@ public int hashCode() {
 
     @Override
     public String toString() {
-        return string;
+        return "'" + string + "'@" + Integer.toHexString(offset);
     }
 
     public boolean isEmpty() {
-        return string.length() == 0;
+        return string.isEmpty();
     }
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringTable.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringTable.java
index 1242c79a154e..df040db89197 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringTable.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringTable.java
@@ -26,8 +26,8 @@
 
 package com.oracle.objectfile.debugentry;
 
-import java.util.HashMap;
 import java.util.Iterator;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * Allows incoming strings to be reduced to unique (up to equals) instances and supports marking of
@@ -36,22 +36,10 @@
  */
 public class StringTable implements Iterable<StringEntry> {
 
-    private final HashMap<String, StringEntry> table;
+    private final ConcurrentHashMap<String, StringEntry> table;
 
     public StringTable() {
-        this.table = new HashMap<>();
-    }
-
-    /**
-     * Ensures a unique instance of a string exists in the table, inserting the supplied String if
-     * no equivalent String is already present. This should only be called before the string section
-     * has been written.
-     *
-     * @param string the string to be included in the table
-     * @return the unique instance of the String
-     */
-    public String uniqueString(String string) {
-        return ensureString(string, false);
+        this.table = new ConcurrentHashMap<>();
     }
 
     /**
@@ -64,19 +52,7 @@ public String uniqueString(String string) {
      * @return the unique instance of the String
      */
     public String uniqueDebugString(String string) {
-        return ensureString(string, true);
-    }
-
-    private String ensureString(String string, boolean addToStrSection) {
-        StringEntry stringEntry = table.get(string);
-        if (stringEntry == null) {
-            stringEntry = new StringEntry(string);
-            table.put(string, stringEntry);
-        }
-        if (addToStrSection && !stringEntry.isAddToStrSection()) {
-            stringEntry.setAddToStrSection();
-        }
-        return stringEntry.getString();
+        return table.computeIfAbsent(string, StringEntry::new).getString();
     }
 
     /**
@@ -90,9 +66,6 @@ private String ensureString(String string, boolean addToStrSection) {
     public int debugStringIndex(String string) {
         StringEntry stringEntry = table.get(string);
         assert stringEntry != null : "\"" + string + "\" not in string table";
-        if (stringEntry == null) {
-            return -1;
-        }
         return stringEntry.getOffset();
     }
 
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java
index d56919481b15..f73296e37d54 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java
@@ -28,15 +28,8 @@
 
 import java.lang.reflect.Modifier;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
-import java.util.stream.Stream;
-
-import com.oracle.objectfile.debuginfo.DebugInfoProvider;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFieldInfo;
-import com.oracle.objectfile.elf.dwarf.DwarfDebugInfo;
-
-import jdk.graal.compiler.debug.DebugContext;
-import jdk.vm.ci.meta.ResolvedJavaType;
 
 /**
  * An intermediate type that provides behaviour for managing fields. This unifies code for handling
@@ -46,7 +39,7 @@ public abstract class StructureTypeEntry extends TypeEntry {
     /**
      * Details of fields located in this instance.
      */
-    protected final List<FieldEntry> fields;
+    private final List<FieldEntry> fields;
 
     /**
      * The type signature of this types' layout. The layout of a type contains debug info of fields
@@ -55,54 +48,24 @@ public abstract class StructureTypeEntry extends TypeEntry {
      */
     protected long layoutTypeSignature;
 
-    public StructureTypeEntry(String typeName, int size) {
-        super(typeName, size);
+    public StructureTypeEntry(String typeName, int size, long classOffset, long typeSignature,
+                              long compressedTypeSignature, long layoutTypeSignature, long compressedLayoutTypeSignature) {
+        super(typeName, size, classOffset, typeSignature, compressedTypeSignature);
+        this.layoutTypeSignature = layoutTypeSignature;
+
         this.fields = new ArrayList<>();
-        this.layoutTypeSignature = 0;
     }
 
     public long getLayoutTypeSignature() {
         return layoutTypeSignature;
     }
 
-    public Stream<FieldEntry> fields() {
-        return fields.stream();
+    public void addField(FieldEntry field) {
+        fields.add(field);
     }
 
     public List<FieldEntry> getFields() {
-        return fields;
-    }
-
-    public int fieldCount() {
-        return fields.size();
-    }
-
-    protected void processField(DebugFieldInfo debugFieldInfo, DebugInfoBase debugInfoBase, DebugContext debugContext) {
-        /* Delegate this so superclasses can override this and inspect the computed FieldEntry. */
-        addField(debugFieldInfo, debugInfoBase, debugContext);
-    }
-
-    protected FieldEntry addField(DebugFieldInfo debugFieldInfo, DebugInfoBase debugInfoBase, DebugContext debugContext) {
-        String fieldName = debugInfoBase.uniqueDebugString(debugFieldInfo.name());
-        ResolvedJavaType valueType = debugFieldInfo.valueType();
-        String valueTypeName = valueType.toJavaName();
-        int fieldSize = debugFieldInfo.size();
-        int fieldoffset = debugFieldInfo.offset();
-        boolean fieldIsEmbedded = debugFieldInfo.isEmbedded();
-        int fieldModifiers = debugFieldInfo.modifiers();
-        if (debugContext.isLogEnabled()) {
-            debugContext.log("typename %s adding %s field %s type %s%s size %s at offset 0x%x%n",
-                    typeName, memberModifiers(fieldModifiers), fieldName, valueTypeName, (fieldIsEmbedded ? "(embedded)" : ""), fieldSize, fieldoffset);
-        }
-        TypeEntry valueTypeEntry = debugInfoBase.lookupTypeEntry(valueType);
-        /*
-         * n.b. the field file may differ from the owning class file when the field is a
-         * substitution
-         */
-        FileEntry fileEntry = debugInfoBase.ensureFileEntry(debugFieldInfo);
-        FieldEntry fieldEntry = new FieldEntry(fileEntry, fieldName, this, valueTypeEntry, fieldSize, fieldoffset, fieldIsEmbedded, fieldModifiers);
-        fields.add(fieldEntry);
-        return fieldEntry;
+        return Collections.unmodifiableList(fields);
     }
 
     String memberModifiers(int modifiers) {
@@ -137,15 +100,4 @@ String memberModifiers(int modifiers) {
 
         return builder.toString();
     }
-
-    @Override
-    public void addDebugInfo(@SuppressWarnings("unused") DebugInfoBase debugInfoBase, DebugInfoProvider.DebugTypeInfo debugTypeInfo, @SuppressWarnings("unused") DebugContext debugContext) {
-        super.addDebugInfo(debugInfoBase, debugTypeInfo, debugContext);
-        // header type does not have a separate layout type
-        if (this instanceof HeaderTypeEntry) {
-            this.layoutTypeSignature = typeSignature;
-        } else {
-            this.layoutTypeSignature = debugTypeInfo.typeSignature(DwarfDebugInfo.LAYOUT_PREFIX);
-        }
-    }
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/TypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/TypeEntry.java
index 723466e6c52f..4796670d96f3 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/TypeEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/TypeEntry.java
@@ -26,26 +26,11 @@
 
 package com.oracle.objectfile.debugentry;
 
-import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind.ARRAY;
-import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind.ENUM;
-import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind.FOREIGN;
-import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind.HEADER;
-import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind.INSTANCE;
-import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind.INTERFACE;
-import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind.PRIMITIVE;
-
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind;
-import com.oracle.objectfile.elf.dwarf.DwarfDebugInfo;
-
-import com.oracle.objectfile.runtime.RuntimeDebugInfoBase;
-import jdk.graal.compiler.debug.DebugContext;
-
 public abstract class TypeEntry {
     /**
      * The name of this type.
      */
-    protected final String typeName;
+    private final String typeName;
 
     /**
      * The type signature of this type. This is a pointer to the underlying layout of the type.
@@ -62,19 +47,20 @@ public abstract class TypeEntry {
      * The offset of the java.lang.Class instance for this class in the image heap or -1 if no such
      * object exists.
      */
-    private long classOffset;
+    private final long classOffset;
 
     /**
      * The size of an occurrence of this type in bytes.
      */
-    protected final int size;
+    private final int size;
 
-    protected TypeEntry(String typeName, int size) {
+    protected TypeEntry(String typeName, int size, long classOffset, long typeSignature,
+                        long typeSignatureForCompressed) {
         this.typeName = typeName;
         this.size = size;
-        this.classOffset = -1;
-        this.typeSignature = 0;
-        this.typeSignatureForCompressed = 0;
+        this.classOffset = classOffset;
+        this.typeSignature = typeSignature;
+        this.typeSignatureForCompressed = typeSignatureForCompressed;
     }
 
     public long getTypeSignature() {
@@ -97,34 +83,32 @@ public String getTypeName() {
         return typeName;
     }
 
-    public abstract DebugTypeKind typeKind();
-
     public boolean isPrimitive() {
-        return typeKind() == PRIMITIVE;
+        return false;
     }
 
     public boolean isHeader() {
-        return typeKind() == HEADER;
+        return false;
     }
 
     public boolean isArray() {
-        return typeKind() == ARRAY;
+        return false;
     }
 
     public boolean isInstance() {
-        return typeKind() == INSTANCE;
+        return false;
     }
 
     public boolean isInterface() {
-        return typeKind() == INTERFACE;
+        return false;
     }
 
     public boolean isEnum() {
-        return typeKind() == ENUM;
+        return false;
     }
 
     public boolean isForeign() {
-        return typeKind() == FOREIGN;
+        return false;
     }
 
     /**
@@ -148,15 +132,18 @@ public boolean isStructure() {
         return isClass() || isHeader();
     }
 
-    public void addDebugInfo(@SuppressWarnings("unused") DebugInfoBase debugInfoBase, DebugTypeInfo debugTypeInfo, @SuppressWarnings("unused") DebugContext debugContext) {
-        /* Record the location of the Class instance in the heap if there is one */
-        this.classOffset = debugTypeInfo.classOffset();
-        this.typeSignature = debugTypeInfo.typeSignature("");
-        // primitives, header and foreign types are never stored compressed
-        if (!debugInfoBase.useHeapBase() || this instanceof PrimitiveTypeEntry || this instanceof HeaderTypeEntry || this instanceof ForeignTypeEntry) {
-            this.typeSignatureForCompressed = typeSignature;
-        } else {
-            this.typeSignatureForCompressed = debugTypeInfo.typeSignature(DwarfDebugInfo.COMPRESSED_PREFIX);
-        }
+    @Override
+    public String toString() {
+        String kind = switch (this) {
+            case PrimitiveTypeEntry p -> "Primitive";
+            case HeaderTypeEntry h -> "Header";
+            case ArrayTypeEntry a -> "Array";
+            case InterfaceClassEntry i  -> "Interface";
+            case EnumClassEntry e -> "Enum";
+            case ForeignTypeEntry f -> "Foreign";
+            case ClassEntry c -> "Instance";
+            default -> "";
+        };
+        return String.format("%sType(%s size=%d @%s)", kind, getTypeName(), getSize(), Long.toHexString(classOffset));
     }
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/CallRange.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/CallRange.java
index c69270610d55..4fef52f59525 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/CallRange.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/CallRange.java
@@ -28,46 +28,60 @@
 
 import com.oracle.objectfile.debugentry.MethodEntry;
 
-class CallRange extends SubRange {
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+
+public class CallRange extends Range {
+
     /**
-     * The first direct callee whose range is wholly contained in this range or null if this is a
+     * The direct callees whose ranges are wholly contained in this range. Empty if this is a
      * leaf range.
      */
-    protected SubRange firstCallee;
-    /**
-     * The last direct callee whose range is wholly contained in this range.
-     */
-    protected SubRange lastCallee;
+    private final List<Range> callees = new ArrayList<>();
+
+    protected CallRange(PrimaryRange primary, MethodEntry methodEntry, int lo, int hi, int line, CallRange caller, int depth) {
+        super(primary, methodEntry, lo, hi, line, caller, depth);
+    }
 
-    protected CallRange(MethodEntry methodEntry, long lo, long hi, int line, PrimaryRange primary, Range caller) {
-        super(methodEntry, lo, hi, line, primary, caller);
-        this.firstCallee = null;
-        this.lastCallee = null;
+    @Override
+    public List<Range> getCallees() {
+        return callees;
     }
 
     @Override
-    protected void addCallee(SubRange callee) {
-        assert this.lo <= callee.lo;
-        assert this.hi >= callee.hi;
-        assert callee.caller == this;
-        assert callee.siblingCallee == null;
-        if (this.firstCallee == null) {
-            assert this.lastCallee == null;
-            this.firstCallee = this.lastCallee = callee;
+    public Stream<Range> rangeStream() {
+        return Stream.concat(super.rangeStream(), callees.stream().flatMap(Range::rangeStream));
+    }
+
+    protected void addCallee(Range callee, boolean isInitialRange) {
+        //System.out.println("Caller lo: " + this.getLo() + ", Callee lo: " + callee.getLo());
+        //System.out.println("Caller hi: " + this.getHi() + ", Callee hi: " + callee.getHi());
+        //System.out.println("Caller method: " + this.getMethodName() + ", Callee method: " + callee.getMethodName());
+        assert this.getLoOffset() <= callee.getLoOffset();
+        assert this.getHiOffset() >= callee.getHiOffset();
+        assert callee.getCaller() == this;
+        if (isInitialRange) {
+            callees.addFirst(callee);
         } else {
-            this.lastCallee.siblingCallee = callee;
-            this.lastCallee = callee;
+            callees.add(callee);
         }
     }
 
-    @Override
-    public SubRange getFirstCallee() {
-        return firstCallee;
+    protected void insertCallee(Range callee, int pos) {
+        assert pos < callees.size();
+        assert this.getLoOffset() <= callee.getLoOffset();
+        assert this.getHiOffset() >= callee.getHiOffset();
+        assert callee.getCaller() == this;
+        callees.add(pos, callee);
     }
 
     @Override
     public boolean isLeaf() {
-        assert firstCallee != null;
-        return false;
+        return callees.isEmpty();
+    }
+
+    public void removeCallee(Range callee) {
+        callees.remove(callee);
     }
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/LeafRange.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/LeafRange.java
index 2fc9f37fb45d..e472e90392f7 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/LeafRange.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/LeafRange.java
@@ -28,20 +28,11 @@
 
 import com.oracle.objectfile.debugentry.MethodEntry;
 
-class LeafRange extends SubRange {
-    protected LeafRange(MethodEntry methodEntry, long lo, long hi, int line, PrimaryRange primary, Range caller) {
-        super(methodEntry, lo, hi, line, primary, caller);
+public class LeafRange extends Range {
+    protected LeafRange(PrimaryRange primary, MethodEntry methodEntry, int lo, int hi, int line, CallRange caller, int depth) {
+        super(primary, methodEntry, lo, hi, line, caller, depth);
     }
 
-    @Override
-    protected void addCallee(SubRange callee) {
-        assert false : "should never be adding callees to a leaf range!";
-    }
-
-    @Override
-    public SubRange getFirstCallee() {
-        return null;
-    }
 
     @Override
     public boolean isLeaf() {
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/PrimaryRange.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/PrimaryRange.java
index 10f713f840b7..082b22cdf0bb 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/PrimaryRange.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/PrimaryRange.java
@@ -1,77 +1,27 @@
-/*
- * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2022, 2022, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
 package com.oracle.objectfile.debugentry.range;
 
 import com.oracle.objectfile.debugentry.MethodEntry;
 
-public class PrimaryRange extends Range {
-    /**
-     * The first subrange in the range covered by this primary or null if this primary as no
-     * subranges.
-     */
-    protected SubRange firstCallee;
-    /**
-     * The last subrange in the range covered by this primary.
-     */
-    protected SubRange lastCallee;
-
-    protected PrimaryRange(MethodEntry methodEntry, long lo, long hi, int line) {
-        super(methodEntry, lo, hi, line, -1);
-        this.firstCallee = null;
-        this.lastCallee = null;
-    }
+public class PrimaryRange extends CallRange {
+    private final long codeOffset;
 
-    @Override
-    public boolean isPrimary() {
-        return true;
+    protected PrimaryRange(MethodEntry methodEntry, int lo, int hi, int line, long codeOffset) {
+        super(null, methodEntry, lo, hi, line, null, -1);
+        this.codeOffset = codeOffset;
     }
 
     @Override
-    protected void addCallee(SubRange callee) {
-        assert this.lo <= callee.lo;
-        assert this.hi >= callee.hi;
-        assert callee.caller == this;
-        assert callee.siblingCallee == null;
-        if (this.firstCallee == null) {
-            assert this.lastCallee == null;
-            this.firstCallee = this.lastCallee = callee;
-        } else {
-            this.lastCallee.siblingCallee = callee;
-            this.lastCallee = callee;
-        }
+    public long getCodeOffset() {
+        return codeOffset;
     }
 
     @Override
-    public SubRange getFirstCallee() {
-        return firstCallee;
+    public boolean isPrimary() {
+        return true;
     }
 
     @Override
-    public boolean isLeaf() {
-        return firstCallee == null;
+    public PrimaryRange getPrimary() {
+        return this;
     }
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java
index 748d6c4eddd7..19da7c57e9be 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java
@@ -27,14 +27,21 @@
 package com.oracle.objectfile.debugentry.range;
 
 import com.oracle.objectfile.debugentry.FileEntry;
+import com.oracle.objectfile.debugentry.LocalEntry;
+import com.oracle.objectfile.debugentry.LocalValueEntry;
 import com.oracle.objectfile.debugentry.MethodEntry;
 import com.oracle.objectfile.debugentry.TypeEntry;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalValueInfo;
+import com.oracle.objectfile.debuginfo.DebugInfoProvider;
+import jdk.vm.ci.meta.JavaConstant;
+import jdk.vm.ci.meta.JavaKind;
+import jdk.vm.ci.meta.PrimitiveConstant;
 
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
 
 /**
  * Details of a specific address range in a compiled method either a primary range identifying a
@@ -70,12 +77,24 @@
  * represented by the parent call range at level N.
  */
 public abstract class Range {
-    private static final String CLASS_DELIMITER = ".";
-    protected final MethodEntry methodEntry;
-    protected final long lo;
-    protected long hi;
-    protected final int line;
-    protected final int depth;
+    private final MethodEntry methodEntry;
+    private final int loOffset;
+    private int hiOffset;
+    private final int line;
+    private final int depth;
+
+    /**
+     * The range for the caller or the primary range when this range is for top level method code.
+     */
+    private final CallRange caller;
+
+    private final PrimaryRange primary;
+
+    /**
+     * Values for the associated method's local and parameter variables that are available or,
+     * alternatively, marked as invalid in this range.
+     */
+    private final List<LocalValueEntry> localValueInfos = new ArrayList<>();
 
     /**
      * Create a primary range representing the root of the subrange tree for a top level compiled
@@ -87,8 +106,8 @@ public abstract class Range {
      * @param line the line number associated with the range
      * @return a new primary range to serve as the root of the subrange tree.
      */
-    public static PrimaryRange createPrimary(MethodEntry methodEntry, long lo, long hi, int line) {
-        return new PrimaryRange(methodEntry, lo, hi, line);
+    public static PrimaryRange createPrimary(MethodEntry methodEntry, int lo, int hi, int line, long codeOffset) {
+        return new PrimaryRange(methodEntry, lo, hi, line, codeOffset);
     }
 
     /**
@@ -99,57 +118,110 @@ public static PrimaryRange createPrimary(MethodEntry methodEntry, long lo, long
      * @param lo the lowest address included in the range.
      * @param hi the first address above the highest address in the range.
      * @param line the line number associated with the range
-     * @param primary the primary range to which this subrange belongs
      * @param caller the range for which this is a subrange, either an inlined call range or the
      *            primary range.
      * @param isLeaf true if this is a leaf range with no subranges
      * @return a new subrange to be linked into the range tree below the primary range.
      */
-    public static SubRange createSubrange(MethodEntry methodEntry, long lo, long hi, int line, PrimaryRange primary, Range caller, boolean isLeaf) {
-        assert primary != null;
-        assert primary.isPrimary();
+    public static Range createSubrange(PrimaryRange primary, MethodEntry methodEntry, int lo, int hi, int line, CallRange caller, boolean isLeaf, boolean isInitialRange) {
+        assert caller != null;
+        if (caller != primary) {
+            methodEntry.setInlined();
+        }
+        Range callee;
         if (isLeaf) {
-            return new LeafRange(methodEntry, lo, hi, line, primary, caller);
+            callee = new LeafRange(primary, methodEntry, lo, hi, line, caller, caller.getDepth() + 1);
         } else {
-            return new CallRange(methodEntry, lo, hi, line, primary, caller);
+            callee = new CallRange(primary, methodEntry, lo, hi, line, caller, caller.getDepth() + 1);
         }
+
+        caller.addCallee(callee, isInitialRange);
+        return callee;
     }
 
-    protected Range(MethodEntry methodEntry, long lo, long hi, int line, int depth) {
+    public static Range createSubrange(PrimaryRange primary, MethodEntry methodEntry, int lo, int hi, int line, CallRange caller, boolean isLeaf) {
+        return createSubrange(primary, methodEntry, lo, hi, line, caller, isLeaf, false);
+    }
+
+    protected Range(PrimaryRange primary, MethodEntry methodEntry, int loOffset, int hiOffset, int line, CallRange caller, int depth) {
         assert methodEntry != null;
+        this.primary = primary;
         this.methodEntry = methodEntry;
-        this.lo = lo;
-        this.hi = hi;
+        this.loOffset = loOffset;
+        this.hiOffset = hiOffset;
         this.line = line;
+        this.caller = caller;
         this.depth = depth;
     }
 
-    protected abstract void addCallee(SubRange callee);
+    public Range split(int stackDecrement, int frameSize, int preExtendFrameSize) {
+        // this should be for an initial range extending beyond the stack decrement
+        assert loOffset == 0 && loOffset < stackDecrement && stackDecrement < hiOffset : "invalid split request";
+        Range newRange = Range.createSubrange(this.primary, this.methodEntry, stackDecrement, this.hiOffset, this.line, this.caller, this.isLeaf());
+        this.hiOffset = stackDecrement;
+
+        // TODO fix this properly
+        this.caller.removeCallee(newRange);
+        this.caller.insertCallee(newRange, 1);
+
+        for (LocalValueEntry localInfo : this.localValueInfos) {
+            if (localInfo.localKind() == DebugInfoProvider.LocalValueKind.STACK) {
+                // need to redefine the value for this param using a stack slot value
+                // that allows for the stack being extended by framesize. however we
+                // also need to remove any adjustment that was made to allow for the
+                // difference between the caller SP and the pre-extend callee SP
+                // because of a stacked return address.
+                int adjustment = frameSize - preExtendFrameSize;
+                newRange.localValueInfos.add(new LocalValueEntry(localInfo.line(), localInfo.name(), localInfo.type(), localInfo.regIndex(), localInfo.stackSlot() + adjustment, localInfo.heapOffset(), localInfo.constant(), localInfo.localKind(), localInfo.local()));
+            } else {
+                newRange.localValueInfos.add(localInfo);
+            }
+        }
+        return newRange;
+    }
 
     public boolean contains(Range other) {
-        return (lo <= other.lo && hi >= other.hi);
+        return (loOffset <= other.loOffset && hiOffset >= other.hiOffset);
     }
 
-    public abstract boolean isPrimary();
+    public boolean isPrimary() {
+        return false;
+    }
 
     public String getClassName() {
-        return methodEntry.ownerType().getTypeName();
+        return methodEntry.getOwnerType().getTypeName();
     }
 
     public String getMethodName() {
-        return methodEntry.methodName();
+        return methodEntry.getMethodName();
     }
 
     public String getSymbolName() {
         return methodEntry.getSymbolName();
     }
 
-    public long getHi() {
-        return hi;
+    public int getHiOffset() {
+        return hiOffset;
+    }
+
+    public int getLoOffset() {
+        return loOffset;
+    }
+
+    public long getCodeOffset() {
+        return primary.getCodeOffset();
     }
 
     public long getLo() {
-        return lo;
+        return getCodeOffset() + loOffset;
+    }
+
+    public long getHi() {
+        return getCodeOffset() + hiOffset;
+    }
+
+    public PrimaryRange getPrimary() {
+        return primary;
     }
 
     public int getLine() {
@@ -157,31 +229,27 @@ public int getLine() {
     }
 
     public String getFullMethodName() {
-        return constructClassAndMethodName();
+        return getExtendedMethodName(false);
     }
 
     public String getFullMethodNameWithParams() {
-        return constructClassAndMethodNameWithParams();
+        return getExtendedMethodName(true);
     }
 
     public boolean isDeoptTarget() {
         return methodEntry.isDeopt();
     }
 
-    private String getExtendedMethodName(boolean includeClass, boolean includeParams, boolean includeReturnType) {
+    private String getExtendedMethodName(boolean includeParams) {
         StringBuilder builder = new StringBuilder();
-        if (includeReturnType && methodEntry.getValueType().getTypeName().length() > 0) {
-            builder.append(methodEntry.getValueType().getTypeName());
-            builder.append(' ');
-        }
-        if (includeClass && getClassName() != null) {
+        if (getClassName() != null) {
             builder.append(getClassName());
-            builder.append(CLASS_DELIMITER);
+            builder.append(".");
         }
         builder.append(getMethodName());
         if (includeParams) {
             builder.append("(");
-            TypeEntry[] paramTypes = methodEntry.getParamTypes();
+            List<TypeEntry> paramTypes = methodEntry.getParamTypes();
             if (paramTypes != null) {
                 String prefix = "";
                 for (TypeEntry t : paramTypes) {
@@ -192,21 +260,9 @@ private String getExtendedMethodName(boolean includeClass, boolean includeParams
             }
             builder.append(')');
         }
-        if (includeReturnType) {
-            builder.append(" ");
-            builder.append(methodEntry.getValueType().getTypeName());
-        }
         return builder.toString();
     }
 
-    private String constructClassAndMethodName() {
-        return getExtendedMethodName(true, false, false);
-    }
-
-    private String constructClassAndMethodNameWithParams() {
-        return getExtendedMethodName(true, true, false);
-    }
-
     public FileEntry getFileEntry() {
         return methodEntry.getFileEntry();
     }
@@ -217,7 +273,7 @@ public int getModifiers() {
 
     @Override
     public String toString() {
-        return String.format("Range(lo=0x%05x hi=0x%05x %s %s:%d)", lo, hi, constructClassAndMethodNameWithParams(), methodEntry.getFullFileName(), line);
+        return String.format("Range(lo=0x%05x hi=0x%05x %s %s:%d)", getLo(), getHi(), getFullMethodNameWithParams(), methodEntry.getFullFileName(), line);
     }
 
     public String getFileName() {
@@ -228,74 +284,129 @@ public MethodEntry getMethodEntry() {
         return methodEntry;
     }
 
-    public abstract SubRange getFirstCallee();
-
     public abstract boolean isLeaf();
 
     public boolean includesInlineRanges() {
-        SubRange child = getFirstCallee();
-        while (child != null && child.isLeaf()) {
-            child = child.getSiblingCallee();
+        for (Range callee : getCallees()) {
+            if (!callee.isLeaf()) {
+                return true;
+            }
         }
-        return child != null;
+        return false;
+    }
+
+    public List<Range> getCallees() {
+        return List.of();
+    }
+
+    public Stream<Range> rangeStream() {
+        return Stream.of(this);
     }
 
     public int getDepth() {
         return depth;
     }
 
-    public HashMap<DebugLocalInfo, List<SubRange>> getVarRangeMap() {
-        MethodEntry calleeMethod;
-        if (isPrimary()) {
-            calleeMethod = getMethodEntry();
-        } else {
-            assert !isLeaf() : "should only be looking up var ranges for inlined calls";
-            calleeMethod = getFirstCallee().getMethodEntry();
-        }
-        HashMap<DebugLocalInfo, List<SubRange>> varRangeMap = new HashMap<>();
-        if (calleeMethod.getThisParam() != null) {
-            varRangeMap.put(calleeMethod.getThisParam(), new ArrayList<>());
-        }
-        for (int i = 0; i < calleeMethod.getParamCount(); i++) {
-            varRangeMap.put(calleeMethod.getParam(i), new ArrayList<>());
-        }
-        for (int i = 0; i < calleeMethod.getLocalCount(); i++) {
-            varRangeMap.put(calleeMethod.getLocal(i), new ArrayList<>());
+    public Map<LocalEntry, List<Range>> getVarRangeMap() {
+        HashMap<LocalEntry, List<Range>> varRangeMap = new HashMap<>();
+        for (Range callee : getCallees()) {
+            for (LocalValueEntry localValue : callee.localValueInfos) {
+                LocalEntry local = localValue.local();
+                if (local != null) {
+                    switch (localValue.localKind()) {
+                        case REGISTER:
+                        case STACK:
+                        case CONSTANT:
+                            varRangeMap.computeIfAbsent(local, l -> new ArrayList<>()).add(callee);
+                            break;
+                        case UNDEFINED:
+                            break;
+                    }
+                }
+            }
         }
-        return updateVarRangeMap(varRangeMap);
+        return varRangeMap;
     }
 
-    public HashMap<DebugLocalInfo, List<SubRange>> updateVarRangeMap(HashMap<DebugLocalInfo, List<SubRange>> varRangeMap) {
-        // leaf subranges of the current range may provide values for param or local vars
-        // of this range's method. find them and index the range so that we can identify
-        // both the local/param and the associated range.
-        SubRange subRange = this.getFirstCallee();
-        while (subRange != null) {
-            addVarRanges(subRange, varRangeMap);
-            subRange = subRange.siblingCallee;
+    public boolean hasLocalValues(LocalEntry local) {
+        for (Range callee : getCallees()) {
+            for (LocalValueEntry localValue : callee.localValueInfos) {
+                if (local == localValue.local()) {
+                    switch (localValue.localKind()) {
+                        case REGISTER:
+                        case STACK:
+                            return true;
+                        case CONSTANT:
+                            JavaConstant constant = localValue.constant();
+                            // can only handle primitive or null constants just now
+                            return constant instanceof PrimitiveConstant || constant.getJavaKind() == JavaKind.Object;
+                        case UNDEFINED:
+                            break;
+                    }
+                }
+            }
         }
-        return varRangeMap;
+        return false;
     }
 
-    public void addVarRanges(SubRange subRange, HashMap<DebugLocalInfo, List<SubRange>> varRangeMap) {
-        int localValueCount = subRange.getLocalValueCount();
-        for (int i = 0; i < localValueCount; i++) {
-            DebugLocalValueInfo localValueInfo = subRange.getLocalValue(i);
-            DebugLocalInfo local = subRange.getLocal(i);
-            if (local != null) {
-                switch (localValueInfo.localKind()) {
-                    case REGISTER:
-                    case STACKSLOT:
-                    case CONSTANT:
-                        List<SubRange> varRanges = varRangeMap.get(local);
-                        assert varRanges != null : "local not present in var to ranges map!";
-                        varRanges.add(subRange);
-                        break;
-                    case UNDEFINED:
-                        break;
-                }
+    public Range getCaller() {
+        return caller;
+    }
+
+    public List<LocalValueEntry> getLocalValues() {
+        return Collections.unmodifiableList(localValueInfos);
+    }
+
+    public int getLocalValueCount() {
+        return localValueInfos.size();
+    }
+
+    public LocalValueEntry getLocalValue(int i) {
+        return localValueInfos.get(i);
+    }
+
+    public LocalEntry getLocal(int i) {
+        return getLocalValue(i).local();
+    }
+
+    public void setLocalValueInfo(List<LocalValueEntry> localValueInfos) {
+        this.localValueInfos.addAll(localValueInfos);
+    }
+
+    public LocalValueEntry lookupValue(LocalEntry local) {
+        for (LocalValueEntry localValue : localValueInfos) {
+            if (localValue.local() == local) {
+                return localValue;
             }
         }
+        return null;
     }
 
+    public boolean tryMerge(Range that) {
+        assert this.caller == that.caller;
+        assert this.isLeaf() == that.isLeaf();
+        assert this.depth == that.depth : "should only compare sibling ranges";
+        assert this.hiOffset <= that.loOffset : "later nodes should not overlap earlier ones";
+        if (this.hiOffset != that.loOffset) {
+            return false;
+        }
+        if (this.methodEntry != that.methodEntry) {
+            return false;
+        }
+        if (this.line != that.line) {
+            return false;
+        }
+        if (this.localValueInfos.size() != that.localValueInfos.size()) {
+            return false;
+        }
+        for (int i = 0; i < this.localValueInfos.size(); i++) {
+            if (!this.getLocalValue(i).equals(that.getLocalValue(i))) {
+                return false;
+            }
+        }
+        // merging just requires updating lo and hi range as everything else is equal
+        this.hiOffset = that.hiOffset;
+        caller.removeCallee(that);
+        return true;
+    }
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/SubRange.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/SubRange.java
deleted file mode 100644
index 58b2178b42e2..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/SubRange.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2022, 2022, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.debugentry.range;
-
-import com.oracle.objectfile.debugentry.MethodEntry;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider;
-
-public abstract class SubRange extends Range {
-    private static final DebugInfoProvider.DebugLocalInfo[] EMPTY_LOCAL_INFOS = new DebugInfoProvider.DebugLocalInfo[0];
-    /**
-     * The root of the call tree the subrange belongs to.
-     */
-    private final PrimaryRange primary;
-    /**
-     * The range for the caller or the primary range when this range is for top level method code.
-     */
-    protected Range caller;
-    /**
-     * A link to a sibling callee, i.e., a range sharing the same caller with this range.
-     */
-    protected SubRange siblingCallee;
-    /**
-     * Values for the associated method's local and parameter variables that are available or,
-     * alternatively, marked as invalid in this range.
-     */
-    private DebugInfoProvider.DebugLocalValueInfo[] localValueInfos;
-    /**
-     * The set of local or parameter variables with which each corresponding local value in field
-     * localValueInfos is associated. Local values which are associated with the same local or
-     * parameter variable will share the same reference in the corresponding array entries. Local
-     * values with which no local variable can be associated will have a null reference in the
-     * corresponding array. The latter case can happen when a local value has an invalid slot or
-     * when a local value that maps to a parameter slot has a different name or type to the
-     * parameter.
-     */
-    private DebugInfoProvider.DebugLocalInfo[] localInfos;
-
-    @SuppressWarnings("this-escape")
-    protected SubRange(MethodEntry methodEntry, long lo, long hi, int line, PrimaryRange primary, Range caller) {
-        super(methodEntry, lo, hi, line, (caller == null ? 0 : caller.depth + 1));
-        this.caller = caller;
-        if (caller != null) {
-            caller.addCallee(this);
-        }
-        assert primary != null;
-        this.primary = primary;
-    }
-
-    public Range getPrimary() {
-        return primary;
-    }
-
-    @Override
-    public boolean isPrimary() {
-        return false;
-    }
-
-    public Range getCaller() {
-        return caller;
-    }
-
-    @Override
-    public abstract SubRange getFirstCallee();
-
-    @Override
-    public abstract boolean isLeaf();
-
-    public int getLocalValueCount() {
-        return localValueInfos.length;
-    }
-
-    public DebugInfoProvider.DebugLocalValueInfo getLocalValue(int i) {
-        assert i >= 0 && i < localValueInfos.length : "bad index";
-        return localValueInfos[i];
-    }
-
-    public DebugInfoProvider.DebugLocalInfo getLocal(int i) {
-        assert i >= 0 && i < localInfos.length : "bad index";
-        return localInfos[i];
-    }
-
-    public void setLocalValueInfo(DebugInfoProvider.DebugLocalValueInfo[] localValueInfos) {
-        int len = localValueInfos.length;
-        this.localValueInfos = localValueInfos;
-        this.localInfos = (len > 0 ? new DebugInfoProvider.DebugLocalInfo[len] : EMPTY_LOCAL_INFOS);
-        // set up mapping from local values to local variables
-        for (int i = 0; i < len; i++) {
-            localInfos[i] = methodEntry.recordLocal(localValueInfos[i]);
-        }
-    }
-
-    public DebugInfoProvider.DebugLocalValueInfo lookupValue(DebugInfoProvider.DebugLocalInfo local) {
-        int localValueCount = getLocalValueCount();
-        for (int i = 0; i < localValueCount; i++) {
-            if (getLocal(i) == local) {
-                return getLocalValue(i);
-            }
-        }
-        return null;
-    }
-
-    public SubRange getSiblingCallee() {
-        return siblingCallee;
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java
index a4ffa8bb144c..50b91f99f823 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java
@@ -26,23 +26,22 @@
 
 package com.oracle.objectfile.debuginfo;
 
-import java.nio.file.Path;
 import java.util.List;
-import java.util.function.Consumer;
-import java.util.stream.Stream;
 
-import jdk.graal.compiler.debug.DebugContext;
-import jdk.vm.ci.meta.JavaConstant;
-import jdk.vm.ci.meta.JavaKind;
-import jdk.vm.ci.meta.ResolvedJavaMethod;
-import jdk.vm.ci.meta.ResolvedJavaType;
+import com.oracle.objectfile.debugentry.CompiledMethodEntry;
+import com.oracle.objectfile.debugentry.StringTable;
+import com.oracle.objectfile.debugentry.TypeEntry;
 
 /**
  * Interfaces used to allow a native image to communicate details of types, code and data to the
  * underlying object file so that the latter can insert appropriate debug info.
  */
 public interface DebugInfoProvider {
+
+    void installDebugInfo();
+
     boolean useHeapBase();
+    boolean isRuntimeCompilation();
 
     /**
      * Number of bits oops are left shifted by when using compressed oops.
@@ -71,382 +70,23 @@ public interface DebugInfoProvider {
 
     int compiledCodeMax();
 
-    /**
-     * An interface implemented by items that can be located in a file.
-     */
-    interface DebugFileInfo {
-        /**
-         * @return the name of the file containing a file element excluding any path.
-         */
-        String fileName();
-
-        /**
-         * @return a relative path to the file containing a file element derived from its package
-         *         name or {@code null} if the element is in the empty package.
-         */
-        Path filePath();
-    }
-
-    interface DebugTypeInfo extends DebugFileInfo {
-        ResolvedJavaType idType();
-
-        enum DebugTypeKind {
-            PRIMITIVE,
-            ENUM,
-            INSTANCE,
-            INTERFACE,
-            ARRAY,
-            HEADER,
-            FOREIGN;
-
-            @Override
-            public String toString() {
-                switch (this) {
-                    case PRIMITIVE:
-                        return "primitive";
-                    case ENUM:
-                        return "enum";
-                    case INSTANCE:
-                        return "instance";
-                    case INTERFACE:
-                        return "interface";
-                    case ARRAY:
-                        return "array";
-                    case HEADER:
-                        return "header";
-                    case FOREIGN:
-                        return "foreign";
-                    default:
-                        return "???";
-                }
-            }
-        }
-
-        void debugContext(Consumer<DebugContext> action);
-
-        /**
-         * @return the fully qualified name of the debug type.
-         */
-        String typeName();
-
-        /**
-         * @return a 64bit type signature to uniquely identify the type
-         */
-        long typeSignature(String prefix);
-
-        DebugTypeKind typeKind();
-
-        /**
-         * returns the offset in the heap at which the java.lang.Class instance which models this
-         * class is located or -1 if no such instance exists for this class.
-         *
-         * @return the offset of the java.lang.Class instance which models this class or -1.
-         */
-        long classOffset();
-
-        int size();
-    }
-
-    interface DebugInstanceTypeInfo extends DebugTypeInfo {
-        String loaderName();
-
-        Stream<DebugFieldInfo> fieldInfoProvider();
-
-        Stream<DebugMethodInfo> methodInfoProvider();
-
-        ResolvedJavaType superClass();
-
-        Stream<ResolvedJavaType> interfaces();
-    }
-
-    interface DebugEnumTypeInfo extends DebugInstanceTypeInfo {
-    }
-
-    interface DebugInterfaceTypeInfo extends DebugInstanceTypeInfo {
-    }
-
-    interface DebugForeignTypeInfo extends DebugInstanceTypeInfo {
-        String typedefName();
-
-        boolean isWord();
-
-        boolean isStruct();
-
-        boolean isPointer();
+    StringTable getStringTable();
 
-        boolean isIntegral();
+    List<TypeEntry> typeEntries();
+    List<CompiledMethodEntry> compiledMethodEntries();
 
-        boolean isFloat();
+    String cachePath();
 
-        boolean isSigned();
 
-        ResolvedJavaType parent();
-
-        ResolvedJavaType pointerTo();
-    }
-
-    interface DebugArrayTypeInfo extends DebugTypeInfo {
-        int baseSize();
-
-        int lengthOffset();
-
-        ResolvedJavaType elementType();
-
-        Stream<DebugFieldInfo> fieldInfoProvider();
-    }
-
-    interface DebugPrimitiveTypeInfo extends DebugTypeInfo {
-        /*
-         * NUMERIC excludes LOGICAL types boolean and void
-         */
-        int FLAG_NUMERIC = 1 << 0;
-        /*
-         * INTEGRAL excludes FLOATING types float and double
-         */
-        int FLAG_INTEGRAL = 1 << 1;
-        /*
-         * SIGNED excludes UNSIGNED type char
-         */
-        int FLAG_SIGNED = 1 << 2;
-
-        int bitCount();
-
-        char typeChar();
-
-        int flags();
-    }
-
-    interface DebugHeaderTypeInfo extends DebugTypeInfo {
-
-        Stream<DebugFieldInfo> fieldInfoProvider();
-    }
-
-    interface DebugMemberInfo extends DebugFileInfo {
-
-        String name();
-
-        ResolvedJavaType valueType();
-
-        int modifiers();
+    enum FrameSizeChangeType {
+        EXTEND,
+        CONTRACT;
     }
 
-    interface DebugFieldInfo extends DebugMemberInfo {
-        int offset();
-
-        int size();
-
-        boolean isEmbedded();
+    enum LocalValueKind {
+        UNDEFINED,
+        REGISTER,
+        STACK,
+        CONSTANT;
     }
-
-    interface DebugMethodInfo extends DebugMemberInfo {
-        /**
-         * @return the line number for the outer or inlined segment.
-         */
-        int line();
-
-        /**
-         * @return an array of DebugLocalInfo objects holding details of this method's parameters
-         */
-        DebugLocalInfo[] getParamInfo();
-
-        /**
-         * @return a DebugLocalInfo objects holding details of the target instance parameter this if
-         *         the method is an instance method or null if it is a static method.
-         */
-        DebugLocalInfo getThisParamInfo();
-
-        /**
-         * @return the symbolNameForMethod string
-         */
-        String symbolNameForMethod();
-
-        /**
-         * @return true if this method has been compiled in as a deoptimization target
-         */
-        boolean isDeoptTarget();
-
-        /**
-         * @return true if this method is a constructor.
-         */
-        boolean isConstructor();
-
-        /**
-         * @return true if this is a virtual method. In Graal a virtual method can become
-         *         non-virtual if all other implementations are non-reachable.
-         */
-        boolean isVirtual();
-
-        /**
-         * @return the offset into the virtual function table for this method if virtual
-         */
-        int vtableOffset();
-
-        /**
-         * @return true if this method is an override of another method.
-         */
-        boolean isOverride();
-
-        /*
-         * Return the unique type that owns this method. <p/>
-         *
-         * @return the unique type that owns this method
-         */
-        ResolvedJavaType ownerType();
-
-        /*
-         * Return the unique identifier for this method. The result can be used to unify details of
-         * methods presented via interface DebugTypeInfo with related details of compiled methods
-         * presented via interface DebugRangeInfo and of call frame methods presented via interface
-         * DebugLocationInfo. <p/>
-         *
-         * @return the unique identifier for this method
-         */
-        ResolvedJavaMethod idMethod();
-    }
-
-    /**
-     * Access details of a compiled top level or inline method producing the code in a specific
-     * {@link com.oracle.objectfile.debugentry.range.Range}.
-     */
-    interface DebugRangeInfo extends DebugMethodInfo {
-
-        /**
-         * @return the lowest address containing code generated for an outer or inlined code segment
-         *         reported at this line represented as an offset into the code segment.
-         */
-        long addressLo();
-
-        /**
-         * @return the first address above the code generated for an outer or inlined code segment
-         *         reported at this line represented as an offset into the code segment.
-         */
-        long addressHi();
-    }
-
-    /**
-     * Access details of a specific compiled method.
-     */
-    interface DebugCodeInfo extends DebugRangeInfo {
-        void debugContext(Consumer<DebugContext> action);
-
-        /**
-         * @return a stream of records detailing source local var and line locations within the
-         *         compiled method.
-         */
-        Stream<DebugLocationInfo> locationInfoProvider();
-
-        /**
-         * @return the size of the method frame between prologue and epilogue.
-         */
-        int getFrameSize();
-
-        /**
-         * @return a list of positions at which the stack is extended to a full frame or torn down
-         *         to an empty frame
-         */
-        List<DebugFrameSizeChange> getFrameSizeChanges();
-    }
-
-    /**
-     * Access details of a specific heap object.
-     */
-    interface DebugDataInfo {
-        void debugContext(Consumer<DebugContext> action);
-
-        String getProvenance();
-
-        String getTypeName();
-
-        String getPartition();
-
-        long getOffset();
-
-        long getSize();
-    }
-
-    /**
-     * Access details of code generated for a specific outer or inlined method at a given line
-     * number.
-     */
-    interface DebugLocationInfo extends DebugRangeInfo {
-        /**
-         * @return the {@link DebugLocationInfo} of the nested inline caller-line
-         */
-        DebugLocationInfo getCaller();
-
-        /**
-         * @return a stream of {@link DebugLocalValueInfo} objects identifying local or parameter
-         *         variables present in the frame of the current range.
-         */
-        DebugLocalValueInfo[] getLocalValueInfo();
-
-        boolean isLeaf();
-    }
-
-    /**
-     * A DebugLocalInfo details a local or parameter variable recording its name and type, the
-     * (abstract machine) local slot index it resides in and the number of slots it occupies.
-     */
-    interface DebugLocalInfo {
-        ResolvedJavaType valueType();
-
-        String name();
-
-        String typeName();
-
-        int slot();
-
-        int slotCount();
-
-        JavaKind javaKind();
-
-        int line();
-    }
-
-    /**
-     * A DebugLocalValueInfo details the value a local or parameter variable present in a specific
-     * frame. The value may be undefined. If not then the instance records its type and either its
-     * (constant) value or the register or stack location in which the value resides.
-     */
-    interface DebugLocalValueInfo extends DebugLocalInfo {
-        enum LocalKind {
-            UNDEFINED,
-            REGISTER,
-            STACKSLOT,
-            CONSTANT
-        }
-
-        LocalKind localKind();
-
-        int regIndex();
-
-        int stackSlot();
-
-        long heapOffset();
-
-        JavaConstant constantValue();
-    }
-
-    interface DebugFrameSizeChange {
-        enum Type {
-            EXTEND,
-            CONTRACT
-        }
-
-        int getOffset();
-
-        DebugFrameSizeChange.Type getType();
-    }
-
-    @SuppressWarnings("unused")
-    Stream<DebugTypeInfo> typeInfoProvider();
-
-    Stream<DebugCodeInfo> codeInfoProvider();
-
-    @SuppressWarnings("unused")
-    Stream<DebugDataInfo> dataInfoProvider();
-
-    Path getCachePath();
-
-    void recordActivity();
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/ELFObjectFile.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/ELFObjectFile.java
index bfe8cc4ebac5..76af5bce5409 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/ELFObjectFile.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/ELFObjectFile.java
@@ -36,14 +36,7 @@
 import java.util.Set;
 
 import com.oracle.objectfile.elf.dwarf.DwarfLocSectionImpl;
-import com.oracle.objectfile.runtime.dwarf.RuntimeDwarfAbbrevSectionImpl;
-import com.oracle.objectfile.runtime.dwarf.RuntimeDwarfDebugInfo;
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider;
-import com.oracle.objectfile.runtime.dwarf.RuntimeDwarfFrameSectionImpl;
-import com.oracle.objectfile.runtime.dwarf.RuntimeDwarfInfoSectionImpl;
-import com.oracle.objectfile.runtime.dwarf.RuntimeDwarfLineSectionImpl;
-import com.oracle.objectfile.runtime.dwarf.RuntimeDwarfLocSectionImpl;
-import com.oracle.objectfile.runtime.dwarf.RuntimeDwarfStrSectionImpl;
+import com.oracle.objectfile.elf.dwarf.DwarfDebugInfo;
 import org.graalvm.nativeimage.ImageSingletons;
 import org.graalvm.nativeimage.Platform;
 
@@ -57,7 +50,6 @@
 import com.oracle.objectfile.debuginfo.DebugInfoProvider;
 import com.oracle.objectfile.elf.dwarf.DwarfARangesSectionImpl;
 import com.oracle.objectfile.elf.dwarf.DwarfAbbrevSectionImpl;
-import com.oracle.objectfile.elf.dwarf.DwarfDebugInfo;
 import com.oracle.objectfile.elf.dwarf.DwarfFrameSectionImpl;
 import com.oracle.objectfile.elf.dwarf.DwarfInfoSectionImpl;
 import com.oracle.objectfile.elf.dwarf.DwarfLineSectionImpl;
@@ -1182,13 +1174,13 @@ protected int getMinimumFileSize() {
     @Override
     public void installDebugInfo(DebugInfoProvider debugInfoProvider) {
         DwarfDebugInfo dwarfSections = new DwarfDebugInfo(getMachine(), getByteOrder());
+
         /* We need an implementation for each generated DWARF section. */
         DwarfStrSectionImpl elfStrSectionImpl = dwarfSections.getStrSectionImpl();
         DwarfAbbrevSectionImpl elfAbbrevSectionImpl = dwarfSections.getAbbrevSectionImpl();
         DwarfFrameSectionImpl frameSectionImpl = dwarfSections.getFrameSectionImpl();
         DwarfLocSectionImpl elfLocSectionImpl = dwarfSections.getLocSectionImpl();
         DwarfInfoSectionImpl elfInfoSectionImpl = dwarfSections.getInfoSectionImpl();
-        // DwarfTypesSectionImpl elfTypesSectionImpl = dwarfSections.getTypesSectionImpl();
         DwarfARangesSectionImpl elfARangesSectionImpl = dwarfSections.getARangesSectionImpl();
         DwarfRangesSectionImpl elfRangesSectionImpl = dwarfSections.getRangesSectionImpl();
         DwarfLineSectionImpl elfLineSectionImpl = dwarfSections.getLineSectionImpl();
@@ -1198,7 +1190,6 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) {
         newDebugSection(frameSectionImpl.getSectionName(), frameSectionImpl);
         newDebugSection(elfLocSectionImpl.getSectionName(), elfLocSectionImpl);
         newDebugSection(elfInfoSectionImpl.getSectionName(), elfInfoSectionImpl);
-        // newDebugSection(elfTypesSectionImpl.getSectionName(), elfTypesSectionImpl);
         newDebugSection(elfARangesSectionImpl.getSectionName(), elfARangesSectionImpl);
         newDebugSection(elfRangesSectionImpl.getSectionName(), elfRangesSectionImpl);
         newDebugSection(elfLineSectionImpl.getSectionName(), elfLineSectionImpl);
@@ -1210,7 +1201,6 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) {
          */
         createDefinedSymbol(elfAbbrevSectionImpl.getSectionName(), elfAbbrevSectionImpl.getElement(), 0, 0, false, false);
         createDefinedSymbol(elfInfoSectionImpl.getSectionName(), elfInfoSectionImpl.getElement(), 0, 0, false, false);
-        // createDefinedSymbol(elfTypesSectionImpl.getSectionName(), elfTypesSectionImpl.getElement(), 0, 0, false, false);
         createDefinedSymbol(elfLineSectionImpl.getSectionName(), elfLineSectionImpl.getElement(), 0, 0, false, false);
         createDefinedSymbol(elfStrSectionImpl.getSectionName(), elfStrSectionImpl.getElement(), 0, 0, false, false);
         createDefinedSymbol(elfRangesSectionImpl.getSectionName(), elfRangesSectionImpl.getElement(), 0, 0, false, false);
@@ -1237,53 +1227,6 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) {
         dwarfSections.installDebugInfo(debugInfoProvider);
     }
 
-    @Override
-    public void installRuntimeDebugInfo(RuntimeDebugInfoProvider runtimeDebugInfoProvider) {
-        RuntimeDwarfDebugInfo dwarfSections = new RuntimeDwarfDebugInfo(getMachine(), getByteOrder());
-        /* We need an implementation for each generated DWARF section. */
-        RuntimeDwarfStrSectionImpl elfStrSectionImpl = dwarfSections.getStrSectionImpl();
-        RuntimeDwarfAbbrevSectionImpl elfAbbrevSectionImpl = dwarfSections.getAbbrevSectionImpl();
-        RuntimeDwarfFrameSectionImpl frameSectionImpl = dwarfSections.getFrameSectionImpl();
-        RuntimeDwarfLocSectionImpl elfLocSectionImpl = dwarfSections.getLocSectionImpl();
-        RuntimeDwarfInfoSectionImpl elfInfoSectionImpl = dwarfSections.getInfoSectionImpl();
-        RuntimeDwarfLineSectionImpl elfLineSectionImpl = dwarfSections.getLineSectionImpl();
-        /* Now we can create the section elements with empty content. */
-        newDebugSection(elfStrSectionImpl.getSectionName(), elfStrSectionImpl);
-        newDebugSection(elfAbbrevSectionImpl.getSectionName(), elfAbbrevSectionImpl);
-        newDebugSection(frameSectionImpl.getSectionName(), frameSectionImpl);
-        newDebugSection(elfLocSectionImpl.getSectionName(), elfLocSectionImpl);
-        newDebugSection(elfInfoSectionImpl.getSectionName(), elfInfoSectionImpl);
-        newDebugSection(elfLineSectionImpl.getSectionName(), elfLineSectionImpl);
-        /*
-         * Add symbols for the base of all DWARF sections whose content may need to be referenced
-         * using a section global offset. These need to be written using a base relative reloc so
-         * that they get updated if the section is merged with DWARF content from other ELF objects
-         * during image linking.
-         */
-        createDefinedSymbol(elfAbbrevSectionImpl.getSectionName(), elfAbbrevSectionImpl.getElement(), 0, 0, false, false);
-        createDefinedSymbol(elfInfoSectionImpl.getSectionName(), elfInfoSectionImpl.getElement(), 0, 0, false, false);
-        createDefinedSymbol(elfLineSectionImpl.getSectionName(), elfLineSectionImpl.getElement(), 0, 0, false, false);
-        createDefinedSymbol(elfStrSectionImpl.getSectionName(), elfStrSectionImpl.getElement(), 0, 0, false, false);
-        createDefinedSymbol(elfLocSectionImpl.getSectionName(), elfLocSectionImpl.getElement(), 0, 0, false, false);
-        /*
-         * The byte[] for each implementation's content are created and written under
-         * getOrDecideContent. Doing that ensures that all dependent sections are filled in and then
-         * sized according to the declared dependencies. However, if we leave it at that then
-         * associated reloc sections only get created when the first reloc is inserted during
-         * content write that's too late for them to have layout constraints included in the layout
-         * decision set and causes an NPE during reloc section write. So we need to create the
-         * relevant reloc sections here in advance.
-         */
-        elfStrSectionImpl.getOrCreateRelocationElement(0);
-        elfAbbrevSectionImpl.getOrCreateRelocationElement(0);
-        frameSectionImpl.getOrCreateRelocationElement(0);
-        elfInfoSectionImpl.getOrCreateRelocationElement(0);
-        elfLocSectionImpl.getOrCreateRelocationElement(0);
-        elfLineSectionImpl.getOrCreateRelocationElement(0);
-        /* Ok now we can populate the debug info model. */
-        dwarfSections.installDebugInfo(runtimeDebugInfoProvider);
-    }
-
     @SuppressWarnings("unused")
     static boolean useExplicitAddend(long addend) {
         // For now, we are always using explicit addends
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfARangesSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfARangesSectionImpl.java
index 91ba489360c5..ca06b1514d3f 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfARangesSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfARangesSectionImpl.java
@@ -91,8 +91,8 @@ public void createContent() {
          */
         assert !contentByteArrayCreated();
         Cursor byteCount = new Cursor();
-        instanceClassStream().filter(ClassEntry::hasCompiledEntries).forEachOrdered(classEntry -> {
-            byteCount.add(entrySize(classEntry.compiledEntryCount()));
+        instanceClassStream().filter(ClassEntry::hasCompiledMethods).forEachOrdered(classEntry -> {
+            byteCount.add(entrySize(classEntry.compiledMethods().size()));
         });
         byte[] buffer = new byte[byteCount.get()];
         super.setContent(buffer);
@@ -117,7 +117,7 @@ public byte[] getOrDecideContent(Map<ObjectFile.Element, LayoutDecisionMap> alre
         LayoutDecisionMap decisionMap = alreadyDecided.get(textElement);
         if (decisionMap != null) {
             Object valueObj = decisionMap.getDecidedValue(LayoutDecision.Kind.VADDR);
-            if (valueObj != null && valueObj instanceof Number) {
+            if (valueObj instanceof Number) {
                 /*
                  * This may not be the final vaddr for the text segment but it will be close enough
                  * to make debug easier i.e. to within a 4k page or two.
@@ -138,11 +138,11 @@ public void writeContent(DebugContext context) {
         enableLog(context, cursor.get());
 
         log(context, "  [0x%08x] DEBUG_ARANGES", cursor.get());
-        instanceClassStream().filter(ClassEntry::hasCompiledEntries).forEachOrdered(classEntry -> {
+        instanceClassStream().filter(ClassEntry::hasCompiledMethods).forEachOrdered(classEntry -> {
             int lengthPos = cursor.get();
             log(context, "  [0x%08x] class %s CU 0x%x", lengthPos, classEntry.getTypeName(), getCUIndex(classEntry));
             cursor.set(writeHeader(getCUIndex(classEntry), buffer, cursor.get()));
-            classEntry.compiledEntries().forEachOrdered(compiledMethodEntry -> {
+            classEntry.compiledMethods().forEach(compiledMethodEntry -> {
                 cursor.set(writeARange(context, compiledMethodEntry, buffer, cursor.get()));
             });
             // write two terminating zeroes
@@ -176,9 +176,9 @@ private int writeHeader(int cuIndex, byte[] buffer, int p) {
 
     int writeARange(DebugContext context, CompiledMethodEntry compiledMethod, byte[] buffer, int p) {
         int pos = p;
-        Range primary = compiledMethod.getPrimary();
+        Range primary = compiledMethod.primary();
         log(context, "  [0x%08x] %016x %016x %s", pos, debugTextBase + primary.getLo(), primary.getHi() - primary.getLo(), primary.getFullMethodNameWithParams());
-        pos = writeRelocatableCodeOffset(primary.getLo(), buffer, pos);
+        pos = writeCodeOffset(primary.getLo(), buffer, pos);
         pos = writeLong(primary.getHi() - primary.getLo(), buffer, pos);
         return pos;
     }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java
index ead4598414a3..a0a465e97d83 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java
@@ -914,60 +914,62 @@ public void writeContent(DebugContext context) {
 
     public int writeAbbrevs(DebugContext context, byte[] buffer, int p) {
         int pos = p;
+        // Write Abbrevs that are shared for AOT and Runtime compilation
         pos = writeCompileUnitAbbrevs(context, buffer, pos);
-        pos = writeTypeUnitAbbrev(context, buffer, pos);
-
-        pos = writePrimitiveTypeAbbrev(context, buffer, pos);
-        pos = writeVoidTypeAbbrev(context, buffer, pos);
-        pos = writeObjectHeaderAbbrev(context, buffer, pos);
-        pos = writeClassConstantAbbrev(context, buffer, pos);
-
         pos = writeNamespaceAbbrev(context, buffer, pos);
-
         pos = writeClassLayoutAbbrevs(context, buffer, pos);
-        pos = writeClassReferenceAbbrevs(context, buffer, pos);
         pos = writeMethodDeclarationAbbrevs(context, buffer, pos);
-        pos = writeFieldDeclarationAbbrevs(context, buffer, pos);
-
-        pos = writeArrayLayoutAbbrev(context, buffer, pos);
-
-        pos = writeInterfaceLayoutAbbrev(context, buffer, pos);
-
-        pos = writeForeignTypedefAbbrev(context, buffer, pos);
-        pos = writeForeignStructAbbrev(context, buffer, pos);
-
-        pos = writeHeaderFieldAbbrev(context, buffer, pos);
-        pos = writeArrayElementFieldAbbrev(context, buffer, pos);
-        pos = writeArrayDataTypeAbbrevs(context, buffer, pos);
-        pos = writeArraySubrangeTypeAbbrev(context, buffer, pos);
         pos = writeMethodLocationAbbrev(context, buffer, pos);
-        pos = writeAbstractInlineMethodAbbrev(context, buffer, pos);
-        pos = writeStaticFieldLocationAbbrev(context, buffer, pos);
-        pos = writeSuperReferenceAbbrev(context, buffer, pos);
-        pos = writeInterfaceImplementorAbbrev(context, buffer, pos);
-
-        pos = writeInlinedSubroutineAbbrev(buffer, pos, false);
-        pos = writeInlinedSubroutineAbbrev(buffer, pos, true);
-
-        /*
-         * if we address rebasing is required then we need to use compressed layout types supplied
-         * with a suitable data_location attribute and compressed pointer types to ensure that gdb
-         * converts offsets embedded in static or instance fields to raw pointers. Transformed
-         * addresses are typed using pointers to the underlying layout.
-         *
-         * if address rebasing is not required then a data_location attribute on the layout type
-         * will ensure that address tag bits are removed.
-         */
-        if (dwarfSections.useHeapBase()) {
-            pos = writeCompressedLayoutAbbrev(context, buffer, pos);
-        }
+        pos = writeInlinedSubroutineAbbrev(buffer, pos);
 
         pos = writeParameterDeclarationAbbrevs(context, buffer, pos);
         pos = writeLocalDeclarationAbbrevs(context, buffer, pos);
-
         pos = writeParameterLocationAbbrevs(context, buffer, pos);
         pos = writeLocalLocationAbbrevs(context, buffer, pos);
 
+
+        // Write Abbrevs that are only used for AOT debuginfo generation
+        if (!dwarfSections.isRuntimeCompilation()) {
+            pos = writeTypeUnitAbbrev(context, buffer, pos);
+
+            pos = writePrimitiveTypeAbbrev(context, buffer, pos);
+            pos = writeVoidTypeAbbrev(context, buffer, pos);
+            pos = writeObjectHeaderAbbrev(context, buffer, pos);
+
+            pos = writeClassReferenceAbbrevs(context, buffer, pos);
+            pos = writeFieldDeclarationAbbrevs(context, buffer, pos);
+            pos = writeClassConstantAbbrev(context, buffer, pos);
+
+            pos = writeArrayLayoutAbbrev(context, buffer, pos);
+
+            pos = writeInterfaceLayoutAbbrev(context, buffer, pos);
+
+            pos = writeForeignTypedefAbbrev(context, buffer, pos);
+            pos = writeForeignStructAbbrev(context, buffer, pos);
+
+            pos = writeHeaderFieldAbbrev(context, buffer, pos);
+            pos = writeArrayElementFieldAbbrev(context, buffer, pos);
+            pos = writeArrayDataTypeAbbrevs(context, buffer, pos);
+            pos = writeArraySubrangeTypeAbbrev(context, buffer, pos);
+            pos = writeAbstractInlineMethodAbbrev(context, buffer, pos);
+            pos = writeStaticFieldLocationAbbrev(context, buffer, pos);
+            pos = writeSuperReferenceAbbrev(context, buffer, pos);
+            pos = writeInterfaceImplementorAbbrev(context, buffer, pos);
+
+            /*
+             * if we address rebasing is required then we need to use compressed layout types
+             * supplied with a suitable data_location attribute and compressed pointer types to ensure
+             * that gdb converts offsets embedded in static or instance fields to raw pointers.
+             * Transformed addresses are typed using pointers to the underlying layout.
+             *
+             * if address rebasing is not required then a data_location attribute on the layout type
+             * will ensure that address tag bits are removed.
+             */
+            if (dwarfSections.useHeapBase()) {
+                pos = writeCompressedLayoutAbbrev(context, buffer, pos);
+            }
+        }
+
         /* write a null abbrev to terminate the sequence */
         pos = writeNullAbbrev(context, buffer, pos);
         return pos;
@@ -987,9 +989,11 @@ private int writeHasChildren(DwarfHasChildren hasChildren, byte[] buffer, int po
 
     private int writeCompileUnitAbbrevs(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
         int pos = p;
-        pos = writeCompileUnitAbbrev(context, AbbrevCode.CLASS_CONSTANT_UNIT, buffer, pos);
-        pos = writeCompileUnitAbbrev(context, AbbrevCode.CLASS_UNIT_1, buffer, pos);
-        pos = writeCompileUnitAbbrev(context, AbbrevCode.CLASS_UNIT_2, buffer, pos);
+        if (!dwarfSections.isRuntimeCompilation()) {
+            pos = writeCompileUnitAbbrev(context, AbbrevCode.CLASS_CONSTANT_UNIT, buffer, pos);
+            pos = writeCompileUnitAbbrev(context, AbbrevCode.CLASS_UNIT_1, buffer, pos);
+            pos = writeCompileUnitAbbrev(context, AbbrevCode.CLASS_UNIT_2, buffer, pos);
+        }
         pos = writeCompileUnitAbbrev(context, AbbrevCode.CLASS_UNIT_3, buffer, pos);
         return pos;
     }
@@ -1114,11 +1118,13 @@ private int writeNamespaceAbbrev(@SuppressWarnings("unused") DebugContext contex
 
     private int writeClassLayoutAbbrevs(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
         int pos = p;
-        pos = writeClassLayoutAbbrev(context, AbbrevCode.CLASS_LAYOUT_1, buffer, pos);
-        pos = writeClassLayoutAbbrev(context, AbbrevCode.CLASS_LAYOUT_3, buffer, pos);
-        if (!dwarfSections.useHeapBase()) {
-            pos = writeClassLayoutAbbrev(context, AbbrevCode.CLASS_LAYOUT_2, buffer, pos);
+        if (!dwarfSections.isRuntimeCompilation()) {
+            pos = writeClassLayoutAbbrev(context, AbbrevCode.CLASS_LAYOUT_1, buffer, pos);
+            if (!dwarfSections.useHeapBase()) {
+                pos = writeClassLayoutAbbrev(context, AbbrevCode.CLASS_LAYOUT_2, buffer, pos);
+            }
         }
+        pos = writeClassLayoutAbbrev(context, AbbrevCode.CLASS_LAYOUT_3, buffer, pos);
         return pos;
     }
 
@@ -1189,8 +1195,12 @@ private int writeClassReferenceAbbrev(@SuppressWarnings("unused") DebugContext c
     private int writeMethodDeclarationAbbrevs(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
         int pos = p;
         pos = writeMethodDeclarationAbbrev(context, AbbrevCode.METHOD_DECLARATION, buffer, pos);
+        pos = writeMethodDeclarationAbbrev(context, AbbrevCode.METHOD_DECLARATION_INLINE, buffer, pos);
         pos = writeMethodDeclarationAbbrev(context, AbbrevCode.METHOD_DECLARATION_STATIC, buffer, pos);
-        pos = writeMethodDeclarationAbbrev(context, AbbrevCode.METHOD_DECLARATION_SKELETON, buffer, pos);
+        pos = writeMethodDeclarationAbbrev(context, AbbrevCode.METHOD_DECLARATION_INLINE_STATIC, buffer, pos);
+        if (!dwarfSections.isRuntimeCompilation()) {
+            pos = writeMethodDeclarationAbbrev(context, AbbrevCode.METHOD_DECLARATION_SKELETON, buffer, pos);
+        }
         return pos;
     }
 
@@ -1199,11 +1209,15 @@ private int writeMethodDeclarationAbbrev(@SuppressWarnings("unused") DebugContex
         pos = writeAbbrevCode(abbrevCode, buffer, pos);
         pos = writeTag(DwarfTag.DW_TAG_subprogram, buffer, pos);
         pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_yes, buffer, pos);
+        if (abbrevCode == AbbrevCode.METHOD_DECLARATION_INLINE || abbrevCode == AbbrevCode.METHOD_DECLARATION_INLINE_STATIC) {
+            pos = writeAttrType(DwarfAttribute.DW_AT_inline, buffer, pos);
+            pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos);
+        }
         pos = writeAttrType(DwarfAttribute.DW_AT_external, buffer, pos);
         pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos);
         pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos);
         pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos);
-        if (abbrevCode == AbbrevCode.METHOD_DECLARATION || abbrevCode == AbbrevCode.METHOD_DECLARATION_STATIC) {
+        if (abbrevCode != AbbrevCode.METHOD_DECLARATION_SKELETON) {
             pos = writeAttrType(DwarfAttribute.DW_AT_decl_file, buffer, pos);
             pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos);
             pos = writeAttrType(DwarfAttribute.DW_AT_decl_line, buffer, pos);
@@ -1219,14 +1233,14 @@ private int writeMethodDeclarationAbbrev(@SuppressWarnings("unused") DebugContex
         pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos);
         pos = writeAttrType(DwarfAttribute.DW_AT_declaration, buffer, pos);
         pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos);
-        if (abbrevCode == AbbrevCode.METHOD_DECLARATION || abbrevCode == AbbrevCode.METHOD_DECLARATION_STATIC) {
-            /* This is not in DWARF2 */
-            // pos = writeAttrType(DW_AT_virtuality, buffer, pos);
-            // pos = writeAttrForm(DW_FORM_data1, buffer, pos);
+        /* This is not in DWARF2 */
+        // pos = writeAttrType(DW_AT_virtuality, buffer, pos);
+        // pos = writeAttrForm(DW_FORM_data1, buffer, pos);
+        if (abbrevCode != AbbrevCode.METHOD_DECLARATION_SKELETON) {
             pos = writeAttrType(DwarfAttribute.DW_AT_containing_type, buffer, pos);
             pos = writeAttrForm(DwarfForm.DW_FORM_ref_sig8, buffer, pos);
         }
-        if (abbrevCode == AbbrevCode.METHOD_DECLARATION) {
+        if (abbrevCode == AbbrevCode.METHOD_DECLARATION || abbrevCode == AbbrevCode.METHOD_DECLARATION_INLINE) {
             pos = writeAttrType(DwarfAttribute.DW_AT_object_pointer, buffer, pos);
             pos = writeAttrForm(DwarfForm.DW_FORM_ref4, buffer, pos);
         }
@@ -1613,8 +1627,10 @@ private int writeParameterDeclarationAbbrevs(DebugContext context, byte[] buffer
         pos = writeParameterDeclarationAbbrev(context, AbbrevCode.METHOD_PARAMETER_DECLARATION_1, buffer, pos);
         pos = writeParameterDeclarationAbbrev(context, AbbrevCode.METHOD_PARAMETER_DECLARATION_2, buffer, pos);
         pos = writeParameterDeclarationAbbrev(context, AbbrevCode.METHOD_PARAMETER_DECLARATION_3, buffer, pos);
-        pos = writeSkeletonParameterDeclarationAbbrev(context, AbbrevCode.METHOD_PARAMETER_DECLARATION_4, buffer, pos);
-        pos = writeSkeletonParameterDeclarationAbbrev(context, AbbrevCode.METHOD_PARAMETER_DECLARATION_5, buffer, pos);
+        if (!dwarfSections.isRuntimeCompilation()) {
+            pos = writeSkeletonParameterDeclarationAbbrev(context, AbbrevCode.METHOD_PARAMETER_DECLARATION_4, buffer, pos);
+            pos = writeSkeletonParameterDeclarationAbbrev(context, AbbrevCode.METHOD_PARAMETER_DECLARATION_5, buffer, pos);
+        }
         return pos;
     }
 
@@ -1760,11 +1776,11 @@ private int writeNullAbbrev(@SuppressWarnings("unused") DebugContext context, by
         return pos;
     }
 
-    private int writeInlinedSubroutineAbbrev(byte[] buffer, int p, boolean withChildren) {
+    private int writeInlinedSubroutineAbbrev(byte[] buffer, int p) {
         int pos = p;
-        pos = writeAbbrevCode(withChildren ? AbbrevCode.INLINED_SUBROUTINE_WITH_CHILDREN : AbbrevCode.INLINED_SUBROUTINE, buffer, pos);
+        pos = writeAbbrevCode(AbbrevCode.INLINED_SUBROUTINE, buffer, pos);
         pos = writeTag(DwarfTag.DW_TAG_inlined_subroutine, buffer, pos);
-        pos = writeHasChildren(withChildren ? DwarfHasChildren.DW_CHILDREN_yes : DwarfHasChildren.DW_CHILDREN_no, buffer, pos);
+        pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_yes, buffer, pos);
         pos = writeAttrType(DwarfAttribute.DW_AT_abstract_origin, buffer, pos);
         pos = writeAttrForm(DwarfForm.DW_FORM_ref4, buffer, pos);
         pos = writeAttrType(DwarfAttribute.DW_AT_low_pc, buffer, pos);
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java
index e3634115c5b3..2dba77ae051d 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
@@ -27,18 +27,19 @@
 package com.oracle.objectfile.elf.dwarf;
 
 import java.nio.ByteOrder;
-
-import org.graalvm.collections.EconomicMap;
+import java.util.HashMap;
 
 import com.oracle.objectfile.debugentry.ClassEntry;
 import com.oracle.objectfile.debugentry.DebugInfoBase;
+import com.oracle.objectfile.debugentry.LocalEntry;
 import com.oracle.objectfile.debugentry.MethodEntry;
 import com.oracle.objectfile.debugentry.StructureTypeEntry;
 import com.oracle.objectfile.debugentry.TypeEntry;
 import com.oracle.objectfile.debugentry.range.Range;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalInfo;
+import com.oracle.objectfile.debuginfo.DebugInfoProvider;
 import com.oracle.objectfile.elf.ELFMachine;
 import com.oracle.objectfile.elf.dwarf.constants.DwarfLanguage;
+import jdk.graal.compiler.debug.DebugContext;
 
 /**
  * A class that models the debug info in an organization that facilitates generation of the required
@@ -52,7 +53,7 @@ public class DwarfDebugInfo extends DebugInfoBase {
     /*
      * Define all the abbrev section codes we need for our DIEs.
      */
-    enum AbbrevCode {
+    public enum AbbrevCode {
         /* null marker which must come first as its ordinal has to equal zero */
         NULL,
         /* Level 0 DIEs. */
@@ -81,7 +82,9 @@ enum AbbrevCode {
         COMPRESSED_LAYOUT,
         /* Level 2 DIEs. */
         METHOD_DECLARATION,
+        METHOD_DECLARATION_INLINE,
         METHOD_DECLARATION_STATIC,
+        METHOD_DECLARATION_INLINE_STATIC,
         METHOD_DECLARATION_SKELETON,
         FIELD_DECLARATION_1,
         FIELD_DECLARATION_2,
@@ -125,21 +128,6 @@ enum AbbrevCode {
     public static final byte rheapbase_x86 = (byte) 14;
     public static final byte rthread_x86 = (byte) 15;
 
-    /*
-     * A prefix used to label indirect types used to ensure gdb performs oop reference --> raw
-     * address translation
-     */
-    public static final String COMPRESSED_PREFIX = "_z_.";
-    /*
-     * A prefix used for type signature generation to generate unique type signatures for type
-     * layout type units
-     */
-    public static final String LAYOUT_PREFIX = "_layout_.";
-    /*
-     * The name of the type for header field hub which needs special case processing to remove tag
-     * bits
-     */
-    public static final String HUB_TYPE_NAME = "java.lang.Class";
     /* Full byte/word values. */
     private final DwarfStrSectionImpl dwarfStrSection;
     private final DwarfAbbrevSectionImpl dwarfAbbrevSection;
@@ -164,17 +152,17 @@ enum AbbrevCode {
      * n.b. this collection includes entries for the structure types used to define the object and
      * array headers which do not have an associated TypeEntry.
      */
-    private final EconomicMap<TypeEntry, DwarfClassProperties> classPropertiesIndex = EconomicMap.create();
+    private final HashMap<TypeEntry, DwarfClassProperties> classPropertiesIndex = new HashMap<>();
 
     /**
      * A collection of method properties associated with each generated method record.
      */
-    private final EconomicMap<MethodEntry, DwarfMethodProperties> methodPropertiesIndex = EconomicMap.create();
+    private final HashMap<MethodEntry, DwarfMethodProperties> methodPropertiesIndex = new HashMap<>();
 
     /**
      * A collection of local variable properties associated with an inlined subrange.
      */
-    private final EconomicMap<Range, DwarfLocalProperties> rangeLocalPropertiesIndex = EconomicMap.create();
+    private final HashMap<Range, DwarfLocalProperties> rangeLocalPropertiesIndex = new HashMap<>();
 
     @SuppressWarnings("this-escape")
     public DwarfDebugInfo(ELFMachine elfMachine, ByteOrder byteOrder) {
@@ -271,7 +259,7 @@ static class DwarfClassProperties {
         /**
          * Map from field names to info section index for the field declaration.
          */
-        private EconomicMap<String, Integer> fieldDeclarationIndex;
+        private HashMap<String, Integer> fieldDeclarationIndex;
 
         public StructureTypeEntry getTypeEntry() {
             return typeEntry;
@@ -301,12 +289,12 @@ static class DwarfMethodProperties {
          * Per class map that identifies the info declarations for a top level method declaration or
          * an abstract inline method declaration.
          */
-        private EconomicMap<ClassEntry, DwarfLocalProperties> localPropertiesMap;
+        private HashMap<ClassEntry, DwarfLocalProperties> localPropertiesMap;
 
         /**
          * Per class map that identifies the info declaration for an abstract inline method.
          */
-        private EconomicMap<ClassEntry, Integer> abstractInlineMethodIndex;
+        private HashMap<ClassEntry, Integer> abstractInlineMethodIndex;
 
         DwarfMethodProperties() {
             methodDeclarationIndex = -1;
@@ -326,19 +314,14 @@ public void setMethodDeclarationIndex(int pos) {
 
         public DwarfLocalProperties getLocalProperties(ClassEntry classEntry) {
             if (localPropertiesMap == null) {
-                localPropertiesMap = EconomicMap.create();
-            }
-            DwarfLocalProperties localProperties = localPropertiesMap.get(classEntry);
-            if (localProperties == null) {
-                localProperties = new DwarfLocalProperties();
-                localPropertiesMap.put(classEntry, localProperties);
+                localPropertiesMap = new HashMap<>();
             }
-            return localProperties;
+            return localPropertiesMap.computeIfAbsent(classEntry, k -> new DwarfLocalProperties());
         }
 
         public void setAbstractInlineMethodIndex(ClassEntry classEntry, int pos) {
             if (abstractInlineMethodIndex == null) {
-                abstractInlineMethodIndex = EconomicMap.create();
+                abstractInlineMethodIndex = new HashMap<>();
             }
             // replace but check it did not change
             Integer val = abstractInlineMethodIndex.put(classEntry, pos);
@@ -463,9 +446,9 @@ public void setFieldDeclarationIndex(StructureTypeEntry entry, String fieldName,
         DwarfClassProperties classProperties;
         classProperties = lookupClassProperties(entry);
         assert classProperties.getTypeEntry() == entry;
-        EconomicMap<String, Integer> fieldDeclarationIndex = classProperties.fieldDeclarationIndex;
+        HashMap<String, Integer> fieldDeclarationIndex = classProperties.fieldDeclarationIndex;
         if (fieldDeclarationIndex == null) {
-            classProperties.fieldDeclarationIndex = fieldDeclarationIndex = EconomicMap.create();
+            classProperties.fieldDeclarationIndex = fieldDeclarationIndex = new HashMap<>();
         }
         if (fieldDeclarationIndex.get(fieldName) != null) {
             assert fieldDeclarationIndex.get(fieldName) == pos : entry.getTypeName() + fieldName;
@@ -478,7 +461,7 @@ public int getFieldDeclarationIndex(StructureTypeEntry entry, String fieldName)
         DwarfClassProperties classProperties;
         classProperties = lookupClassProperties(entry);
         assert classProperties.getTypeEntry() == entry;
-        EconomicMap<String, Integer> fieldDeclarationIndex = classProperties.fieldDeclarationIndex;
+        HashMap<String, Integer> fieldDeclarationIndex = classProperties.fieldDeclarationIndex;
         assert fieldDeclarationIndex != null : fieldName;
         assert fieldDeclarationIndex.get(fieldName) != null : entry.getTypeName() + fieldName;
         return fieldDeclarationIndex.get(fieldName);
@@ -500,17 +483,17 @@ public int getMethodDeclarationIndex(MethodEntry methodEntry) {
      */
 
     static final class DwarfLocalProperties {
-        private EconomicMap<DebugLocalInfo, Integer> locals;
+        private final HashMap<LocalEntry, Integer> locals;
 
         private DwarfLocalProperties() {
-            locals = EconomicMap.create();
+            locals = new HashMap<>();
         }
 
-        int getIndex(DebugLocalInfo localInfo) {
+        int getIndex(LocalEntry localInfo) {
             return locals.get(localInfo);
         }
 
-        void setIndex(DebugLocalInfo localInfo, int index) {
+        void setIndex(LocalEntry localInfo, int index) {
             if (locals.get(localInfo) != null) {
                 assert locals.get(localInfo) == index;
             } else {
@@ -533,16 +516,16 @@ private DwarfLocalProperties addRangeLocalProperties(Range range) {
         return localProperties;
     }
 
-    public DwarfLocalProperties lookupLocalProperties(ClassEntry classEntry, MethodEntry methodEntry) {
+    private DwarfLocalProperties lookupLocalProperties(ClassEntry classEntry, MethodEntry methodEntry) {
         return lookupMethodProperties(methodEntry).getLocalProperties(classEntry);
     }
 
-    public void setMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, DebugLocalInfo localInfo, int index) {
+    public void setMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, LocalEntry localInfo, int index) {
         DwarfLocalProperties localProperties = lookupLocalProperties(classEntry, methodEntry);
         localProperties.setIndex(localInfo, index);
     }
 
-    public int getMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, DebugLocalInfo localInfo) {
+    public int getMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, LocalEntry localInfo) {
         DwarfLocalProperties localProperties = lookupLocalProperties(classEntry, methodEntry);
         assert localProperties != null : "get of non-existent local index";
         int index = localProperties.getIndex(localInfo);
@@ -550,7 +533,7 @@ public int getMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, D
         return index;
     }
 
-    public void setRangeLocalIndex(Range range, DebugLocalInfo localInfo, int index) {
+    public void setRangeLocalIndex(Range range, LocalEntry localInfo, int index) {
         DwarfLocalProperties rangeProperties = rangeLocalPropertiesIndex.get(range);
         if (rangeProperties == null) {
             rangeProperties = addRangeLocalProperties(range);
@@ -558,10 +541,10 @@ public void setRangeLocalIndex(Range range, DebugLocalInfo localInfo, int index)
         rangeProperties.setIndex(localInfo, index);
     }
 
-    public int getRangeLocalIndex(Range range, DebugLocalInfo localinfo) {
+    public int getRangeLocalIndex(Range range, LocalEntry localInfo) {
         DwarfLocalProperties rangeProperties = rangeLocalPropertiesIndex.get(range);
         assert rangeProperties != null : "get of non-existent local index";
-        int index = rangeProperties.getIndex(localinfo);
+        int index = rangeProperties.getIndex(localInfo);
         assert index >= 0 : "get of local index before it was set";
         return index;
     }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImpl.java
index ea0c269521f1..585d9a1bac6b 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImpl.java
@@ -27,8 +27,8 @@
 package com.oracle.objectfile.elf.dwarf;
 
 import com.oracle.objectfile.debugentry.CompiledMethodEntry;
+import com.oracle.objectfile.debugentry.FrameSizeChangeEntry;
 import com.oracle.objectfile.debugentry.range.Range;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider;
 import com.oracle.objectfile.elf.dwarf.constants.DwarfFrameValue;
 import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName;
 import jdk.graal.compiler.debug.DebugContext;
@@ -143,11 +143,11 @@ private int writeCIEVersion(byte[] buffer, int pos) {
     private int writeMethodFrame(CompiledMethodEntry compiledEntry, byte[] buffer, int p) {
         int pos = p;
         int lengthPos = pos;
-        Range range = compiledEntry.getPrimary();
+        Range range = compiledEntry.primary();
         long lo = range.getLo();
         long hi = range.getHi();
-        pos = writeFDEHeader((int) lo, (int) hi, buffer, pos);
-        pos = writeFDEs(compiledEntry.getFrameSize(), compiledEntry.getFrameSizeInfos(), buffer, pos);
+        pos = writeFDEHeader(lo, hi, buffer, pos);
+        pos = writeFDEs(compiledEntry.frameSize(), compiledEntry.frameSizeInfos(), buffer, pos);
         pos = writePaddingNops(buffer, pos);
         patchLength(lengthPos, buffer, pos);
         return pos;
@@ -161,9 +161,9 @@ private int writeMethodFrames(byte[] buffer, int p) {
         return cursor.get();
     }
 
-    protected abstract int writeFDEs(int frameSize, List<DebugInfoProvider.DebugFrameSizeChange> frameSizeInfos, byte[] buffer, int pos);
+    protected abstract int writeFDEs(int frameSize, List<FrameSizeChangeEntry> frameSizeInfos, byte[] buffer, int pos);
 
-    private int writeFDEHeader(int lo, int hi, byte[] buffer, int p) {
+    private int writeFDEHeader(long lo, long hi, byte[] buffer, int p) {
         /*
          * We only need a vanilla FDE header with default fields the layout is:
          *
@@ -189,7 +189,7 @@ private int writeFDEHeader(int lo, int hi, byte[] buffer, int p) {
         /* CIE_offset */
         pos = writeInt(0, buffer, pos);
         /* Initial address. */
-        pos = writeRelocatableCodeOffset(lo, buffer, pos);
+        pos = writeCodeOffset(lo, buffer, pos);
         /* Address range. */
         return writeLong(hi - lo, buffer, pos);
     }
@@ -263,8 +263,7 @@ protected int writeOffset(int register, int offset, byte[] buffer, int p) {
 
     protected int writeRestore(int register, byte[] buffer, int p) {
         byte op = restoreOp(register);
-        int pos = p;
-        return writeByte(op, buffer, pos);
+        return writeByte(op, buffer, p);
     }
 
     @SuppressWarnings("unused")
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplAArch64.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplAArch64.java
index b568d396c919..192c59177ebf 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplAArch64.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplAArch64.java
@@ -26,7 +26,7 @@
 
 package com.oracle.objectfile.elf.dwarf;
 
-import com.oracle.objectfile.debuginfo.DebugInfoProvider;
+import com.oracle.objectfile.debugentry.FrameSizeChangeEntry;
 
 import java.util.List;
 
@@ -73,14 +73,14 @@ public int writeInitialInstructions(byte[] buffer, int p) {
     }
 
     @Override
-    protected int writeFDEs(int frameSize, List<DebugInfoProvider.DebugFrameSizeChange> frameSizeInfos, byte[] buffer, int p) {
+    protected int writeFDEs(int frameSize, List<FrameSizeChangeEntry> frameSizeInfos, byte[] buffer, int p) {
         int pos = p;
         int currentOffset = 0;
-        for (DebugInfoProvider.DebugFrameSizeChange debugFrameSizeInfo : frameSizeInfos) {
-            int advance = debugFrameSizeInfo.getOffset() - currentOffset;
+        for (FrameSizeChangeEntry frameSizeInfo : frameSizeInfos) {
+            int advance = frameSizeInfo.offset() - currentOffset;
             currentOffset += advance;
             pos = writeAdvanceLoc(advance, buffer, pos);
-            if (debugFrameSizeInfo.getType() == DebugInfoProvider.DebugFrameSizeChange.Type.EXTEND) {
+            if (frameSizeInfo.isExtend()) {
                 /*
                  * SP has been extended so rebase CFA using full frame.
                  *
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplX86_64.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplX86_64.java
index 2a2fb2fd78ba..45621ae2e72b 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplX86_64.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplX86_64.java
@@ -26,7 +26,7 @@
 
 package com.oracle.objectfile.elf.dwarf;
 
-import com.oracle.objectfile.debuginfo.DebugInfoProvider;
+import com.oracle.objectfile.debugentry.FrameSizeChangeEntry;
 
 import java.util.List;
 
@@ -84,14 +84,14 @@ public int writeInitialInstructions(byte[] buffer, int p) {
     }
 
     @Override
-    protected int writeFDEs(int frameSize, List<DebugInfoProvider.DebugFrameSizeChange> frameSizeInfos, byte[] buffer, int p) {
+    protected int writeFDEs(int frameSize, List<FrameSizeChangeEntry> frameSizeInfos, byte[] buffer, int p) {
         int pos = p;
         int currentOffset = 0;
-        for (DebugInfoProvider.DebugFrameSizeChange debugFrameSizeInfo : frameSizeInfos) {
-            int advance = debugFrameSizeInfo.getOffset() - currentOffset;
+        for (FrameSizeChangeEntry frameSizeInfo : frameSizeInfos) {
+            int advance = frameSizeInfo.offset() - currentOffset;
             currentOffset += advance;
             pos = writeAdvanceLoc(advance, buffer, pos);
-            if (debugFrameSizeInfo.getType() == DebugInfoProvider.DebugFrameSizeChange.Type.EXTEND) {
+            if (frameSizeInfo.isExtend()) {
                 /*
                  * SP has been extended so rebase CFA using full frame.
                  *
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 6d5926c0530e..1c81b8749c0e 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
@@ -28,13 +28,11 @@
 
 import java.lang.reflect.Modifier;
 import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Stream;
 
-import org.graalvm.collections.EconomicSet;
-
 import com.oracle.objectfile.debugentry.ArrayTypeEntry;
 import com.oracle.objectfile.debugentry.ClassEntry;
 import com.oracle.objectfile.debugentry.CompiledMethodEntry;
@@ -43,15 +41,14 @@
 import com.oracle.objectfile.debugentry.ForeignTypeEntry;
 import com.oracle.objectfile.debugentry.HeaderTypeEntry;
 import com.oracle.objectfile.debugentry.InterfaceClassEntry;
+import com.oracle.objectfile.debugentry.LocalEntry;
+import com.oracle.objectfile.debugentry.LocalValueEntry;
 import com.oracle.objectfile.debugentry.MethodEntry;
 import com.oracle.objectfile.debugentry.PrimitiveTypeEntry;
 import com.oracle.objectfile.debugentry.StructureTypeEntry;
 import com.oracle.objectfile.debugentry.TypeEntry;
 import com.oracle.objectfile.debugentry.range.Range;
-import com.oracle.objectfile.debugentry.range.SubRange;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalValueInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugPrimitiveTypeInfo;
+import com.oracle.objectfile.debuginfo.DebugInfoProvider;
 import com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.AbbrevCode;
 import com.oracle.objectfile.elf.dwarf.constants.DwarfAccess;
 import com.oracle.objectfile.elf.dwarf.constants.DwarfEncoding;
@@ -64,7 +61,6 @@
 import com.oracle.objectfile.elf.dwarf.constants.DwarfVersion;
 
 import jdk.graal.compiler.debug.DebugContext;
-import jdk.vm.ci.meta.JavaConstant;
 import jdk.vm.ci.meta.JavaKind;
 import jdk.vm.ci.meta.PrimitiveConstant;
 
@@ -113,48 +109,55 @@ public void writeContent(DebugContext context) {
         assert pos == size;
     }
 
-    DwarfEncoding computeEncoding(int flags, int bitCount) {
+    DwarfEncoding computeEncoding(PrimitiveTypeEntry type) {
+        int bitCount = type.getBitCount();
         assert bitCount > 0;
-        if ((flags & DebugPrimitiveTypeInfo.FLAG_NUMERIC) != 0) {
-            if (((flags & DebugPrimitiveTypeInfo.FLAG_INTEGRAL) != 0)) {
-                if ((flags & DebugPrimitiveTypeInfo.FLAG_SIGNED) != 0) {
-                    switch (bitCount) {
-                        case 8:
-                            return DwarfEncoding.DW_ATE_signed_char;
-                        default:
-                            assert bitCount == 16 || bitCount == 32 || bitCount == 64;
-                            return DwarfEncoding.DW_ATE_signed;
-                    }
+        if (type.isNumericInteger()) {
+            if (type.isUnsigned()) {
+                if (bitCount == 1) {
+                    return DwarfEncoding.DW_ATE_boolean;
                 } else {
                     assert bitCount == 16;
                     return DwarfEncoding.DW_ATE_unsigned;
                 }
+            } else if (bitCount == 8) {
+                return DwarfEncoding.DW_ATE_signed_char;
             } else {
-                assert bitCount == 32 || bitCount == 64;
-                return DwarfEncoding.DW_ATE_float;
+                assert bitCount == 16 || bitCount == 32 || bitCount == 64;
+                return DwarfEncoding.DW_ATE_signed;
             }
         } else {
-            assert bitCount == 1;
-            return DwarfEncoding.DW_ATE_boolean;
+            assert type.isNumericFloat();
+            assert bitCount == 32 || bitCount == 64;
+            return DwarfEncoding.DW_ATE_float;
         }
     }
 
     public int generateContent(DebugContext context, byte[] buffer) {
         int pos = 0;
-        /* Write TUs for primitive types and header struct. */
-        pos = writeBuiltInTypes(context, buffer, pos);
+        if (dwarfSections.isRuntimeCompilation()) {
+            Cursor cursor = new Cursor(pos);
+            instanceClassStream().filter(ClassEntry::hasCompiledMethods).forEachOrdered(classEntry -> {
+                setCUIndex(classEntry, cursor.get());
+                cursor.set(writeInstanceClassInfo(context, classEntry, buffer, cursor.get()));
+            });
+            pos = cursor.get();
+        } else {
+            /* Write TUs for primitive types and header struct. */
+            pos = writeBuiltInTypes(context, buffer, pos);
 
-        /*
-         * Write TUs and CUs for all instance classes, which includes interfaces and enums. That
-         * also incorporates interfaces that model foreign types.
-         */
-        pos = writeInstanceClasses(context, buffer, pos);
+            /*
+             * Write TUs and CUs for all instance classes, which includes interfaces and enums. That
+             * also incorporates interfaces that model foreign types.
+             */
+            pos = writeInstanceClasses(context, buffer, pos);
 
-        /* Write TUs and CUs for array types. */
-        pos = writeArrays(context, buffer, pos);
+            /* Write TUs and CUs for array types. */
+            pos = writeArrays(context, buffer, pos);
 
-        /* Write CU for class constant objects. */
-        pos = writeClassConstantObjects(context, buffer, pos);
+            /* Write CU for class constant objects. */
+            pos = writeClassConstantObjects(context, buffer, pos);
+        }
 
         return pos;
     }
@@ -165,7 +168,7 @@ private int writeSkeletonClassLayout(DebugContext context, ClassEntry classEntry
         AbbrevCode abbrevCode = AbbrevCode.CLASS_LAYOUT_3;
         log(context, "  [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal());
         pos = writeAbbrevCode(abbrevCode, buffer, pos);
-        String name = classEntry.getTypeName();
+        String name = uniqueDebugString(classEntry.getTypeName());
         log(context, "  [0x%08x]     name  0x%x (%s)", pos, debugStringIndex(name), name);
         pos = writeStrSectionOffset(name, buffer, pos);
         log(context, "  [0x%08x]     declaration true", pos);
@@ -218,7 +221,7 @@ private int writeClassConstantObjects(DebugContext context, byte[] buffer, int p
         String name = uniqueDebugString("JAVA");
         log(context, "  [0x%08x]     name  0x%x (%s)", pos, debugStringIndex(name), name);
         pos = writeStrSectionOffset(name, buffer, pos);
-        String compilationDirectory = dwarfSections.getCachePath();
+        String compilationDirectory = uniqueDebugString(dwarfSections.getCachePath());
         log(context, "  [0x%08x]     comp_dir  0x%x (%s)", pos, debugStringIndex(compilationDirectory), compilationDirectory);
         pos = writeStrSectionOffset(compilationDirectory, buffer, pos);
 
@@ -257,10 +260,10 @@ private int writePrimitiveType(DebugContext context, PrimitiveTypeEntry primitiv
         byte bitCount = (byte) primitiveTypeEntry.getBitCount();
         log(context, "  [0x%08x]     bitCount  %d", pos, bitCount);
         pos = writeAttrData1(bitCount, buffer, pos);
-        DwarfEncoding encoding = computeEncoding(primitiveTypeEntry.getFlags(), bitCount);
+        DwarfEncoding encoding = computeEncoding(primitiveTypeEntry);
         log(context, "  [0x%08x]     encoding  0x%x", pos, encoding.value());
         pos = writeAttrEncoding(encoding, buffer, pos);
-        String name = primitiveTypeEntry.getTypeName();
+        String name = uniqueDebugString(primitiveTypeEntry.getTypeName());
         log(context, "  [0x%08x]     name  0x%x (%s)", pos, debugStringIndex(name), name);
         pos = writeStrSectionOffset(name, buffer, pos);
 
@@ -284,7 +287,7 @@ private int writeVoidType(DebugContext context, PrimitiveTypeEntry primitiveType
         AbbrevCode abbrevCode = AbbrevCode.VOID_TYPE;
         log(context, "  [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal());
         pos = writeAbbrevCode(abbrevCode, buffer, pos);
-        String name = primitiveTypeEntry.getTypeName();
+        String name = uniqueDebugString(primitiveTypeEntry.getTypeName());
         log(context, "  [0x%08x]     name  0x%x (%s)", pos, debugStringIndex(name), name);
         pos = writeStrSectionOffset(name, buffer, pos);
 
@@ -303,7 +306,7 @@ private int writeHeaderType(DebugContext context, HeaderTypeEntry headerTypeEntr
         int lengthPos = pos;
         pos = writeTUPreamble(context, headerTypeEntry.getTypeSignature(), "", buffer, p);
 
-        String name = headerTypeEntry.getTypeName();
+        String name = uniqueDebugString(headerTypeEntry.getTypeName());
         byte size = (byte) headerTypeEntry.getSize();
         log(context, "  [0x%08x] header type %s", pos, name);
         AbbrevCode abbrevCode = AbbrevCode.OBJECT_HEADER;
@@ -313,7 +316,7 @@ private int writeHeaderType(DebugContext context, HeaderTypeEntry headerTypeEntr
         pos = writeStrSectionOffset(name, buffer, pos);
         log(context, "  [0x%08x]     byte_size  0x%x", pos, size);
         pos = writeAttrData1(size, buffer, pos);
-        pos = writeStructFields(context, headerTypeEntry.fields(), buffer, pos);
+        pos = writeStructFields(context, headerTypeEntry.getFields(), buffer, pos);
 
         /* Write a terminating null attribute. */
         pos = writeAttrNull(buffer, pos);
@@ -326,7 +329,7 @@ private int writeHeaderType(DebugContext context, HeaderTypeEntry headerTypeEntr
         return pos;
     }
 
-    private int writeStructFields(DebugContext context, Stream<FieldEntry> fields, byte[] buffer, int p) {
+    private int writeStructFields(DebugContext context, List<FieldEntry> fields, byte[] buffer, int p) {
         Cursor cursor = new Cursor(p);
         fields.forEach(fieldEntry -> {
             cursor.set(writeStructField(context, fieldEntry, buffer, cursor.get()));
@@ -336,7 +339,7 @@ private int writeStructFields(DebugContext context, Stream<FieldEntry> fields, b
 
     private int writeStructField(DebugContext context, FieldEntry fieldEntry, byte[] buffer, int p) {
         int pos = p;
-        String fieldName = fieldEntry.fieldName();
+        String fieldName = uniqueDebugString(fieldEntry.fieldName());
         TypeEntry valueType = fieldEntry.getValueType();
         long typeSignature = 0;
         int typeIdx = 0;
@@ -598,7 +601,7 @@ private int writeInterfaceLayoutTypeUnit(DebugContext context, InterfaceClassEnt
         AbbrevCode abbrevCode = AbbrevCode.INTERFACE_LAYOUT;
         log(context, "  [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal());
         pos = writeAbbrevCode(abbrevCode, buffer, pos);
-        String name = interfaceClassEntry.getTypeName();
+        String name = uniqueDebugString(interfaceClassEntry.getTypeName());
         log(context, "  [0x%08x]     name  0x%x (%s)", pos, debugStringIndex(name), name);
         pos = writeStrSectionOffset(name, buffer, pos);
 
@@ -640,7 +643,7 @@ private int writeClassLayoutTypeUnit(DebugContext context, ClassEntry classEntry
         }
         log(context, "  [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal());
         pos = writeAbbrevCode(abbrevCode, buffer, pos);
-        String name = classEntry.getTypeName();
+        String name = uniqueDebugString(classEntry.getTypeName());
         log(context, "  [0x%08x]     name  0x%x (%s)", pos, debugStringIndex(name), name);
         pos = writeStrSectionOffset(name, buffer, pos);
         int size = classEntry.getSize();
@@ -764,7 +767,7 @@ private int writeForeignLayoutTypeUnit(DebugContext context, ForeignTypeEntry fo
             // define the type as a typedef for a signed or unsigned word i.e. we don't have a
             // layout type
             pos = writeForeignWordLayout(context, foreignTypeEntry, size, foreignTypeEntry.isSigned(), buffer, pos);
-        } else if (foreignTypeEntry.isIntegral()) {
+        } else if (foreignTypeEntry.isInteger()) {
             // use a suitably sized signed or unsigned integral type as the layout type
             pos = writeForeignIntegerLayout(context, foreignTypeEntry, size, foreignTypeEntry.isSigned(), buffer, pos);
         } else if (foreignTypeEntry.isFloat()) {
@@ -823,7 +826,7 @@ private int writeInstanceClassInfo(DebugContext context, ClassEntry classEntry,
         pos = writeCUHeader(buffer, pos);
         assert pos == lengthPos + CU_DIE_HEADER_SIZE;
         AbbrevCode abbrevCode;
-        if (classEntry.hasCompiledEntries()) {
+        if (classEntry.hasCompiledMethods()) {
             if (getLocationListIndex(classEntry) == 0) {
                 abbrevCode = AbbrevCode.CLASS_UNIT_2;
             } else {
@@ -844,15 +847,15 @@ private int writeInstanceClassInfo(DebugContext context, ClassEntry classEntry,
         }
         log(context, "  [0x%08x]     name  0x%x (%s)", pos, debugStringIndex(name), name);
         pos = writeStrSectionOffset(uniqueDebugString(name), buffer, pos);
-        String compilationDirectory = dwarfSections.getCachePath();
+        String compilationDirectory = uniqueDebugString(dwarfSections.getCachePath());
         log(context, "  [0x%08x]     comp_dir  0x%x (%s)", pos, debugStringIndex(compilationDirectory), compilationDirectory);
         pos = writeStrSectionOffset(compilationDirectory, buffer, pos);
         if (abbrevCode == AbbrevCode.CLASS_UNIT_2 || abbrevCode == AbbrevCode.CLASS_UNIT_3) {
             int codeRangesIndex = getCodeRangesIndex(classEntry);
             log(context, "  [0x%08x]     ranges  0x%x", pos, codeRangesIndex);
             pos = writeRangeListsSectionOffset(codeRangesIndex, buffer, pos);
-            // write low_pc as well as ranges so that location lists can default the base address
-            int lo = classEntry.lowpc();
+            // write low_pc as well as ranges so that lcoation lists can default the base address
+            long lo = classEntry.lowpc();
             log(context, "  [0x%08x]     low_pc  0x%x", pos, codeRangesIndex);
             pos = writeAttrAddress(lo, buffer, pos);
             int lineIndex = getLineIndex(classEntry);
@@ -971,7 +974,7 @@ private int writeSuperReference(DebugContext context, long typeSignature, String
     }
 
     private int writeFields(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) {
-        return classEntry.fields().filter(DwarfInfoSectionImpl::isManifestedField).reduce(p,
+        return classEntry.getFields().stream().filter(DwarfInfoSectionImpl::isManifestedField).reduce(p,
                         (pos, fieldEntry) -> writeField(context, classEntry, fieldEntry, buffer, pos),
                         (oldPos, newPos) -> newPos);
     }
@@ -1003,7 +1006,7 @@ private int writeField(DebugContext context, StructureTypeEntry entry, FieldEntr
         log(context, "  [0x%08x] <2> Abbrev Number %d", pos, abbrevCode.ordinal());
         pos = writeAbbrevCode(abbrevCode, buffer, pos);
 
-        String name = fieldEntry.fieldName();
+        String name = uniqueDebugString(fieldEntry.fieldName());
         log(context, "  [0x%08x]     name  0x%x (%s)", pos, debugStringIndex(name), name);
         pos = writeStrSectionOffset(name, buffer, pos);
         /* We may not have a file and line for a field. */
@@ -1053,13 +1056,13 @@ private int writeSkeletonMethodDeclarations(DebugContext context, ClassEntry cla
 
     private int writeSkeletonMethodDeclaration(DebugContext context, ClassEntry classEntry, MethodEntry method, byte[] buffer, int p) {
         int pos = p;
-        log(context, "  [0x%08x] method declaration %s::%s", pos, classEntry.getTypeName(), method.methodName());
+        log(context, "  [0x%08x] method declaration %s::%s", pos, classEntry.getTypeName(), method.getMethodName());
         AbbrevCode abbrevCode = AbbrevCode.METHOD_DECLARATION_SKELETON;
         log(context, "  [0x%08x] <2> Abbrev Number %d", pos, abbrevCode.ordinal());
         pos = writeAbbrevCode(abbrevCode, buffer, pos);
         log(context, "  [0x%08x]     external  true", pos);
         pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos);
-        String name = uniqueDebugString(method.methodName());
+        String name = uniqueDebugString(method.getMethodName());
         log(context, "  [0x%08x]     name 0x%x (%s)", pos, debugStringIndex(name), name);
         pos = writeStrSectionOffset(name, buffer, pos);
         String linkageName = uniqueDebugString(method.getSymbolName());
@@ -1087,22 +1090,22 @@ private int writeSkeletonMethodDeclaration(DebugContext context, ClassEntry clas
     private int writeSkeletonMethodParameterDeclarations(DebugContext context, MethodEntry method, byte[] buffer, int p) {
         int pos = p;
         if (!Modifier.isStatic(method.getModifiers())) {
-            DebugLocalInfo paramInfo = method.getThisParam();
+            LocalEntry paramInfo = method.getThisParam();
             pos = writeSkeletonMethodParameterDeclaration(context, paramInfo, true, buffer, pos);
         }
         for (int i = 0; i < method.getParamCount(); i++) {
-            DebugLocalInfo paramInfo = method.getParam(i);
+            LocalEntry paramInfo = method.getParam(i);
             pos = writeSkeletonMethodParameterDeclaration(context, paramInfo, false, buffer, pos);
         }
         return pos;
     }
 
-    private int writeSkeletonMethodParameterDeclaration(DebugContext context, DebugLocalInfo paramInfo, boolean artificial, byte[] buffer,
+    private int writeSkeletonMethodParameterDeclaration(DebugContext context, LocalEntry paramInfo, boolean artificial, byte[] buffer,
                     int p) {
         int pos = p;
         log(context, "  [0x%08x] method parameter declaration", pos);
         AbbrevCode abbrevCode;
-        TypeEntry paramType = lookupType(paramInfo.valueType());
+        TypeEntry paramType = paramInfo.type();
         if (artificial) {
             abbrevCode = AbbrevCode.METHOD_PARAMETER_DECLARATION_4;
         } else {
@@ -1125,29 +1128,38 @@ private int writeMethodDeclarations(DebugContext context, ClassEntry classEntry,
         for (MethodEntry method : classEntry.getMethods()) {
             if (method.isInRange() || method.isInlined()) {
                 /*
-                 * Declare all methods whether or not they have been compiled or inlined.
+                 * Declare all methods whether they have been compiled or inlined.
                  */
-                pos = writeMethodDeclaration(context, classEntry, method, buffer, pos);
+                pos = writeMethodDeclaration(context, classEntry, method, false, buffer, pos);
             }
         }
 
         return pos;
     }
 
-    private int writeMethodDeclaration(DebugContext context, ClassEntry classEntry, MethodEntry method, byte[] buffer, int p) {
+    private int writeMethodDeclaration(DebugContext context, ClassEntry classEntry, MethodEntry method, boolean isInlined, byte[] buffer, int p) {
         int pos = p;
         String methodKey = method.getSymbolName();
         String linkageName = uniqueDebugString(methodKey);
         setMethodDeclarationIndex(method, pos);
         int modifiers = method.getModifiers();
         boolean isStatic = Modifier.isStatic(modifiers);
-        log(context, "  [0x%08x] method declaration %s::%s", pos, classEntry.getTypeName(), method.methodName());
-        AbbrevCode abbrevCode = (isStatic ? AbbrevCode.METHOD_DECLARATION_STATIC : AbbrevCode.METHOD_DECLARATION);
+        log(context, "  [0x%08x] method declaration %s::%s", pos, classEntry.getTypeName(), method.getMethodName());
+        AbbrevCode abbrevCode;
+        if (isInlined) {
+            abbrevCode = (isStatic ? AbbrevCode.METHOD_DECLARATION_INLINE_STATIC : AbbrevCode.METHOD_DECLARATION_INLINE);
+        } else {
+            abbrevCode = (isStatic ? AbbrevCode.METHOD_DECLARATION_STATIC : AbbrevCode.METHOD_DECLARATION);
+        }
         log(context, "  [0x%08x] <2> Abbrev Number %d", pos, abbrevCode.ordinal());
         pos = writeAbbrevCode(abbrevCode, buffer, pos);
+        if (isInlined) {
+            log(context, "  [0x%08x]     inline  0x%x", pos, DwarfInline.DW_INL_inlined.value());
+            pos = writeAttrInline(DwarfInline.DW_INL_inlined, buffer, pos);
+        }
         log(context, "  [0x%08x]     external  true", pos);
         pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos);
-        String name = uniqueDebugString(method.methodName());
+        String name = uniqueDebugString(method.getMethodName());
         log(context, "  [0x%08x]     name 0x%x (%s)", pos, debugStringIndex(name), name);
         pos = writeStrSectionOffset(name, buffer, pos);
         FileEntry fileEntry = method.getFileEntry();
@@ -1176,7 +1188,7 @@ private int writeMethodDeclaration(DebugContext context, ClassEntry classEntry,
         long typeSignature = classEntry.getLayoutTypeSignature();
         log(context, "  [0x%08x]     containing_type 0x%x (%s)", pos, typeSignature, classEntry.getTypeName());
         pos = writeTypeSignature(typeSignature, buffer, pos);
-        if (abbrevCode == AbbrevCode.METHOD_DECLARATION) {
+        if (abbrevCode == AbbrevCode.METHOD_DECLARATION | abbrevCode == AbbrevCode.METHOD_DECLARATION_INLINE) {
             /* Record the current position so we can back patch the object pointer. */
             int objectPointerIndex = pos;
             /*
@@ -1204,26 +1216,26 @@ private int writeMethodParameterDeclarations(DebugContext context, ClassEntry cl
         int refAddr;
         if (!Modifier.isStatic(method.getModifiers())) {
             refAddr = pos;
-            DebugLocalInfo paramInfo = method.getThisParam();
+            LocalEntry paramInfo = method.getThisParam();
             setMethodLocalIndex(classEntry, method, paramInfo, refAddr);
             pos = writeMethodParameterDeclaration(context, paramInfo, fileIdx, true, level, buffer, pos);
         }
         for (int i = 0; i < method.getParamCount(); i++) {
             refAddr = pos;
-            DebugLocalInfo paramInfo = method.getParam(i);
+            LocalEntry paramInfo = method.getParam(i);
             setMethodLocalIndex(classEntry, method, paramInfo, refAddr);
             pos = writeMethodParameterDeclaration(context, paramInfo, fileIdx, false, level, buffer, pos);
         }
         return pos;
     }
 
-    private int writeMethodParameterDeclaration(DebugContext context, DebugLocalInfo paramInfo, int fileIdx, boolean artificial, int level, byte[] buffer,
+    private int writeMethodParameterDeclaration(DebugContext context, LocalEntry paramInfo, int fileIdx, boolean artificial, int level, byte[] buffer,
                     int p) {
         int pos = p;
         log(context, "  [0x%08x] method parameter declaration", pos);
         AbbrevCode abbrevCode;
         String paramName = paramInfo.name();
-        TypeEntry paramType = lookupType(paramInfo.valueType());
+        TypeEntry paramType = paramInfo.type();
         int line = paramInfo.line();
         if (artificial) {
             abbrevCode = AbbrevCode.METHOD_PARAMETER_DECLARATION_1;
@@ -1259,20 +1271,20 @@ private int writeMethodLocalDeclarations(DebugContext context, ClassEntry classE
         int refAddr;
         for (int i = 0; i < method.getLocalCount(); i++) {
             refAddr = pos;
-            DebugLocalInfo localInfo = method.getLocal(i);
+            LocalEntry localInfo = method.getLocal(i);
             setMethodLocalIndex(classEntry, method, localInfo, refAddr);
             pos = writeMethodLocalDeclaration(context, localInfo, fileIdx, level, buffer, pos);
         }
         return pos;
     }
 
-    private int writeMethodLocalDeclaration(DebugContext context, DebugLocalInfo paramInfo, int fileIdx, int level, byte[] buffer,
+    private int writeMethodLocalDeclaration(DebugContext context, LocalEntry paramInfo, int fileIdx, int level, byte[] buffer,
                     int p) {
         int pos = p;
         log(context, "  [0x%08x] method local declaration", pos);
         AbbrevCode abbrevCode;
-        String paramName = paramInfo.name();
-        TypeEntry paramType = lookupType(paramInfo.valueType());
+        String paramName = uniqueDebugString(paramInfo.name());
+        TypeEntry paramType = paramInfo.type();
         int line = paramInfo.line();
         if (line >= 0) {
             abbrevCode = AbbrevCode.METHOD_LOCAL_DECLARATION_1;
@@ -1282,7 +1294,7 @@ private int writeMethodLocalDeclaration(DebugContext context, DebugLocalInfo par
         log(context, "  [0x%08x] <%d> Abbrev Number %d", pos, level, abbrevCode.ordinal());
         pos = writeAbbrevCode(abbrevCode, buffer, pos);
         log(context, "  [0x%08x]     name %s", pos, paramName);
-        pos = writeStrSectionOffset(uniqueDebugString(paramName), buffer, pos);
+        pos = writeStrSectionOffset(paramName, buffer, pos);
         if (abbrevCode == AbbrevCode.METHOD_LOCAL_DECLARATION_1) {
             log(context, "  [0x%08x]     file 0x%x", pos, fileIdx);
             pos = writeAttrData2((short) fileIdx, buffer, pos);
@@ -1298,7 +1310,7 @@ private int writeMethodLocalDeclaration(DebugContext context, DebugLocalInfo par
     }
 
     private int writeInterfaceImplementors(DebugContext context, InterfaceClassEntry interfaceClassEntry, byte[] buffer, int p) {
-        return interfaceClassEntry.implementors().reduce(p,
+        return interfaceClassEntry.getImplementors().stream().reduce(p,
                         (pos, classEntry) -> writeInterfaceImplementor(context, classEntry, buffer, pos),
                         (oldPos, newPos) -> newPos);
     }
@@ -1348,7 +1360,7 @@ private int writeForeignStructLayout(DebugContext context, ForeignTypeEntry fore
             long typeSignature = parent.getLayoutTypeSignature();
             pos = writeSuperReference(context, typeSignature, parent.getTypedefName(), buffer, pos);
         }
-        pos = writeStructFields(context, foreignTypeEntry.fields(), buffer, pos);
+        pos = writeStructFields(context, foreignTypeEntry.getFields(), buffer, pos);
         /*
          * Write a terminating null attribute.
          */
@@ -1442,7 +1454,7 @@ private int writeStaticFieldLocations(DebugContext context, ClassEntry classEntr
          * offset indicates that the field has been folded into code as an unmaterialized constant.
          */
         Cursor cursor = new Cursor(p);
-        classEntry.fields().filter(DwarfInfoSectionImpl::isManifestedStaticField)
+        classEntry.getFields().stream().filter(DwarfInfoSectionImpl::isManifestedStaticField)
                         .forEach(fieldEntry -> {
                             cursor.set(writeClassStaticFieldLocation(context, classEntry, fieldEntry, buffer, cursor.get()));
                         });
@@ -1455,7 +1467,7 @@ private int writeStaticFieldDeclarations(DebugContext context, ClassEntry classE
          * offset indicates that the field has been folded into code as an unmaterialized constant.
          */
         Cursor cursor = new Cursor(p);
-        classEntry.fields().filter(DwarfInfoSectionImpl::isManifestedStaticField)
+        classEntry.getFields().stream().filter(DwarfInfoSectionImpl::isManifestedStaticField)
                         .forEach(fieldEntry -> {
                             cursor.set(writeClassStaticFieldDeclaration(context, classEntry, fieldEntry, buffer, cursor.get()));
                         });
@@ -1483,7 +1495,7 @@ private int writeClassStaticFieldDeclaration(DebugContext context, ClassEntry cl
         log(context, "  [0x%08x] <2> Abbrev Number %d", pos, abbrevCode.ordinal());
         pos = writeAbbrevCode(abbrevCode, buffer, pos);
 
-        String name = fieldEntry.fieldName();
+        String name = uniqueDebugString(fieldEntry.fieldName());
         log(context, "  [0x%08x]     name  0x%x (%s)", pos, debugStringIndex(name), name);
         pos = writeStrSectionOffset(name, buffer, pos);
         /* We may not have a file and line for a field. */
@@ -1551,7 +1563,7 @@ private int writeArrayLayoutTypeUnit(DebugContext context, ArrayTypeEntry arrayT
         AbbrevCode abbrevCode = AbbrevCode.ARRAY_LAYOUT;
         log(context, "  [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal());
         pos = writeAbbrevCode(abbrevCode, buffer, pos);
-        String name = arrayTypeEntry.getTypeName();
+        String name = uniqueDebugString(arrayTypeEntry.getTypeName());
         log(context, "  [0x%08x]     name 0x%x (%s)", pos, debugStringIndex(name), name);
         pos = writeStrSectionOffset(name, buffer, pos);
         log(context, "  [0x%08x]     byte_size  0x%x", pos, size);
@@ -1606,7 +1618,7 @@ private int writeArray(DebugContext context, ArrayTypeEntry arrayTypeEntry, byte
         String name = uniqueDebugString("JAVA");
         log(context, "  [0x%08x]     name  0x%x (%s)", pos, debugStringIndex(name), name);
         pos = writeStrSectionOffset(name, buffer, pos);
-        String compilationDirectory = dwarfSections.getCachePath();
+        String compilationDirectory = uniqueDebugString(dwarfSections.getCachePath());
         log(context, "  [0x%08x]     comp_dir  0x%x (%s)", pos, debugStringIndex(compilationDirectory), compilationDirectory);
         pos = writeStrSectionOffset(compilationDirectory, buffer, pos);
 
@@ -1641,7 +1653,7 @@ private int writeSkeletonArrayLayout(DebugContext context, ArrayTypeEntry arrayT
         AbbrevCode abbrevCode = AbbrevCode.CLASS_LAYOUT_3;
         log(context, "  [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal());
         pos = writeAbbrevCode(abbrevCode, buffer, pos);
-        String name = arrayTypeEntry.getTypeName();
+        String name = uniqueDebugString(arrayTypeEntry.getTypeName());
         log(context, "  [0x%08x]     name  0x%x (%s)", pos, debugStringIndex(name), name);
         pos = writeStrSectionOffset(name, buffer, pos);
         log(context, "  [0x%08x]     declaration true", pos);
@@ -1657,7 +1669,7 @@ private int writeSkeletonArrayLayout(DebugContext context, ArrayTypeEntry arrayT
 
     private int writeFields(DebugContext context, ArrayTypeEntry arrayTypeEntry, byte[] buffer, int p) {
         Cursor cursor = new Cursor(p);
-        arrayTypeEntry.fields().filter(DwarfInfoSectionImpl::isManifestedField)
+        arrayTypeEntry.getFields().stream().filter(DwarfInfoSectionImpl::isManifestedField)
                         .forEach(fieldEntry -> {
                             cursor.set(writeField(context, arrayTypeEntry, fieldEntry, buffer, cursor.get()));
                         });
@@ -1744,7 +1756,7 @@ private int writeArrayElementField(DebugContext context, int offset, int arrayDa
 
     private int writeMethodLocations(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) {
         Cursor cursor = new Cursor(p);
-        classEntry.compiledEntries().forEach(compiledMethodEntry -> {
+        classEntry.compiledMethods().forEach(compiledMethodEntry -> {
             cursor.set(writeMethodLocation(context, classEntry, compiledMethodEntry, buffer, cursor.get()));
         });
         return cursor.get();
@@ -1752,7 +1764,7 @@ private int writeMethodLocations(DebugContext context, ClassEntry classEntry, by
 
     private int writeMethodLocation(DebugContext context, ClassEntry classEntry, CompiledMethodEntry compiledEntry, byte[] buffer, int p) {
         int pos = p;
-        Range primary = compiledEntry.getPrimary();
+        Range primary = compiledEntry.primary();
         log(context, "  [0x%08x] method location", pos);
         AbbrevCode abbrevCode = AbbrevCode.METHOD_LOCATION;
         log(context, "  [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal());
@@ -1770,7 +1782,7 @@ private int writeMethodLocation(DebugContext context, ClassEntry classEntry, Com
         int methodSpecOffset = getMethodDeclarationIndex(primary.getMethodEntry());
         log(context, "  [0x%08x]     specification  0x%x (%s)", pos, methodSpecOffset, methodKey);
         pos = writeInfoSectionOffset(methodSpecOffset, buffer, pos);
-        HashMap<DebugLocalInfo, List<SubRange>> varRangeMap = primary.getVarRangeMap();
+        Map<LocalEntry, List<Range>> varRangeMap = primary.getVarRangeMap();
         pos = writeMethodParameterLocations(context, classEntry, varRangeMap, primary, 2, buffer, pos);
         pos = writeMethodLocalLocations(context, classEntry, varRangeMap, primary, 2, buffer, pos);
         if (primary.includesInlineRanges()) {
@@ -1786,80 +1798,72 @@ private int writeMethodLocation(DebugContext context, ClassEntry classEntry, Com
         return writeAttrNull(buffer, pos);
     }
 
-    private int writeMethodParameterLocations(DebugContext context, ClassEntry classEntry, HashMap<DebugLocalInfo, List<SubRange>> varRangeMap, Range range, int depth, byte[] buffer, int p) {
+    private int writeMethodParameterLocations(DebugContext context, ClassEntry classEntry, Map<LocalEntry, List<Range>> varRangeMap, Range range, int depth, byte[] buffer, int p) {
         int pos = p;
         MethodEntry methodEntry;
         if (range.isPrimary()) {
             methodEntry = range.getMethodEntry();
         } else {
             assert !range.isLeaf() : "should only be looking up var ranges for inlined calls";
-            methodEntry = range.getFirstCallee().getMethodEntry();
+            methodEntry = range.getCallees().getFirst().getMethodEntry();
         }
         if (!Modifier.isStatic(methodEntry.getModifiers())) {
-            DebugLocalInfo thisParamInfo = methodEntry.getThisParam();
+            LocalEntry thisParamInfo = methodEntry.getThisParam();
             int refAddr = getMethodLocalIndex(classEntry, methodEntry, thisParamInfo);
-            List<SubRange> ranges = varRangeMap.get(thisParamInfo);
-            pos = writeMethodLocalLocation(context, range, thisParamInfo, refAddr, ranges, depth, true, buffer, pos);
+            pos = writeMethodLocalLocation(context, range, thisParamInfo, varRangeMap, refAddr, depth, true, buffer, pos);
         }
         for (int i = 0; i < methodEntry.getParamCount(); i++) {
-            DebugLocalInfo paramInfo = methodEntry.getParam(i);
+            LocalEntry paramInfo = methodEntry.getParam(i);
             int refAddr = getMethodLocalIndex(classEntry, methodEntry, paramInfo);
-            List<SubRange> ranges = varRangeMap.get(paramInfo);
-            pos = writeMethodLocalLocation(context, range, paramInfo, refAddr, ranges, depth, true, buffer, pos);
+            pos = writeMethodLocalLocation(context, range, paramInfo, varRangeMap, refAddr, depth, true, buffer, pos);
         }
         return pos;
     }
 
-    private int writeMethodLocalLocations(DebugContext context, ClassEntry classEntry, HashMap<DebugLocalInfo, List<SubRange>> varRangeMap, Range range, int depth, byte[] buffer, int p) {
+    private int writeMethodLocalLocations(DebugContext context, ClassEntry classEntry, Map<LocalEntry, List<Range>> varRangeMap, Range range, int depth, byte[] buffer, int p) {
         int pos = p;
         MethodEntry methodEntry;
         if (range.isPrimary()) {
             methodEntry = range.getMethodEntry();
         } else {
             assert !range.isLeaf() : "should only be looking up var ranges for inlined calls";
-            methodEntry = range.getFirstCallee().getMethodEntry();
+            methodEntry = range.getCallees().getFirst().getMethodEntry();
         }
         int count = methodEntry.getLocalCount();
         for (int i = 0; i < count; i++) {
-            DebugLocalInfo localInfo = methodEntry.getLocal(i);
+            LocalEntry localInfo = methodEntry.getLocal(i);
             int refAddr = getMethodLocalIndex(classEntry, methodEntry, localInfo);
-            List<SubRange> ranges = varRangeMap.get(localInfo);
-            pos = writeMethodLocalLocation(context, range, localInfo, refAddr, ranges, depth, false, buffer, pos);
+            pos = writeMethodLocalLocation(context, range, localInfo, varRangeMap, refAddr, depth, false, buffer, pos);
         }
         return pos;
     }
 
-    private int writeMethodLocalLocation(DebugContext context, Range range, DebugLocalInfo localInfo, int refAddr, List<SubRange> ranges, int depth, boolean isParam, byte[] buffer,
+    private int writeMethodLocalLocation(DebugContext context, Range range, LocalEntry localInfo, Map<LocalEntry, List<Range>> varRangeMap, int refAddr, int depth, boolean isParam, byte[] buffer,
                     int p) {
         int pos = p;
-        log(context, "  [0x%08x] method %s location %s:%s", pos, (isParam ? "parameter" : "local"), localInfo.name(), localInfo.typeName());
-        List<DebugLocalValueInfo> localValues = new ArrayList<>();
-        for (SubRange subrange : ranges) {
-            DebugLocalValueInfo value = subrange.lookupValue(localInfo);
+        log(context, "  [0x%08x] method %s location %s:%s", pos, (isParam ? "parameter" : "local"), localInfo.name(), localInfo.type().getTypeName());
+
+        boolean hasValues = false;
+        for (Range subrange : varRangeMap.getOrDefault(localInfo, new ArrayList<>())) {
+            LocalValueEntry value = subrange.lookupValue(localInfo);
             if (value != null) {
-                log(context, "  [0x%08x]     local  %s:%s [0x%x, 0x%x] = %s", pos, value.name(), value.typeName(), subrange.getLo(), subrange.getHi(), formatValue(value));
-                switch (value.localKind()) {
-                    case REGISTER:
-                    case STACKSLOT:
-                        localValues.add(value);
-                        break;
-                    case CONSTANT:
-                        JavaConstant constant = value.constantValue();
-                        // can only handle primitive or null constants just now
-                        if (constant instanceof PrimitiveConstant || constant.getJavaKind() == JavaKind.Object) {
-                            localValues.add(value);
-                        }
-                        break;
-                    default:
-                        break;
+                log(context, "  [0x%08x]     local  %s:%s [0x%x, 0x%x] = %s", pos, value.name(), value.type().getTypeName(), subrange.getLo(), subrange.getHi(), formatValue(value));
+                DebugInfoProvider.LocalValueKind localKind = value.localKind();
+                // can only handle primitive or null constants just now
+                if (localKind == DebugInfoProvider.LocalValueKind.REGISTER || localKind == DebugInfoProvider.LocalValueKind.STACK || (
+                        localKind == DebugInfoProvider.LocalValueKind.CONSTANT && (value.constant() instanceof PrimitiveConstant || value.constant().getJavaKind() == JavaKind.Object)
+                        )) {
+                    hasValues = true;
+                    break;
                 }
             }
         }
+
         AbbrevCode abbrevCode;
-        if (localValues.isEmpty()) {
-            abbrevCode = (isParam ? AbbrevCode.METHOD_PARAMETER_LOCATION_1 : AbbrevCode.METHOD_LOCAL_LOCATION_1);
-        } else {
+        if (range.hasLocalValues(localInfo)) {
             abbrevCode = (isParam ? AbbrevCode.METHOD_PARAMETER_LOCATION_2 : AbbrevCode.METHOD_LOCAL_LOCATION_2);
+        } else {
+            abbrevCode = (isParam ? AbbrevCode.METHOD_PARAMETER_LOCATION_1 : AbbrevCode.METHOD_LOCAL_LOCATION_1);
         }
         log(context, "  [0x%08x] <%d> Abbrev Number %d", pos, depth, abbrevCode.ordinal());
         pos = writeAbbrevCode(abbrevCode, buffer, pos);
@@ -1878,16 +1882,15 @@ private int writeMethodLocalLocation(DebugContext context, Range range, DebugLoc
      * Go through the subranges and generate concrete debug entries for inlined methods.
      */
     private int generateConcreteInlinedMethods(DebugContext context, ClassEntry classEntry, CompiledMethodEntry compiledEntry, byte[] buffer, int p) {
-        Range primary = compiledEntry.getPrimary();
+        Range primary = compiledEntry.primary();
         if (primary.isLeaf()) {
             return p;
         }
         int pos = p;
         log(context, "  [0x%08x] concrete entries [0x%x,0x%x] %s", pos, primary.getLo(), primary.getHi(), primary.getFullMethodName());
         int depth = 0;
-        Iterator<SubRange> iterator = compiledEntry.topDownRangeIterator();
-        while (iterator.hasNext()) {
-            SubRange subrange = iterator.next();
+        Stream<Range> iterator = compiledEntry.topDownRangeStream();
+        for (Range subrange : iterator.toList()) {
             if (subrange.isLeaf()) {
                 // we only generate concrete methods for non-leaf entries
                 continue;
@@ -1899,9 +1902,9 @@ private int generateConcreteInlinedMethods(DebugContext context, ClassEntry clas
             }
             depth = subrange.getDepth();
             pos = writeInlineSubroutine(context, classEntry, subrange, depth + 2, buffer, pos);
-            HashMap<DebugLocalInfo, List<SubRange>> varRangeMap = subrange.getVarRangeMap();
             // increment depth to account for parameter and method locations
             depth++;
+            Map<LocalEntry, List<Range>> varRangeMap = subrange.getVarRangeMap();
             pos = writeMethodParameterLocations(context, classEntry, varRangeMap, subrange, depth + 2, buffer, pos);
             pos = writeMethodLocalLocations(context, classEntry, varRangeMap, subrange, depth + 2, buffer, pos);
         }
@@ -1913,17 +1916,22 @@ private int generateConcreteInlinedMethods(DebugContext context, ClassEntry clas
         return pos;
     }
 
-    private int writeInlineSubroutine(DebugContext context, ClassEntry classEntry, SubRange caller, int depth, byte[] buffer, int p) {
+    private int writeInlineSubroutine(DebugContext context, ClassEntry classEntry, Range caller, int depth, byte[] buffer, int p) {
         assert !caller.isLeaf();
         // the supplied range covers an inline call and references the caller method entry. its
         // child ranges all reference the same inlined called method. leaf children cover code for
         // that inlined method. non-leaf children cover code for recursively inlined methods.
         // identify the inlined method by looking at the first callee
-        Range callee = caller.getFirstCallee();
+        Range callee = caller.getCallees().getFirst();
         MethodEntry methodEntry = callee.getMethodEntry();
         String methodKey = methodEntry.getSymbolName();
         /* the abstract index was written in the method's class entry */
-        int abstractOriginIndex = (classEntry == methodEntry.ownerType() ? getMethodDeclarationIndex(methodEntry) : getAbstractInlineMethodIndex(classEntry, methodEntry));
+        int abstractOriginIndex;
+        if (classEntry == methodEntry.getOwnerType() && !dwarfSections.isRuntimeCompilation()) {
+            abstractOriginIndex = getMethodDeclarationIndex(methodEntry);
+        } else {
+            abstractOriginIndex = getAbstractInlineMethodIndex(classEntry, methodEntry);
+        }
 
         int pos = p;
         log(context, "  [0x%08x] concrete inline subroutine [0x%x, 0x%x] %s", pos, caller.getLo(), caller.getHi(), methodKey);
@@ -1945,7 +1953,7 @@ private int writeInlineSubroutine(DebugContext context, ClassEntry classEntry, S
                 fileIndex = classEntry.getFileIdx();
             }
         }
-        final AbbrevCode abbrevCode = AbbrevCode.INLINED_SUBROUTINE_WITH_CHILDREN;
+        final AbbrevCode abbrevCode = AbbrevCode.INLINED_SUBROUTINE;
         log(context, "  [0x%08x] <%d> Abbrev Number %d", pos, depth, abbrevCode.ordinal());
         pos = writeAbbrevCode(abbrevCode, buffer, pos);
         log(context, "  [0x%08x]     abstract_origin  0x%x", pos, abstractOriginIndex);
@@ -1962,31 +1970,34 @@ private int writeInlineSubroutine(DebugContext context, ClassEntry classEntry, S
     }
 
     private int writeAbstractInlineMethods(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) {
-        EconomicSet<MethodEntry> inlinedMethods = collectInlinedMethods(context, classEntry, p);
+        HashSet<MethodEntry> inlinedMethods = collectInlinedMethods(context, classEntry, p);
         int pos = p;
         for (MethodEntry methodEntry : inlinedMethods) {
             // n.b. class entry used to index the method belongs to the inlining method
             // not the inlined method
             setAbstractInlineMethodIndex(classEntry, methodEntry, pos);
-            pos = writeAbstractInlineMethod(context, classEntry, methodEntry, buffer, pos);
+            if (dwarfSections.isRuntimeCompilation()) {
+                pos = writeMethodDeclaration(context, classEntry, methodEntry, true, buffer, pos);
+            } else {
+                pos = writeAbstractInlineMethod(context, classEntry, methodEntry, buffer, pos);
+            }
         }
         return pos;
     }
 
-    private EconomicSet<MethodEntry> collectInlinedMethods(DebugContext context, ClassEntry classEntry, int p) {
-        final EconomicSet<MethodEntry> methods = EconomicSet.create();
-        classEntry.compiledEntries().forEach(compiledEntry -> addInlinedMethods(context, compiledEntry, compiledEntry.getPrimary(), methods, p));
+    private HashSet<MethodEntry> collectInlinedMethods(DebugContext context, ClassEntry classEntry, int p) {
+        final HashSet<MethodEntry> methods = new HashSet<>();
+        classEntry.compiledMethods().forEach(compiledMethod -> addInlinedMethods(context, compiledMethod, compiledMethod.primary(), methods, p));
         return methods;
     }
 
-    private void addInlinedMethods(DebugContext context, CompiledMethodEntry compiledEntry, Range primary, EconomicSet<MethodEntry> hashSet, int p) {
+    private void addInlinedMethods(DebugContext context, CompiledMethodEntry compiledEntry, Range primary, HashSet<MethodEntry> hashSet, int p) {
         if (primary.isLeaf()) {
             return;
         }
         verboseLog(context, "  [0x%08x] collect abstract inlined methods %s", p, primary.getFullMethodName());
-        Iterator<SubRange> iterator = compiledEntry.topDownRangeIterator();
-        while (iterator.hasNext()) {
-            SubRange subrange = iterator.next();
+        Stream<Range> iterator = compiledEntry.topDownRangeStream();
+        for (Range subrange : iterator.toList()) {
             if (subrange.isLeaf()) {
                 // we only generate abstract inline methods for non-leaf entries
                 continue;
@@ -1996,7 +2007,7 @@ private void addInlinedMethods(DebugContext context, CompiledMethodEntry compile
             // for
             // that inlined method. non-leaf children cover code for recursively inlined methods.
             // identify the inlined method by looking at the first callee
-            Range callee = subrange.getFirstCallee();
+            Range callee = subrange.getCallees().getFirst();
             MethodEntry methodEntry = callee.getMethodEntry();
             if (hashSet.add(methodEntry)) {
                 verboseLog(context, "  [0x%08x]   add abstract inlined method %s", p, methodEntry.getSymbolName());
@@ -2006,7 +2017,7 @@ private void addInlinedMethods(DebugContext context, CompiledMethodEntry compile
 
     private int writeAbstractInlineMethod(DebugContext context, ClassEntry classEntry, MethodEntry method, byte[] buffer, int p) {
         int pos = p;
-        log(context, "  [0x%08x] abstract inline method %s::%s", pos, classEntry.getTypeName(), method.methodName());
+        log(context, "  [0x%08x] abstract inline method %s::%s", pos, classEntry.getTypeName(), method.getMethodName());
         AbbrevCode abbrevCode = AbbrevCode.ABSTRACT_INLINE_METHOD;
         log(context, "  [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal());
         pos = writeAbbrevCode(abbrevCode, buffer, pos);
@@ -2024,7 +2035,7 @@ private int writeAbstractInlineMethod(DebugContext context, ClassEntry classEntr
          * If the inline method exists in a different CU then write locals and params otherwise we
          * can just reuse the locals and params in the declaration
          */
-        if (classEntry != method.ownerType()) {
+        if (classEntry != method.getOwnerType()) {
             FileEntry fileEntry = method.getFileEntry();
             if (fileEntry == null) {
                 fileEntry = classEntry.getFileEntry();
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 ae1071c2e700..16cd374f4e9b 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
@@ -41,7 +41,6 @@
 import com.oracle.objectfile.debugentry.CompiledMethodEntry;
 import com.oracle.objectfile.debugentry.FileEntry;
 import com.oracle.objectfile.debugentry.range.Range;
-import com.oracle.objectfile.debugentry.range.SubRange;
 
 /**
  * Section generator for debug_line section.
@@ -84,7 +83,7 @@ public void createContent() {
          */
 
         Cursor byteCount = new Cursor();
-        instanceClassStream().filter(ClassEntry::hasCompiledEntries).forEachOrdered(classEntry -> {
+        instanceClassStream().filter(ClassEntry::hasCompiledMethods).forEachOrdered(classEntry -> {
             setLineIndex(classEntry, byteCount.get());
             int headerSize = headerSize();
             int dirTableSize = computeDirTableSize(classEntry);
@@ -138,7 +137,7 @@ private int computeDirTableSize(ClassEntry classEntry) {
          * 'nul'.
          */
         Cursor cursor = new Cursor();
-        classEntry.dirStream().forEachOrdered(dirEntry -> {
+        classEntry.getDirs().forEach(dirEntry -> {
             int length = countUTF8Bytes(dirEntry.getPathString());
             // We should never have a null or zero length entry in local dirs
             assert length > 0;
@@ -159,9 +158,9 @@ private int computeFileTableSize(ClassEntry classEntry) {
          * time stamps
          */
         Cursor cursor = new Cursor();
-        classEntry.fileStream().forEachOrdered(fileEntry -> {
+        classEntry.getFiles().forEach(fileEntry -> {
             // We want the file base name excluding path.
-            String baseName = fileEntry.getFileName();
+            String baseName = fileEntry.fileName();
             int length = countUTF8Bytes(baseName);
             // We should never have a null or zero length entry in local files.
             assert length > 0;
@@ -193,7 +192,7 @@ public byte[] getOrDecideContent(Map<ObjectFile.Element, LayoutDecisionMap> alre
         LayoutDecisionMap decisionMap = alreadyDecided.get(textElement);
         if (decisionMap != null) {
             Object valueObj = decisionMap.getDecidedValue(LayoutDecision.Kind.VADDR);
-            if (valueObj != null && valueObj instanceof Number) {
+            if (valueObj instanceof Number) {
                 /*
                  * This may not be the final vaddr for the text segment but it will be close enough
                  * to make debug easier i.e. to within a 4k page or two.
@@ -213,7 +212,7 @@ public void writeContent(DebugContext context) {
 
         enableLog(context, cursor.get());
         log(context, "  [0x%08x] DEBUG_LINE", cursor.get());
-        instanceClassStream().filter(ClassEntry::hasCompiledEntries).forEachOrdered(classEntry -> {
+        instanceClassStream().filter(ClassEntry::hasCompiledMethods).forEachOrdered(classEntry -> {
             int pos = cursor.get();
             setLineIndex(classEntry, pos);
             int lengthPos = pos;
@@ -311,7 +310,7 @@ private int writeDirTable(DebugContext context, ClassEntry classEntry, byte[] bu
          */
         Cursor cursor = new Cursor(p);
         Cursor idx = new Cursor(1);
-        classEntry.dirStream().forEach(dirEntry -> {
+        classEntry.getDirs().forEach(dirEntry -> {
             int dirIdx = idx.get();
             assert (classEntry.getDirIdx(dirEntry) == dirIdx);
             String dirPath = dirEntry.getPathString();
@@ -333,12 +332,12 @@ private int writeFileTable(DebugContext context, ClassEntry classEntry, byte[] b
          */
         Cursor cursor = new Cursor(p);
         Cursor idx = new Cursor(1);
-        classEntry.fileStream().forEach(fileEntry -> {
+        classEntry.getFiles().forEach(fileEntry -> {
             int pos = cursor.get();
             int fileIdx = idx.get();
             assert classEntry.getFileIdx(fileEntry) == fileIdx;
             int dirIdx = classEntry.getDirIdx(fileEntry);
-            String baseName = fileEntry.getFileName();
+            String baseName = fileEntry.fileName();
             verboseLog(context, "  [0x%08x] %-5d %-5d %s", pos, fileIdx, dirIdx, baseName);
             pos = writeUTF8StringBytes(baseName, buffer, pos);
             pos = writeULEB(dirIdx, buffer, pos);
@@ -359,7 +358,7 @@ private int writeFileTable(DebugContext context, ClassEntry classEntry, byte[] b
 
     private int writeCompiledMethodLineInfo(DebugContext context, ClassEntry classEntry, CompiledMethodEntry compiledEntry, byte[] buffer, int p) {
         int pos = p;
-        Range primaryRange = compiledEntry.getPrimary();
+        Range primaryRange = compiledEntry.primary();
         // the compiled method might be a substitution and not in the file of the class entry
         FileEntry fileEntry = primaryRange.getFileEntry();
         if (fileEntry == null) {
@@ -367,7 +366,7 @@ private int writeCompiledMethodLineInfo(DebugContext context, ClassEntry classEn
                             primaryRange.getFullMethodNameWithParams());
             return pos;
         }
-        String file = fileEntry.getFileName();
+        String file = fileEntry.fileName();
         int fileIdx = classEntry.getFileIdx(fileEntry);
         /*
          * Each primary represents a method i.e. a contiguous sequence of subranges. For normal
@@ -420,21 +419,21 @@ private int writeCompiledMethodLineInfo(DebugContext context, ClassEntry classEn
         /*
          * Now write a row for each subrange lo and hi.
          */
-        Iterator<SubRange> iterator = compiledEntry.leafRangeIterator();
+        Iterator<Range> iterator = compiledEntry.leafRangeStream().iterator();
         if (prologueRange != null) {
             // skip already processed range
-            SubRange first = iterator.next();
+            Range first = iterator.next();
             assert first == prologueRange;
         }
         while (iterator.hasNext()) {
-            SubRange subrange = iterator.next();
+            Range subrange = iterator.next();
             assert subrange.getLo() >= primaryRange.getLo();
             assert subrange.getHi() <= primaryRange.getHi();
             FileEntry subFileEntry = subrange.getFileEntry();
             if (subFileEntry == null) {
                 continue;
             }
-            String subfile = subFileEntry.getFileName();
+            String subfile = subFileEntry.fileName();
             int subFileIdx = classEntry.getFileIdx(subFileEntry);
             assert subFileIdx > 0;
             long subLine = subrange.getLine();
@@ -555,10 +554,10 @@ private int writeCompiledMethodLineInfo(DebugContext context, ClassEntry classEn
 
     private int writeLineNumberTable(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) {
         Cursor cursor = new Cursor(p);
-        classEntry.compiledEntries().forEachOrdered(compiledMethod -> {
+        classEntry.compiledMethods().forEach(compiledMethod -> {
             int pos = cursor.get();
-            String methodName = compiledMethod.getPrimary().getFullMethodNameWithParams();
-            String fileName = compiledMethod.getClassEntry().getFullFileName();
+            String methodName = compiledMethod.primary().getFullMethodNameWithParams();
+            String fileName = compiledMethod.classEntry().getFullFileName();
             log(context, "  [0x%08x] %s %s", pos, methodName, fileName);
             pos = writeCompiledMethodLineInfo(context, classEntry, compiledMethod, buffer, pos);
             cursor.set(pos);
@@ -566,11 +565,11 @@ private int writeLineNumberTable(DebugContext context, ClassEntry classEntry, by
         return cursor.get();
     }
 
-    private static SubRange prologueLeafRange(CompiledMethodEntry compiledEntry) {
-        Iterator<SubRange> iterator = compiledEntry.leafRangeIterator();
+    private static Range prologueLeafRange(CompiledMethodEntry compiledEntry) {
+        Iterator<Range> iterator = compiledEntry.leafRangeStream().iterator();
         if (iterator.hasNext()) {
-            SubRange range = iterator.next();
-            if (range.getLo() == compiledEntry.getPrimary().getLo()) {
+            Range range = iterator.next();
+            if (range.getLo() == compiledEntry.primary().getLo()) {
                 return range;
             }
         }
@@ -579,10 +578,9 @@ private static SubRange prologueLeafRange(CompiledMethodEntry compiledEntry) {
 
     private int writeCopyOp(DebugContext context, byte[] buffer, int p) {
         DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNS_copy;
-        int pos = p;
         debugCopyCount++;
-        verboseLog(context, "  [0x%08x] Copy %d", pos, debugCopyCount);
-        return writeLineOpcode(opcode, buffer, pos);
+        verboseLog(context, "  [0x%08x] Copy %d", p, debugCopyCount);
+        return writeLineOpcode(opcode, buffer, p);
     }
 
     private int writeAdvancePCOp(DebugContext context, long uleb, byte[] buffer, int p) {
@@ -622,24 +620,21 @@ private int writeSetColumnOp(DebugContext context, long uleb, byte[] buffer, int
     @SuppressWarnings("unused")
     private int writeNegateStmtOp(DebugContext context, byte[] buffer, int p) {
         DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNS_negate_stmt;
-        int pos = p;
-        return writeLineOpcode(opcode, buffer, pos);
+        return writeLineOpcode(opcode, buffer, p);
     }
 
     private int writeSetBasicBlockOp(DebugContext context, byte[] buffer, int p) {
         DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNS_set_basic_block;
-        int pos = p;
-        verboseLog(context, "  [0x%08x] Set basic block", pos);
-        return writeLineOpcode(opcode, buffer, pos);
+        verboseLog(context, "  [0x%08x] Set basic block", p);
+        return writeLineOpcode(opcode, buffer, p);
     }
 
     private int writeConstAddPCOp(DebugContext context, byte[] buffer, int p) {
         DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNS_const_add_pc;
-        int pos = p;
         int advance = opcodeAddress((byte) 255);
         debugAddress += advance;
-        verboseLog(context, "  [0x%08x] Advance PC by constant %d to 0x%08x", pos, advance, debugAddress);
-        return writeLineOpcode(opcode, buffer, pos);
+        verboseLog(context, "  [0x%08x] Advance PC by constant %d to 0x%08x", p, advance, debugAddress);
+        return writeLineOpcode(opcode, buffer, p);
     }
 
     private int writeFixedAdvancePCOp(DebugContext context, short arg, byte[] buffer, int p) {
@@ -677,7 +672,7 @@ private int writeSetAddressOp(DebugContext context, long arg, byte[] buffer, int
          */
         pos = writeULEB(9, buffer, pos);
         pos = writeLineOpcode(opcode, buffer, pos);
-        return writeRelocatableCodeOffset(arg, buffer, pos);
+        return writeCodeOffset(arg, buffer, pos);
     }
 
     @SuppressWarnings("unused")
@@ -733,15 +728,14 @@ private static int opcodeLine(byte opcode) {
     }
 
     private int writeSpecialOpcode(DebugContext context, byte opcode, byte[] buffer, int p) {
-        int pos = p;
         if (debug && opcode == 0) {
             verboseLog(context, "  [0x%08x] ERROR Special Opcode %d: Address 0x%08x Line %d", debugAddress, debugLine);
         }
         debugAddress += opcodeAddress(opcode);
         debugLine += opcodeLine(opcode);
         verboseLog(context, "  [0x%08x] Special Opcode %d: advance Address by %d to 0x%08x and Line by %d to %d",
-                        pos, opcodeId(opcode), opcodeAddress(opcode), debugAddress, opcodeLine(opcode), debugLine);
-        return writeByte(opcode, buffer, pos);
+                p, opcodeId(opcode), opcodeAddress(opcode), debugAddress, opcodeLine(opcode), debugLine);
+        return writeByte(opcode, buffer, p);
     }
 
     private static final int MAX_ADDRESS_ONLY_DELTA = (0xff - LN_OPCODE_BASE) / LN_LINE_RANGE;
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLocSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLocSectionImpl.java
index bdc82370dbc8..21c56d9b53fa 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLocSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLocSectionImpl.java
@@ -38,10 +38,9 @@
 import com.oracle.objectfile.LayoutDecisionMap;
 import com.oracle.objectfile.ObjectFile;
 import com.oracle.objectfile.debugentry.ClassEntry;
+import com.oracle.objectfile.debugentry.LocalEntry;
+import com.oracle.objectfile.debugentry.LocalValueEntry;
 import com.oracle.objectfile.debugentry.range.Range;
-import com.oracle.objectfile.debugentry.range.SubRange;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalValueInfo;
 import com.oracle.objectfile.elf.ELFMachine;
 import com.oracle.objectfile.elf.ELFObjectFile;
 import com.oracle.objectfile.elf.dwarf.constants.DwarfExpressionOpcode;
@@ -131,7 +130,7 @@ private int generateContent(DebugContext context, byte[] buffer) {
         // reason for doing it in class entry order is to because it mirrors the
         // order in which entries appear in the info section. That stops objdump
         // posting spurious messages about overlaps and holes in the var ranges.
-        instanceClassStream().filter(ClassEntry::hasCompiledEntries).forEachOrdered(classEntry -> {
+        instanceClassStream().filter(ClassEntry::hasCompiledMethods).forEachOrdered(classEntry -> {
             List<LocationListEntry> locationListEntries = getLocationListEntries(classEntry);
             if (locationListEntries.isEmpty()) {
                 // no need to emit empty location list
@@ -177,14 +176,14 @@ private int writeLocationListsHeader(int offsetEntries, byte[] buffer, int p) {
         return writeInt(offsetEntries, buffer, pos);
     }
 
-    private record LocationListEntry(Range range, int base, DebugLocalInfo local, List<SubRange> rangeList) {
+    private record LocationListEntry(Range range, long base, LocalEntry local, List<Range> rangeList) {
     }
 
     private static List<LocationListEntry> getLocationListEntries(ClassEntry classEntry) {
         List<LocationListEntry> locationListEntries = new ArrayList<>();
 
-        classEntry.compiledEntries().forEachOrdered(compiledEntry -> {
-            Range primary = compiledEntry.getPrimary();
+        classEntry.compiledMethods().forEach(compiledEntry -> {
+            Range primary = compiledEntry.primary();
             /*
              * Note that offsets are written relative to the primary range base. This requires
              * writing a base address entry before each of the location list ranges. It is possible
@@ -194,14 +193,14 @@ private static List<LocationListEntry> getLocationListEntries(ClassEntry classEn
              * code addresses e.g. to set a breakpoint, leading to a very slow response for the
              * user.
              */
-            int base = primary.getLo();
+            long base = primary.getLo();
             // location list entries for primary range
             locationListEntries.addAll(getRangeLocationListEntries(primary, base));
             // location list entries for inlined calls
             if (!primary.isLeaf()) {
-                Iterator<SubRange> iterator = compiledEntry.topDownRangeIterator();
+                Iterator<Range> iterator = compiledEntry.topDownRangeStream().iterator();
                 while (iterator.hasNext()) {
-                    SubRange subrange = iterator.next();
+                    Range subrange = iterator.next();
                     if (subrange.isLeaf()) {
                         continue;
                     }
@@ -212,10 +211,10 @@ private static List<LocationListEntry> getLocationListEntries(ClassEntry classEn
         return locationListEntries;
     }
 
-    private static List<LocationListEntry> getRangeLocationListEntries(Range range, int base) {
+    private static List<LocationListEntry> getRangeLocationListEntries(Range range, long base) {
         List<LocationListEntry> locationListEntries = new ArrayList<>();
 
-        for (Map.Entry<DebugLocalInfo, List<SubRange>> entry : range.getVarRangeMap().entrySet()) {
+        for (Map.Entry<LocalEntry, List<Range>> entry : range.getVarRangeMap().entrySet()) {
             if (!entry.getValue().isEmpty()) {
                 locationListEntries.add(new LocationListEntry(range, base, entry.getKey(), entry.getValue()));
             }
@@ -224,7 +223,7 @@ private static List<LocationListEntry> getRangeLocationListEntries(Range range,
         return locationListEntries;
     }
 
-    private int writeVarLocations(DebugContext context, DebugLocalInfo local, int base, List<SubRange> rangeList, byte[] buffer, int p) {
+    private int writeVarLocations(DebugContext context, LocalEntry local, long base, List<Range> rangeList, byte[] buffer, int p) {
         assert !rangeList.isEmpty();
         int pos = p;
         // collect ranges and values, merging adjacent ranges that have equal value
@@ -236,9 +235,9 @@ private int writeVarLocations(DebugContext context, DebugLocalInfo local, int ba
         pos = writeAttrAddress(base, buffer, pos);
         // write ranges as offsets from base
         for (LocalValueExtent extent : extents) {
-            DebugLocalValueInfo value = extent.value;
+            LocalValueEntry value = extent.value;
             assert (value != null);
-            log(context, "  [0x%08x]     local  %s:%s [0x%x, 0x%x] = %s", pos, value.name(), value.typeName(), extent.getLo(), extent.getHi(), formatValue(value));
+            log(context, "  [0x%08x]     local  %s:%s [0x%x, 0x%x] = %s", pos, value.name(), value.type().getTypeName(), extent.getLo(), extent.getHi(), formatValue(value));
             pos = writeLocationListEntry(DwarfLocationListEntry.DW_LLE_offset_pair, buffer, pos);
             pos = writeULEB(extent.getLo() - base, buffer, pos);
             pos = writeULEB(extent.getHi() - base, buffer, pos);
@@ -246,17 +245,17 @@ private int writeVarLocations(DebugContext context, DebugLocalInfo local, int ba
                 case REGISTER:
                     pos = writeRegisterLocation(context, value.regIndex(), buffer, pos);
                     break;
-                case STACKSLOT:
+                case STACK:
                     pos = writeStackLocation(context, value.stackSlot(), buffer, pos);
                     break;
                 case CONSTANT:
-                    JavaConstant constant = value.constantValue();
+                    JavaConstant constant = value.constant();
                     if (constant instanceof PrimitiveConstant) {
-                        pos = writePrimitiveConstantLocation(context, value.constantValue(), buffer, pos);
+                        pos = writePrimitiveConstantLocation(context, value.constant(), buffer, pos);
                     } else if (constant.isNull()) {
-                        pos = writeNullConstantLocation(context, value.constantValue(), buffer, pos);
+                        pos = writeNullConstantLocation(context, value.constant(), buffer, pos);
                     } else {
-                        pos = writeObjectConstantLocation(context, value.constantValue(), value.heapOffset(), buffer, pos);
+                        pos = writeObjectConstantLocation(context, value.constant(), value.heapOffset(), buffer, pos);
                     }
                     break;
                 default:
@@ -378,16 +377,16 @@ private int writeObjectConstantLocation(DebugContext context, JavaConstant const
     static class LocalValueExtent {
         long lo;
         long hi;
-        DebugLocalValueInfo value;
+        LocalValueEntry value;
 
-        LocalValueExtent(long lo, long hi, DebugLocalValueInfo value) {
+        LocalValueExtent(long lo, long hi, LocalValueEntry value) {
             this.lo = lo;
             this.hi = hi;
             this.value = value;
         }
 
         @SuppressWarnings("unused")
-        boolean shouldMerge(int otherLo, int otherHi, DebugLocalValueInfo otherValue) {
+        boolean shouldMerge(long otherLo, long otherHi, LocalValueEntry otherValue) {
             // ranges need to be contiguous to merge
             if (hi != otherLo) {
                 return false;
@@ -395,7 +394,7 @@ boolean shouldMerge(int otherLo, int otherHi, DebugLocalValueInfo otherValue) {
             return value.equals(otherValue);
         }
 
-        private LocalValueExtent maybeMerge(int otherLo, int otherHi, DebugLocalValueInfo otherValue) {
+        private LocalValueExtent maybeMerge(long otherLo, long otherHi, LocalValueEntry otherValue) {
             if (shouldMerge(otherLo, otherHi, otherValue)) {
                 // We can extend the current extent to cover the next one.
                 this.hi = otherHi;
@@ -414,14 +413,14 @@ public long getHi() {
             return hi;
         }
 
-        public DebugLocalValueInfo getValue() {
+        public LocalValueEntry getValue() {
             return value;
         }
 
-        public static List<LocalValueExtent> coalesce(DebugLocalInfo local, List<SubRange> rangeList) {
+        public static List<LocalValueExtent> coalesce(LocalEntry local, List<Range> rangeList) {
             List<LocalValueExtent> extents = new ArrayList<>();
             LocalValueExtent current = null;
-            for (SubRange range : rangeList) {
+            for (Range range : rangeList) {
                 if (current == null) {
                     current = new LocalValueExtent(range.getLo(), range.getHi(), range.lookupValue(local));
                     extents.add(current);
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfRangesSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfRangesSectionImpl.java
index e80f64692c85..2a7bba67ba40 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfRangesSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfRangesSectionImpl.java
@@ -61,7 +61,7 @@ public byte[] getOrDecideContent(Map<ObjectFile.Element, LayoutDecisionMap> alre
         LayoutDecisionMap decisionMap = alreadyDecided.get(textElement);
         if (decisionMap != null) {
             Object valueObj = decisionMap.getDecidedValue(LayoutDecision.Kind.VADDR);
-            if (valueObj != null && valueObj instanceof Number) {
+            if (valueObj instanceof Number) {
                 /*
                  * This may not be the final vaddr for the text segment but it will be close enough
                  * to make debug easier i.e. to within a 4k page or two.
@@ -119,7 +119,7 @@ private int writeRangeListsHeader(byte[] buffer, int p) {
     private int writeRangeLists(DebugContext context, byte[] buffer, int p) {
         Cursor entryCursor = new Cursor(p);
 
-        instanceClassStream().filter(ClassEntry::hasCompiledEntries).forEachOrdered(classEntry -> {
+        instanceClassStream().filter(ClassEntry::hasCompiledMethods).forEachOrdered(classEntry -> {
             int pos = entryCursor.get();
             setCodeRangesIndex(classEntry, pos);
             /* Write range list for a class */
@@ -131,18 +131,18 @@ private int writeRangeLists(DebugContext context, byte[] buffer, int p) {
     private int writeRangeList(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) {
         int pos = p;
         log(context, "  [0x%08x] ranges start for class %s", pos, classEntry.getTypeName());
-        long base = classEntry.compiledEntriesBase();
+        long base = classEntry.lowpc();
         log(context, "  [0x%08x] base 0x%x", pos, base);
         pos = writeRangeListEntry(DwarfRangeListEntry.DW_RLE_base_address, buffer, pos);
-        pos = writeRelocatableCodeOffset(base, buffer, pos);
+        pos = writeCodeOffset(base, buffer, pos);
 
         Cursor cursor = new Cursor(pos);
-        classEntry.compiledEntries().forEach(compiledMethodEntry -> {
+        classEntry.compiledMethods().forEach(compiledMethodEntry -> {
             cursor.set(writeRangeListEntry(DwarfRangeListEntry.DW_RLE_offset_pair, buffer, cursor.get()));
 
-            long loOffset = compiledMethodEntry.getPrimary().getLo() - base;
-            long hiOffset = compiledMethodEntry.getPrimary().getHi() - base;
-            log(context, "  [0x%08x] lo 0x%x (%s)", cursor.get(), loOffset, compiledMethodEntry.getPrimary().getFullMethodNameWithParams());
+            long loOffset = compiledMethodEntry.primary().getLo() - base;
+            long hiOffset = compiledMethodEntry.primary().getHi() - base;
+            log(context, "  [0x%08x] lo 0x%x (%s)", cursor.get(), loOffset, compiledMethodEntry.primary().getFullMethodNameWithParams());
             cursor.set(writeULEB(loOffset, buffer, cursor.get()));
             log(context, "  [0x%08x] hi 0x%x", cursor.get(), hiOffset);
             cursor.set(writeULEB(hiOffset, buffer, cursor.get()));
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java
index 04b550b7e9a8..f103fffe5f6e 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java
@@ -28,6 +28,7 @@
 
 import java.nio.ByteOrder;
 import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Stream;
@@ -40,16 +41,14 @@
 import com.oracle.objectfile.debugentry.ArrayTypeEntry;
 import com.oracle.objectfile.debugentry.ClassEntry;
 import com.oracle.objectfile.debugentry.CompiledMethodEntry;
-import com.oracle.objectfile.debugentry.DirEntry;
-import com.oracle.objectfile.debugentry.FileEntry;
 import com.oracle.objectfile.debugentry.HeaderTypeEntry;
+import com.oracle.objectfile.debugentry.LocalEntry;
+import com.oracle.objectfile.debugentry.LocalValueEntry;
 import com.oracle.objectfile.debugentry.MethodEntry;
 import com.oracle.objectfile.debugentry.PrimitiveTypeEntry;
 import com.oracle.objectfile.debugentry.StructureTypeEntry;
 import com.oracle.objectfile.debugentry.TypeEntry;
 import com.oracle.objectfile.debugentry.range.Range;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalValueInfo;
 import com.oracle.objectfile.elf.ELFMachine;
 import com.oracle.objectfile.elf.ELFObjectFile;
 import com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.AbbrevCode;
@@ -70,7 +69,7 @@
  */
 public abstract class DwarfSectionImpl extends BasicProgbitsSectionImpl {
     // auxiliary class used to track byte array positions
-    protected class Cursor {
+    protected static class Cursor {
         private int pos;
 
         public Cursor() {
@@ -104,6 +103,8 @@ public int get() {
     protected long debugAddress = 0;
     protected int debugBase = 0;
 
+    private final ArrayList<Byte> contentBytes = new ArrayList<>();
+
     /**
      * The name of this section.
      */
@@ -281,33 +282,45 @@ protected int putLong(long l, byte[] buffer, int p) {
         return pos;
     }
 
-    protected int putRelocatableCodeOffset(long l, byte[] buffer, int p) {
+    protected int putCodeOffset(long l, byte[] buffer, int p) {
         int pos = p;
-        /*
-         * Mark address so it is relocated relative to the start of the text segment.
-         */
-        markRelocationSite(pos, ObjectFile.RelocationKind.DIRECT_8, DwarfSectionName.TEXT_SECTION.value(), l);
-        pos = writeLong(0, buffer, pos);
+        if (dwarfSections.isRuntimeCompilation()) {
+            pos = writeLong(l, buffer, p);
+        } else {
+            /*
+             * Mark address so it is relocated relative to the start of the text segment.
+             */
+            markRelocationSite(pos, ObjectFile.RelocationKind.DIRECT_8, DwarfSectionName.TEXT_SECTION.value(), l);
+            pos = writeLong(0, buffer, pos);
+        }
         return pos;
     }
 
-    protected int putRelocatableHeapOffset(long l, byte[] buffer, int p) {
+    protected int putHeapOffset(long l, byte[] buffer, int p) {
         int pos = p;
-        /*
-         * Mark address so it is relocated relative to the start of the heap.
-         */
-        markRelocationSite(pos, ObjectFile.RelocationKind.DIRECT_8, DwarfDebugInfo.HEAP_BEGIN_NAME, l);
-        pos = writeLong(0, buffer, pos);
+        if (dwarfSections.isRuntimeCompilation()) {
+            pos = writeLong(l, buffer, pos);
+        } else {
+            /*
+             * Mark address so it is relocated relative to the start of the heap.
+             */
+            markRelocationSite(pos, ObjectFile.RelocationKind.DIRECT_8, DwarfDebugInfo.HEAP_BEGIN_NAME, l);
+            pos = writeLong(0, buffer, pos);
+        }
         return pos;
     }
 
-    protected int putRelocatableDwarfSectionOffset(int offset, byte[] buffer, String referencedSectionName, int p) {
+    protected int putDwarfSectionOffset(int offset, byte[] buffer, String referencedSectionName, int p) {
         int pos = p;
-        /*
-         * Mark address so it is relocated relative to the start of the desired section.
-         */
-        markRelocationSite(pos, ObjectFile.RelocationKind.DIRECT_4, referencedSectionName, offset);
-        pos = writeInt(0, buffer, pos);
+        if (dwarfSections.isRuntimeCompilation()) {
+            pos = writeInt(offset, buffer, pos);
+        } else {
+            /*
+             * Mark address so it is relocated relative to the start of the desired section.
+             */
+            markRelocationSite(pos, ObjectFile.RelocationKind.DIRECT_4, referencedSectionName, offset);
+            pos = writeInt(0, buffer, pos);
+        }
         return pos;
     }
 
@@ -402,17 +415,17 @@ protected int writeLong(long l, byte[] buffer, int p) {
         }
     }
 
-    protected int writeRelocatableCodeOffset(long l, byte[] buffer, int p) {
+    protected int writeCodeOffset(long l, byte[] buffer, int p) {
         if (buffer != null) {
-            return putRelocatableCodeOffset(l, buffer, p);
+            return putCodeOffset(l, buffer, p);
         } else {
             return p + 8;
         }
     }
 
-    protected int writeRelocatableHeapOffset(long l, byte[] buffer, int p) {
+    protected int writeHeapOffset(long l, byte[] buffer, int p) {
         if (buffer != null) {
-            return putRelocatableHeapOffset(l, buffer, p);
+            return putHeapOffset(l, buffer, p);
         } else {
             return p + 8;
         }
@@ -522,7 +535,7 @@ protected int writeFlag(DwarfFlag flag, byte[] buffer, int pos) {
     }
 
     protected int writeAttrAddress(long address, byte[] buffer, int pos) {
-        return writeRelocatableCodeOffset(address, buffer, pos);
+        return writeCodeOffset(address, buffer, pos);
     }
 
     @SuppressWarnings("unused")
@@ -559,9 +572,8 @@ protected int writeAbbrevSectionOffset(int offset, byte[] buffer, int pos) {
     }
 
     protected int writeStrSectionOffset(String value, byte[] buffer, int p) {
-        int pos = p;
         int idx = debugStringIndex(value);
-        return writeStrSectionOffset(idx, buffer, pos);
+        return writeStrSectionOffset(idx, buffer, p);
     }
 
     private int writeStrSectionOffset(int offset, byte[] buffer, int pos) {
@@ -576,7 +588,7 @@ protected int writeDwarfSectionOffset(int offset, byte[] buffer, DwarfSectionNam
         // offsets to abbrev section DIEs need a relocation
         // the linker uses this to update the offset when info sections are merged
         if (buffer != null) {
-            return putRelocatableDwarfSectionOffset(offset, buffer, referencedSectionName.value(), pos);
+            return putDwarfSectionOffset(offset, buffer, referencedSectionName.value(), pos);
         } else {
             return pos + 4;
         }
@@ -639,7 +651,7 @@ protected int writeHeapLocation(long offset, byte[] buffer, int p) {
         if (dwarfSections.useHeapBase()) {
             return writeHeapLocationBaseRelative(offset, buffer, p);
         } else {
-            return writeHeapLocationRelocatable(offset, buffer, p);
+            return writeHeapLocationOffset(offset, buffer, p);
         }
     }
 
@@ -650,25 +662,20 @@ private int writeHeapLocationBaseRelative(long offset, byte[] buffer, int p) {
         return writeSLEB(offset, buffer, pos);
     }
 
-    private int writeHeapLocationRelocatable(long offset, byte[] buffer, int p) {
+    private int writeHeapLocationOffset(long offset, byte[] buffer, int p) {
         int pos = p;
         /* Write a relocatable address relative to the heap section start. */
         pos = writeExprOpcode(DwarfExpressionOpcode.DW_OP_addr, buffer, pos);
-        return writeRelocatableHeapOffset(offset, buffer, pos);
+        return writeHeapOffset(offset, buffer, pos);
     }
 
-    protected static String formatValue(DebugLocalValueInfo value) {
-        switch (value.localKind()) {
-            case REGISTER:
-                return "REG:" + value.regIndex();
-            case STACKSLOT:
-                return "STACK:" + value.stackSlot();
-            case CONSTANT:
-                return "CONST:" + value.constantValue() + "[" + Long.toHexString(value.heapOffset()) + "]";
-            case UNDEFINED:
-            default:
-                return "-";
-        }
+    protected static String formatValue(LocalValueEntry value) {
+        return switch (value.localKind()) {
+            case REGISTER -> "REG:" + value.regIndex();
+            case STACK -> "STACK:" + value.stackSlot();
+            case CONSTANT -> "CONST:" + value.constant() + "[" + Long.toHexString(value.heapOffset()) + "]";
+            default -> "-";
+        };
     }
 
     /**
@@ -763,7 +770,7 @@ protected Stream<TypeEntry> typeStream() {
      * @return a stream of all primitive types notified via the DebugTypeInfo API.
      */
     protected Stream<PrimitiveTypeEntry> primitiveTypeStream() {
-        return typeStream().filter(TypeEntry::isPrimitive).map(entry -> ((PrimitiveTypeEntry) entry));
+        return dwarfSections.getPrimitiveTypes().stream();
     }
 
     /**
@@ -772,7 +779,7 @@ protected Stream<PrimitiveTypeEntry> primitiveTypeStream() {
      * @return a stream of all array types notified via the DebugTypeInfo API.
      */
     protected Stream<ArrayTypeEntry> arrayTypeStream() {
-        return typeStream().filter(TypeEntry::isArray).map(entry -> ((ArrayTypeEntry) entry));
+        return dwarfSections.getArrayTypes().stream();
     }
 
     /**
@@ -803,6 +810,10 @@ protected Stream<ClassEntry> instanceClassStream() {
         return dwarfSections.getInstanceClasses().stream();
     }
 
+    protected Stream<ClassEntry> instanceClassWithCompilationStream() {
+        return dwarfSections.getInstanceClassesWithCompilation().stream();
+    }
+
     /**
      * Retrieve a stream of all compiled methods notified via the DebugTypeInfo API.
      *
@@ -812,26 +823,6 @@ protected Stream<CompiledMethodEntry> compiledMethodsStream() {
         return dwarfSections.getCompiledMethods().stream();
     }
 
-    protected int compiledMethodsCount() {
-        return dwarfSections.getCompiledMethods().size();
-    }
-
-    protected Stream<FileEntry> fileStream() {
-        return dwarfSections.getFiles().stream();
-    }
-
-    protected int fileCount() {
-        return dwarfSections.getFiles().size();
-    }
-
-    protected Stream<DirEntry> dirStream() {
-        return dwarfSections.getDirs().stream();
-    }
-
-    protected int dirCount() {
-        return dwarfSections.getDirs().size();
-    }
-
     /**
      * Retrieve an iterable for all instance classes, including interfaces and enums, notified via
      * the DebugTypeInfo API.
@@ -854,7 +845,7 @@ protected String uniqueDebugString(String str) {
     }
 
     protected TypeEntry lookupType(ResolvedJavaType type) {
-        return dwarfSections.lookupTypeEntry(type);
+        return null; // dwarfSections.lookupTypeEntry(type, null, null);
     }
 
     protected ClassEntry lookupObjectClass() {
@@ -955,7 +946,7 @@ protected int getAbstractInlineMethodIndex(ClassEntry classEntry, MethodEntry me
      * @param localInfo the local or param whose index is to be recorded.
      * @param index the info section offset to be recorded.
      */
-    protected void setMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, DebugLocalInfo localInfo, int index) {
+    protected void setMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, LocalEntry localInfo, int index) {
         dwarfSections.setMethodLocalIndex(classEntry, methodEntry, localInfo, index);
     }
 
@@ -968,7 +959,7 @@ protected void setMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntr
      * @param localInfo the local or param whose index is to be retrieved.
      * @return the associated info section offset.
      */
-    protected int getMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, DebugLocalInfo localInfo) {
+    protected int getMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, LocalEntry localInfo) {
         if (!contentByteArrayCreated()) {
             return 0;
         }
@@ -984,7 +975,7 @@ protected int getMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry
      * @param localInfo the local or param whose index is to be recorded.
      * @param index the info section offset index to be recorded.
      */
-    protected void setRangeLocalIndex(Range range, DebugLocalInfo localInfo, int index) {
+    protected void setRangeLocalIndex(Range range, LocalEntry localInfo, int index) {
         dwarfSections.setRangeLocalIndex(range, localInfo, index);
     }
 
@@ -997,7 +988,7 @@ protected void setRangeLocalIndex(Range range, DebugLocalInfo localInfo, int ind
      * @param localInfo the local or param whose index is to be retrieved.
      * @return the associated info section offset.
      */
-    protected int getRangeLocalIndex(Range range, DebugLocalInfo localInfo) {
+    protected int getRangeLocalIndex(Range range, LocalEntry localInfo) {
         return dwarfSections.getRangeLocalIndex(range, localInfo);
     }
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfStrSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfStrSectionImpl.java
index 34cddd22e18f..5ddf7f8dc735 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfStrSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfStrSectionImpl.java
@@ -45,11 +45,9 @@ public void createContent() {
 
         int pos = 0;
         for (StringEntry stringEntry : dwarfSections.getStringTable()) {
-            if (stringEntry.isAddToStrSection()) {
-                stringEntry.setOffset(pos);
-                String string = stringEntry.getString();
-                pos += countUTF8Bytes(string) + 1;
-            }
+            stringEntry.setOffset(pos);
+            String string = stringEntry.getString();
+            pos += countUTF8Bytes(string) + 1;
         }
         byte[] buffer = new byte[pos];
         super.setContent(buffer);
@@ -67,12 +65,10 @@ public void writeContent(DebugContext context) {
 
         verboseLog(context, " [0x%08x] DEBUG_STR", pos);
         for (StringEntry stringEntry : dwarfSections.getStringTable()) {
-            if (stringEntry.isAddToStrSection()) {
-                assert stringEntry.getOffset() == pos;
-                String string = stringEntry.getString();
-                pos = writeUTF8StringBytes(string, buffer, pos);
-                verboseLog(context, " [0x%08x] string = %s", pos, string);
-            }
+            assert stringEntry.getOffset() == pos;
+            String string = stringEntry.getString();
+            pos = writeUTF8StringBytes(string, buffer, pos);
+            verboseLog(context, " [0x%08x] string = %s", pos, string);
         }
         assert pos == size;
     }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVDebugInfo.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVDebugInfo.java
index 27e0efc5cf04..d7a76f6f6902 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVDebugInfo.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVDebugInfo.java
@@ -27,6 +27,7 @@
 package com.oracle.objectfile.pecoff.cv;
 
 import com.oracle.objectfile.debugentry.DebugInfoBase;
+import com.oracle.objectfile.debuginfo.DebugInfoProvider;
 import com.oracle.objectfile.pecoff.PECoffMachine;
 import jdk.graal.compiler.debug.DebugContext;
 import jdk.graal.compiler.debug.GraalError;
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVLineRecordBuilder.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVLineRecordBuilder.java
index 4b766bca5b50..47be6e836924 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVLineRecordBuilder.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVLineRecordBuilder.java
@@ -29,7 +29,6 @@
 import com.oracle.objectfile.debugentry.FileEntry;
 import com.oracle.objectfile.debugentry.CompiledMethodEntry;
 import com.oracle.objectfile.debugentry.range.Range;
-import com.oracle.objectfile.debugentry.range.SubRange;
 
 import java.util.Iterator;
 
@@ -60,16 +59,16 @@ public void debug(String format, Object... args) {
      */
     CVLineRecord build(CompiledMethodEntry entry) {
         this.compiledEntry = entry;
-        Range primaryRange = compiledEntry.getPrimary();
+        Range primaryRange = compiledEntry.primary();
 
         debug("DEBUG_S_LINES linerecord for 0x%05x file: %s:%d", primaryRange.getLo(), primaryRange.getFileName(), primaryRange.getLine());
         this.lineRecord = new CVLineRecord(cvDebugInfo, primaryRange.getSymbolName());
         debug("CVLineRecord.computeContents: processing primary range %s", primaryRange);
 
         processRange(primaryRange);
-        Iterator<SubRange> iterator = compiledEntry.leafRangeIterator();
+        Iterator<Range> iterator = compiledEntry.leafRangeStream().iterator();
         while (iterator.hasNext()) {
-            SubRange subRange = iterator.next();
+            Range subRange = iterator.next();
             debug("CVLineRecord.computeContents: processing range %s", subRange);
             processRange(subRange);
         }
@@ -102,9 +101,9 @@ private void processRange(Range range) {
         }
 
         /* Add line record. */
-        int lineLoAddr = (int)(range.getLo() - compiledEntry.getPrimary().getLo());
+        int lineLoAddr = (int)(range.getLo() - compiledEntry.primary().getLo());
         int line = Math.max(range.getLine(), 1);
-        debug("  processRange:   addNewLine: 0x%05x-0x%05x %s", lineLoAddr, range.getHi() - compiledEntry.getPrimary().getLo(), line);
+        debug("  processRange:   addNewLine: 0x%05x-0x%05x %s", lineLoAddr, range.getHi() - compiledEntry.primary().getLo(), line);
         lineRecord.addNewLine(lineLoAddr, line);
     }
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVNames.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVNames.java
index a1666dc08ce3..1c71b03e5d02 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVNames.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVNames.java
@@ -41,10 +41,10 @@ static String typeNameToCodeViewName(TypeEntry typeEntry) {
     }
 
     static String methodNameToCodeViewName(MethodEntry memberEntry) {
-        return typeNameToCodeViewName(memberEntry.ownerType()) + "::" + memberEntry.methodName();
+        return typeNameToCodeViewName(memberEntry.getOwnerType()) + "::" + memberEntry.getMethodName();
     }
 
     static String fieldNameToCodeViewName(FieldEntry memberEntry) {
-        return typeNameToCodeViewName(memberEntry.ownerType()) + "::" + memberEntry.fieldName();
+        return typeNameToCodeViewName(memberEntry.getOwnerType()) + "::" + memberEntry.fieldName();
     }
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubrecord.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubrecord.java
index 29d819c743f1..a1df75b6763f 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubrecord.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubrecord.java
@@ -90,7 +90,7 @@ private static String findObjectName(CVDebugInfo cvDebugInfo) {
             String fn = null;
             for (ClassEntry classEntry : cvDebugInfo.getInstanceClasses()) {
                 if (classEntry.getFileName() != null) {
-                    fn = classEntry.getFileEntry().getFileName();
+                    fn = classEntry.getFileEntry().fileName();
                     if (fn.endsWith(".java")) {
                         fn = fn.substring(0, fn.lastIndexOf(".java")) + ".obj";
                     }
@@ -216,7 +216,7 @@ private static String findFirstFile(CVDebugInfo cvDebugInfo) {
             String fn = null;
             for (ClassEntry classEntry : cvDebugInfo.getInstanceClasses()) {
                 if (classEntry.getFileName() != null) {
-                    fn = classEntry.getFileEntry().getFileName();
+                    fn = classEntry.getFileEntry().fileName();
                     break;
                 }
             }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubsectionBuilder.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubsectionBuilder.java
index 3eb4d220c45a..a1af78d28e61 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubsectionBuilder.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubsectionBuilder.java
@@ -85,13 +85,13 @@ void build() {
     private void buildClass(ClassEntry classEntry) {
 
         /* Define all the functions in this class all functions defined in this class. */
-        classEntry.compiledEntries().forEach(this::buildFunction);
+        classEntry.compiledMethods().forEach(this::buildFunction);
 
         /* Define the class itself. */
         addTypeRecords(classEntry);
 
         /* Add manifested static fields as S_GDATA32 records. */
-        classEntry.fields().filter(CVSymbolSubsectionBuilder::isManifestedStaticField).forEach(f -> {
+        classEntry.getFields().stream().filter(CVSymbolSubsectionBuilder::isManifestedStaticField).forEach(f -> {
             int typeIndex = cvDebugInfo.getCVTypeSection().getIndexForPointer(f.getValueType());
             String displayName = CVNames.fieldNameToCodeViewName(f);
             if (cvDebugInfo.useHeapBase()) {
@@ -118,7 +118,7 @@ private static boolean isManifestedStaticField(FieldEntry fieldEntry) {
      * @param compiledEntry compiled method for this function
      */
     private void buildFunction(CompiledMethodEntry compiledEntry) {
-        final Range primaryRange = compiledEntry.getPrimary();
+        final Range primaryRange = compiledEntry.primary();
 
         /* The name as it will appear in the debugger. */
         final String debuggerName = CVNames.methodNameToCodeViewName(primaryRange.getMethodEntry());
@@ -139,7 +139,7 @@ private void buildFunction(CompiledMethodEntry compiledEntry) {
         int localBP = 1 << 14; /* Local base pointer = SP (0=none, 1=sp, 2=bp 3=r13). */
         int paramBP = 1 << 16; /* Param base pointer = SP. */
         int frameFlags = asynceh + localBP + paramBP; /* NB: LLVM uses 0x14000. */
-        addSymbolRecord(new CVSymbolSubrecord.CVSymbolFrameProcRecord(cvDebugInfo, compiledEntry.getFrameSize(), frameFlags));
+        addSymbolRecord(new CVSymbolSubrecord.CVSymbolFrameProcRecord(cvDebugInfo, compiledEntry.frameSize(), frameFlags));
 
         /* TODO: add parameter definitions (types have been added already). */
         /* TODO: add local variables, and their types. */
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVTypeSectionBuilder.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVTypeSectionBuilder.java
index 6ebd0088c30b..8409679b6ac8 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVTypeSectionBuilder.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVTypeSectionBuilder.java
@@ -112,31 +112,20 @@ CVTypeRecord buildType(TypeEntry typeEntry) {
          * If we've never seen the class or only defined it as a forward reference, define it now.
          */
         if (typeRecord != null && typeRecord.type == LF_CLASS && !((CVTypeRecord.CVClassRecord) typeRecord).isForwardRef()) {
-            log("buildType() type %s(%s) is known %s", typeEntry.getTypeName(), typeEntry.typeKind().name(), typeRecord);
+            log("buildType() type %s(%s) is known %s", typeEntry.getTypeName(), "typeEntry.typeKind().name()", typeRecord);
         } else {
-            log("buildType() %s %s size=%d - begin", typeEntry.typeKind().name(), typeEntry.getTypeName(), typeEntry.getSize());
-            switch (typeEntry.typeKind()) {
-                case PRIMITIVE: {
-                    typeRecord = types.getExistingType(typeEntry);
-                    break;
-                }
-                case ARRAY:
-                case ENUM:
-                case INSTANCE:
-                case INTERFACE:
-                    // TODO continue treat foreign types as interfaces/classes but fix this later
-                case FOREIGN: {
-                    typeRecord = buildStructureTypeEntry((StructureTypeEntry) typeEntry);
-                    break;
-                }
-                case HEADER: {
-                    /*
-                     * The bits at the beginning of an Object: contains pointer to DynamicHub.
-                     */
-                    assert typeEntry.getTypeName().equals(OBJ_HEADER_NAME);
-                    typeRecord = buildStructureTypeEntry((HeaderTypeEntry) typeEntry);
-                    break;
-                }
+            log("buildType() %s %s size=%d - begin", "typeEntry.typeKind().name()", typeEntry.getTypeName(), typeEntry.getSize());
+            if (typeEntry.isPrimitive()) {
+                typeRecord = types.getExistingType(typeEntry);
+            } else if (typeEntry.isHeader()) {
+                /*
+                 * The bits at the beginning of an Object: contains pointer to DynamicHub.
+                 */
+                assert typeEntry.getTypeName().equals(OBJ_HEADER_NAME);
+                typeRecord = buildStructureTypeEntry((HeaderTypeEntry) typeEntry);
+            } else if (typeEntry.isClass() || typeEntry.isArray()) {
+                // TODO continue treat foreign types as interfaces/classes but fix this later
+                typeRecord = buildStructureTypeEntry((StructureTypeEntry) typeEntry);
             }
         }
         assert typeRecord != null;
@@ -151,7 +140,7 @@ CVTypeRecord buildType(TypeEntry typeEntry) {
      * @return type record for this function (may return existing matching record)
      */
     CVTypeRecord buildFunction(CompiledMethodEntry entry) {
-        return buildMemberFunction(entry.getClassEntry(), entry.getPrimary().getMethodEntry());
+        return buildMemberFunction(entry.classEntry(), entry.primary().getMethodEntry());
     }
 
     static class FieldListBuilder {
@@ -217,7 +206,7 @@ CVTypeRecord.CVFieldListRecord buildFieldListRecords(CVTypeSectionBuilder builde
 
     private CVTypeRecord buildStructureTypeEntry(final StructureTypeEntry typeEntry) {
 
-        log("buildStructureTypeEntry size=%d kind=%s %s", typeEntry.getSize(), typeEntry.typeKind().name(), typeEntry.getTypeName());
+        log("buildStructureTypeEntry size=%d kind=%s %s", typeEntry.getSize(), "typeEntry.typeKind().name()", typeEntry.getTypeName());
 
         ClassEntry superType = typeEntry.isClass() ? ((ClassEntry) typeEntry).getSuperClass() : null;
         int superTypeIndex = superType != null ? types.getIndexForForwardRef(superType) : 0;
@@ -247,7 +236,7 @@ private CVTypeRecord buildStructureTypeEntry(final StructureTypeEntry typeEntry)
         }
 
         /* Only define manifested fields. */
-        typeEntry.fields().filter(CVTypeSectionBuilder::isManifestedField).forEach(f -> {
+        typeEntry.getFields().stream().filter(CVTypeSectionBuilder::isManifestedField).forEach(f -> {
             log("field %s attr=(%s) offset=%d size=%d valuetype=%s", f.fieldName(), f.getModifiersString(), f.getOffset(), f.getSize(), f.getValueType().getTypeName());
             CVTypeRecord.FieldRecord fieldRecord = buildField(f);
             log("field %s", fieldRecord);
@@ -286,10 +275,10 @@ private CVTypeRecord buildStructureTypeEntry(final StructureTypeEntry typeEntry)
             HashSet<String> overloaded = new HashSet<>(methods.size());
             HashSet<String> allFunctions = new HashSet<>(methods.size());
             methods.forEach(m -> {
-                if (allFunctions.contains(m.methodName())) {
-                    overloaded.add(m.methodName());
+                if (allFunctions.contains(m.getMethodName())) {
+                    overloaded.add(m.getMethodName());
                 } else {
-                    allFunctions.add(m.methodName());
+                    allFunctions.add(m.getMethodName());
                 }
             });
 
@@ -300,12 +289,12 @@ private CVTypeRecord buildStructureTypeEntry(final StructureTypeEntry typeEntry)
                 CVTypeRecord.CVTypeMethodListRecord mlist = new CVTypeRecord.CVTypeMethodListRecord();
 
                 /* LF_MFUNCTION records */
-                methods.stream().filter(methodEntry -> methodEntry.methodName().equals(mname)).forEach(m -> {
-                    log("overloaded method %s attr=(%s) valuetype=%s", m.methodName(), m.getModifiersString(), m.getValueType().getTypeName());
+                methods.stream().filter(methodEntry -> methodEntry.getMethodName().equals(mname)).forEach(m -> {
+                    log("overloaded method %s attr=(%s) valuetype=%s", m.getMethodName(), m.getModifiersString(), m.getValueType().getTypeName());
                     CVTypeRecord.CVTypeMFunctionRecord mFunctionRecord = buildMemberFunction((ClassEntry) typeEntry, m);
                     short attr = modifiersToAttr(m);
                     log("    overloaded method %s", mFunctionRecord);
-                    mlist.add(attr, mFunctionRecord.getSequenceNumber(), m.getVtableOffset(), m.methodName());
+                    mlist.add(attr, mFunctionRecord.getSequenceNumber(), m.getVtableOffset(), m.getMethodName());
                 });
 
                 CVTypeRecord.CVTypeMethodListRecord nmlist = addTypeRecord(mlist);
@@ -315,8 +304,8 @@ private CVTypeRecord buildStructureTypeEntry(final StructureTypeEntry typeEntry)
                 fieldListBuilder.addField(methodRecord);
             });
 
-            methods.stream().filter(methodEntry -> !overloaded.contains(methodEntry.methodName())).forEach(m -> {
-                log("`unique method %s %s(...)", m.methodName(), m.getModifiersString(), m.getValueType().getTypeName(), m.methodName());
+            methods.stream().filter(methodEntry -> !overloaded.contains(methodEntry.getMethodName())).forEach(m -> {
+                log("`unique method %s %s(...)", m.getMethodName(), m.getModifiersString(), m.getValueType().getTypeName(), m.getMethodName());
                 CVTypeRecord.CVOneMethodRecord method = buildMethod((ClassEntry) typeEntry, m);
                 log("    unique method %s", method);
                 fieldListBuilder.addField(method);
@@ -395,7 +384,7 @@ private static short modifiersToAttr(FieldEntry member) {
     private CVTypeRecord.CVOneMethodRecord buildMethod(ClassEntry classEntry, MethodEntry methodEntry) {
         CVTypeRecord.CVTypeMFunctionRecord funcRecord = buildMemberFunction(classEntry, methodEntry);
         short attr = modifiersToAttr(methodEntry);
-        return new CVTypeRecord.CVOneMethodRecord(attr, funcRecord.getSequenceNumber(), methodEntry.getVtableOffset(), methodEntry.methodName());
+        return new CVTypeRecord.CVOneMethodRecord(attr, funcRecord.getSequenceNumber(), methodEntry.getVtableOffset(), methodEntry.getMethodName());
     }
 
     private static short accessToAttr(MemberEntry member) {
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/RuntimeDebugInfoBase.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/RuntimeDebugInfoBase.java
deleted file mode 100644
index cad772ad679d..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/RuntimeDebugInfoBase.java
+++ /dev/null
@@ -1,494 +0,0 @@
-/*
- * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime;
-
-import java.nio.ByteOrder;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Consumer;
-import java.util.stream.Stream;
-
-import com.oracle.objectfile.debugentry.ArrayTypeEntry;
-import com.oracle.objectfile.debugentry.ClassEntry;
-import com.oracle.objectfile.debugentry.CompiledMethodEntry;
-import com.oracle.objectfile.debugentry.DebugInfoBase;
-import com.oracle.objectfile.debugentry.DirEntry;
-import com.oracle.objectfile.debugentry.EnumClassEntry;
-import com.oracle.objectfile.debugentry.FieldEntry;
-import com.oracle.objectfile.debugentry.FileEntry;
-import com.oracle.objectfile.debugentry.ForeignTypeEntry;
-import com.oracle.objectfile.debugentry.HeaderTypeEntry;
-import com.oracle.objectfile.debugentry.InterfaceClassEntry;
-import com.oracle.objectfile.debugentry.LoaderEntry;
-import com.oracle.objectfile.debugentry.MethodEntry;
-import com.oracle.objectfile.debugentry.PrimitiveTypeEntry;
-import com.oracle.objectfile.debugentry.StringTable;
-import com.oracle.objectfile.debugentry.StructureTypeEntry;
-import com.oracle.objectfile.debugentry.TypeEntry;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider;
-import org.graalvm.collections.EconomicMap;
-import org.graalvm.collections.EconomicSet;
-
-import com.oracle.objectfile.debugentry.range.PrimaryRange;
-import com.oracle.objectfile.debugentry.range.Range;
-import com.oracle.objectfile.debugentry.range.SubRange;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugCodeInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFileInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalValueInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocationInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind;
-import com.oracle.objectfile.elf.dwarf.DwarfDebugInfo;
-
-import jdk.graal.compiler.debug.DebugContext;
-import jdk.vm.ci.meta.ResolvedJavaType;
-
-/**
- * An abstract class which indexes the information presented by the DebugInfoProvider in an
- * organization suitable for use by subclasses targeting a specific binary format.
- *
- * This class provides support for iterating over records detailing all the types and compiled
- * methods presented via the DebugInfoProvider interface. The obvious hierarchical traversal order
- * when generating debug info output is:
- *
- * 1) by top level compiled method (and associated primary Range) n.b. these are always presented to
- * the generator in order of ascending address
- *
- * 2) by inlined method (sub range) within top level method, also ordered by ascending address
- *
- * This traversal ensures that debug records are generated in increasing address order
- *
- * An alternative hierarchical traversal order is
- *
- * 1) by top level class (unique ResolvedJavaType id) n.b. types are not guaranteed to be presented
- * to the generator in increasing address order of their method code ranges. In particular many
- * classes do not have top-level compiled methods and may not even have inlined methods.
- *
- * 2) by top level compiled method (and associated primary Range) within a class, which are ordered
- * by ascending address
- *
- * 3) by inlined method (sub range) within top level method, also ordered by ascending address
- *
- * Since clients may need to generate records for classes with no compiled methods, the second
- * traversal order is often employed.
- *
- * n.b. methods of a given class do not always appear in a single continuous address range. The
- * compiler choose to interleave intervening code from other classes or data values in order to get
- * better cache locality. It may also choose to generate deoptimized variants of methods in a
- * separate range from normal, optimized compiled code. This out of (code addess) order sorting may
- * make it difficult to use a class by class traversal to generate debug info in separate per-class
- * units.
- */
-public abstract class RuntimeDebugInfoBase extends DebugInfoBase {
-    protected RuntimeDebugInfoProvider debugInfoProvider;
-
-    private String cuName;
-
-    @SuppressWarnings("this-escape")
-    public RuntimeDebugInfoBase(ByteOrder byteOrder) {
-        super(byteOrder);
-        this.byteOrder = byteOrder;
-        this.useHeapBase = true;
-        this.oopTagsCount = 0;
-        this.oopCompressShift = 0;
-        this.oopReferenceSize = 0;
-        this.pointerSize = 0;
-        this.oopAlignment = 0;
-        this.oopAlignShift = 0;
-        this.hubClassEntry = null;
-        this.compiledCodeMax = 0;
-        // create and index an empty dir with index 0.
-        ensureDirEntry(EMPTY_PATH);
-    }
-
-    /**
-     * Entry point allowing ELFObjectFile to pass on information about types, code and heap data.
-     *
-     * @param debugInfoProvider provider instance passed by ObjectFile client.
-     */
-    @SuppressWarnings("try")
-    public void installDebugInfo(RuntimeDebugInfoProvider debugInfoProvider) {
-        this.debugInfoProvider = debugInfoProvider;
-
-        /*
-         * Track whether we need to use a heap base register.
-         */
-        useHeapBase = debugInfoProvider.useHeapBase();
-
-        cuName = debugInfoProvider.getCompilationUnitName();
-
-        /*
-         * Save count of low order tag bits that may appear in references.
-         */
-        int oopTagsMask = debugInfoProvider.oopTagsMask();
-
-        /* Tag bits must be between 0 and 32 for us to emit as DW_OP_lit<n>. */
-        assert oopTagsMask >= 0 && oopTagsMask < 32;
-        /* Mask must be contiguous from bit 0. */
-        assert ((oopTagsMask + 1) & oopTagsMask) == 0;
-
-        oopTagsCount = Integer.bitCount(oopTagsMask);
-
-        /* Save amount we need to shift references by when loading from an object field. */
-        oopCompressShift = debugInfoProvider.oopCompressShift();
-
-        /* shift bit count must be either 0 or 3 */
-        assert (oopCompressShift == 0 || oopCompressShift == 3);
-
-        /* Save number of bytes in a reference field. */
-        oopReferenceSize = debugInfoProvider.oopReferenceSize();
-
-        /* Save pointer size of current target. */
-        pointerSize = debugInfoProvider.pointerSize();
-
-        /* Save alignment of a reference. */
-        oopAlignment = debugInfoProvider.oopAlignment();
-
-        /* Save alignment of a reference. */
-        oopAlignShift = Integer.bitCount(oopAlignment - 1);
-
-        /* Reference alignment must be 8 bytes. */
-        assert oopAlignment == 8;
-
-        /* retrieve limit for Java code address range */
-        compiledCodeMax = debugInfoProvider.compiledCodeMax();
-
-        /* Ensure we have a null string and cachePath in the string section. */
-        String uniqueNullString = stringTable.uniqueDebugString("");
-        if (debugInfoProvider.getCachePath() != null) {
-            cachePath = stringTable.uniqueDebugString(debugInfoProvider.getCachePath().toString());
-        } else {
-            cachePath = uniqueNullString; // fall back to null string
-        }
-
-        DebugCodeInfo debugCodeInfo = debugInfoProvider.codeInfoProvider();
-        debugCodeInfo.debugContext((debugContext) -> {
-            /*
-             * Primary file name and full method name need to be written to the debug_str section.
-             */
-            ResolvedJavaType ownerType = debugCodeInfo.ownerType();
-
-            DebugInfoProvider.DebugTypeInfo debugTypeInfo = debugInfoProvider.createDebugTypeInfo(ownerType);
-            String typeName = debugTypeInfo.typeName();
-            typeName = stringTable.uniqueDebugString(typeName);
-            DebugTypeKind typeKind = debugTypeInfo.typeKind();
-            int byteSize = debugTypeInfo.size();
-
-            if (debugContext.isLogEnabled(DebugContext.INFO_LEVEL)) {
-                debugContext.log(DebugContext.INFO_LEVEL, "Register %s type %s ", typeKind.toString(), typeName);
-            }
-            String fileName = debugTypeInfo.fileName();
-            Path filePath = debugTypeInfo.filePath();
-            TypeEntry typeEntry = addTypeEntry(ownerType, typeName, fileName, filePath, byteSize, typeKind);
-            typeEntry.addDebugInfo(this, debugTypeInfo, debugContext);
-
-            fileName = debugCodeInfo.fileName();
-            filePath = debugCodeInfo.filePath();
-            String methodName = debugCodeInfo.name();
-            long lo = debugCodeInfo.addressLo();
-            long hi = debugCodeInfo.addressHi();
-            int primaryLine = debugCodeInfo.line();
-
-            /* Search for a method defining this primary range. */
-            ClassEntry classEntry = lookupClassEntry(ownerType);
-            MethodEntry methodEntry = classEntry.ensureMethodEntryForDebugRangeInfo(debugCodeInfo, this, debugContext);
-            PrimaryRange primaryRange = Range.createPrimary(methodEntry, lo, hi, primaryLine);
-            if (debugContext.isLogEnabled(DebugContext.INFO_LEVEL)) {
-                debugContext.log(DebugContext.INFO_LEVEL, "PrimaryRange %s.%s %s %s:%d [0x%x, 0x%x]", ownerType.toJavaName(), methodName, filePath, fileName, primaryLine, lo, hi);
-            }
-            addPrimaryRange(primaryRange, debugCodeInfo, classEntry);
-            /*
-             * Record all subranges even if they have no line or file so we at least get a symbol
-             * for them and don't see a break in the address range.
-             */
-            EconomicMap<DebugLocationInfo, SubRange> subRangeIndex = EconomicMap.create();
-            debugCodeInfo.locationInfoProvider().forEach(debugLocationInfo -> addSubrange(debugLocationInfo, primaryRange, classEntry, subRangeIndex, debugContext));
-
-            collectFilesAndDirs(classEntry);
-
-            debugInfoProvider.recordActivity();
-        });
-
-        // populate a file and dir list and associated index for each class entry
-        // getInstanceClasses().forEach(DebugInfoBase::collectFilesAndDirs);
-    }
-
-    @Override
-    protected TypeEntry createTypeEntry(String typeName, String fileName, Path filePath, int size, DebugTypeKind typeKind) {
-        TypeEntry typeEntry = null;
-        switch (typeKind) {
-            case INSTANCE: {
-                FileEntry fileEntry = addFileEntry(fileName, filePath);
-                typeEntry = new ClassEntry(typeName, fileEntry, size);
-                if (typeEntry.getTypeName().equals(DwarfDebugInfo.HUB_TYPE_NAME)) {
-                    hubClassEntry = (ClassEntry) typeEntry;
-                }
-                break;
-            }
-            case INTERFACE: {
-                FileEntry fileEntry = addFileEntry(fileName, filePath);
-                typeEntry = new InterfaceClassEntry(typeName, fileEntry, size);
-                break;
-            }
-            case ENUM: {
-                FileEntry fileEntry = addFileEntry(fileName, filePath);
-                typeEntry = new EnumClassEntry(typeName, fileEntry, size);
-                break;
-            }
-            case PRIMITIVE:
-                assert fileName.length() == 0;
-                assert filePath == null;
-                typeEntry = new PrimitiveTypeEntry(typeName, size);
-                break;
-            case ARRAY:
-                assert fileName.length() == 0;
-                assert filePath == null;
-                typeEntry = new ArrayTypeEntry(typeName, size);
-                break;
-            case HEADER:
-                assert fileName.length() == 0;
-                assert filePath == null;
-                typeEntry = new HeaderTypeEntry(typeName, size);
-                break;
-            case FOREIGN: {
-                FileEntry fileEntry = addFileEntry(fileName, filePath);
-                typeEntry = new ForeignTypeEntry(typeName, fileEntry, size);
-                break;
-            }
-        }
-        return typeEntry;
-    }
-
-    @Override
-    protected TypeEntry addTypeEntry(ResolvedJavaType idType, String typeName, String fileName, Path filePath, int size, DebugTypeKind typeKind) {
-        TypeEntry typeEntry = (idType != null ? typesIndex.get(idType) : null);
-        if (typeEntry == null) {
-            typeEntry = createTypeEntry(typeName, fileName, filePath, size, typeKind);
-            types.add(typeEntry);
-            if (idType != null) {
-                typesIndex.put(idType, typeEntry);
-            }
-            // track object type and header struct
-            if (idType == null) {
-                headerType = (HeaderTypeEntry) typeEntry;
-            }
-            if (typeName.equals("java.lang.Object")) {
-                objectClass = (ClassEntry) typeEntry;
-            }
-            if (typeName.equals("void")) {
-                voidType = typeEntry;
-            }
-            if (typeEntry instanceof ClassEntry) {
-                indexInstanceClass(idType, (ClassEntry) typeEntry);
-            }
-        } else {
-            if (!(typeEntry.isClass())) {
-                assert ((ClassEntry) typeEntry).getFileName().equals(fileName);
-            }
-        }
-        return typeEntry;
-    }
-
-    @Override
-    public TypeEntry lookupTypeEntry(ResolvedJavaType type) {
-        TypeEntry typeEntry = typesIndex.get(type);
-        if (typeEntry == null) {
-            DebugInfoProvider.DebugTypeInfo debugTypeInfo = debugInfoProvider.createDebugTypeInfo(type);
-            debugTypeInfo.debugContext((debugContext) -> {
-                String typeName = debugTypeInfo.typeName();
-                typeName = stringTable.uniqueDebugString(typeName);
-                DebugTypeKind typeKind = debugTypeInfo.typeKind();
-                int byteSize = debugTypeInfo.size();
-
-                if (debugContext.isLogEnabled(DebugContext.INFO_LEVEL)) {
-                    debugContext.log(DebugContext.INFO_LEVEL, "Register %s type %s ", typeKind.toString(), typeName);
-                }
-                String fileName = debugTypeInfo.fileName();
-                Path filePath = debugTypeInfo.filePath();
-                TypeEntry newTypeEntry = addTypeEntry(type, typeName, fileName, filePath, byteSize, typeKind);
-                newTypeEntry.addDebugInfo(this, debugTypeInfo, debugContext);
-            });
-
-            typeEntry = typesIndex.get(type);
-        }
-
-        if (typeEntry == null) {
-            throw new RuntimeException("Type entry not found " + type.getName());
-        }
-        return typeEntry;
-    }
-
-    @Override
-    public ClassEntry lookupClassEntry(ResolvedJavaType type) {
-        // lookup key should advertise itself as a resolved instance class or interface
-        assert type.isInstanceClass() || type.isInterface();
-        // lookup target should already be included in the index
-        ClassEntry classEntry = instanceClassesIndex.get(type);
-        if (classEntry == null) {
-            DebugInfoProvider.DebugTypeInfo debugTypeInfo = debugInfoProvider.createDebugTypeInfo(type);
-            debugTypeInfo.debugContext((debugContext) -> {
-                String typeName = debugTypeInfo.typeName();
-                typeName = stringTable.uniqueDebugString(typeName);
-                DebugTypeKind typeKind = debugTypeInfo.typeKind();
-                int byteSize = debugTypeInfo.size();
-
-                if (debugContext.isLogEnabled(DebugContext.INFO_LEVEL)) {
-                    debugContext.log(DebugContext.INFO_LEVEL, "Register %s type %s ", typeKind.toString(), typeName);
-                }
-                String fileName = debugTypeInfo.fileName();
-                Path filePath = debugTypeInfo.filePath();
-                TypeEntry newTypeEntry = addTypeEntry(type, typeName, fileName, filePath, byteSize, typeKind);
-                newTypeEntry.addDebugInfo(this, debugTypeInfo, debugContext);
-            });
-
-            classEntry = instanceClassesIndex.get(type);
-        }
-
-        if (classEntry == null || !(classEntry.isClass())) {
-            throw new RuntimeException("Class entry not found " + type.getName());
-        }
-        // lookup target should also be indexed in the types index
-        assert typesIndex.get(type) != null;
-        return classEntry;
-    }
-
-    /**
-     * Recursively creates subranges based on DebugLocationInfo including, and appropriately
-     * linking, nested inline subranges.
-     *
-     * @param locationInfo
-     * @param primaryRange
-     * @param classEntry
-     * @param subRangeIndex
-     * @param debugContext
-     * @return the subrange for {@code locationInfo} linked with all its caller subranges up to the
-     *         primaryRange
-     */
-    @SuppressWarnings("try")
-    @Override
-    protected Range addSubrange(DebugLocationInfo locationInfo, PrimaryRange primaryRange, ClassEntry classEntry, EconomicMap<DebugLocationInfo, SubRange> subRangeIndex, DebugContext debugContext) {
-        /*
-         * We still insert subranges for the primary method but they don't actually count as inline.
-         * we only need a range so that subranges for inline code can refer to the top level line
-         * number.
-         */
-        DebugLocationInfo callerLocationInfo = locationInfo.getCaller();
-        boolean isTopLevel = callerLocationInfo == null;
-        assert (!isTopLevel || (locationInfo.name().equals(primaryRange.getMethodName()) &&
-                locationInfo.ownerType().toJavaName().equals(primaryRange.getClassName())));
-        Range caller = (isTopLevel ? primaryRange : subRangeIndex.get(callerLocationInfo));
-        // the frame tree is walked topdown so inline ranges should always have a caller range
-        assert caller != null;
-
-        final String fileName = locationInfo.fileName();
-        final Path filePath = locationInfo.filePath();
-        final String fullPath = (filePath == null ? "" : filePath.toString() + "/") + fileName;
-        final ResolvedJavaType ownerType = locationInfo.ownerType();
-        final String methodName = locationInfo.name();
-        final long loOff = locationInfo.addressLo();
-        final long hiOff = locationInfo.addressHi() - 1;
-        final long lo = primaryRange.getLo() + locationInfo.addressLo();
-        final long hi = primaryRange.getLo() + locationInfo.addressHi();
-        final int line = locationInfo.line();
-        ClassEntry subRangeClassEntry = lookupClassEntry(ownerType);
-        MethodEntry subRangeMethodEntry = subRangeClassEntry.ensureMethodEntryForDebugRangeInfo(locationInfo, this, debugContext);
-        SubRange subRange = Range.createSubrange(subRangeMethodEntry, lo, hi, line, primaryRange, caller, locationInfo.isLeaf());
-        classEntry.indexSubRange(subRange);
-        subRangeIndex.put(locationInfo, subRange);
-        if (debugContext.isLogEnabled(DebugContext.DETAILED_LEVEL)) {
-            debugContext.log(DebugContext.DETAILED_LEVEL, "SubRange %s.%s %d %s:%d [0x%x, 0x%x] (%d, %d)",
-                    ownerType.toJavaName(), methodName, subRange.getDepth(), fullPath, line, lo, hi, loOff, hiOff);
-        }
-        assert (callerLocationInfo == null || (callerLocationInfo.addressLo() <= loOff && callerLocationInfo.addressHi() >= hiOff)) : "parent range should enclose subrange!";
-        DebugLocalValueInfo[] localValueInfos = locationInfo.getLocalValueInfo();
-        for (int i = 0; i < localValueInfos.length; i++) {
-            DebugLocalValueInfo localValueInfo = localValueInfos[i];
-            if (debugContext.isLogEnabled(DebugContext.DETAILED_LEVEL)) {
-                debugContext.log(DebugContext.DETAILED_LEVEL, "  locals[%d] %s:%s = %s", localValueInfo.slot(), localValueInfo.name(), localValueInfo.typeName(), localValueInfo);
-            }
-        }
-        subRange.setLocalValueInfo(localValueInfos);
-        return subRange;
-    }
-
-    static final Path EMPTY_PATH = Paths.get("");
-
-    @Override
-    protected FileEntry addFileEntry(String fileName, Path filePath) {
-        assert fileName != null;
-        Path dirPath = filePath;
-        Path fileAsPath;
-        if (filePath != null) {
-            fileAsPath = dirPath.resolve(fileName);
-        } else {
-            fileAsPath = Paths.get(fileName);
-            dirPath = EMPTY_PATH;
-        }
-        FileEntry fileEntry = filesIndex.get(fileAsPath);
-        if (fileEntry == null) {
-            DirEntry dirEntry = ensureDirEntry(dirPath);
-            /* Ensure file and cachepath are added to the debug_str section. */
-            uniqueDebugString(fileName);
-            uniqueDebugString(cachePath);
-            fileEntry = new FileEntry(fileName, dirEntry);
-            files.add(fileEntry);
-            /* Index the file entry by file path. */
-            filesIndex.put(fileAsPath, fileEntry);
-        } else {
-            assert fileEntry.getDirEntry().getPath().equals(dirPath);
-        }
-        return fileEntry;
-    }
-
-    @Override
-    protected FileEntry ensureFileEntry(DebugFileInfo debugFileInfo) {
-        String fileName = debugFileInfo.fileName();
-        if (fileName == null || fileName.length() == 0) {
-            return null;
-        }
-        Path filePath = debugFileInfo.filePath();
-        Path fileAsPath;
-        if (filePath == null) {
-            fileAsPath = Paths.get(fileName);
-        } else {
-            fileAsPath = filePath.resolve(fileName);
-        }
-        /* Reuse any existing entry. */
-        FileEntry fileEntry = findFile(fileAsPath);
-        if (fileEntry == null) {
-            fileEntry = addFileEntry(fileName, filePath);
-        }
-        return fileEntry;
-    }
-
-    public CompiledMethodEntry getCompiledMethod() {
-        return compiledMethods.getFirst();
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/RuntimeDebugInfoProvider.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/RuntimeDebugInfoProvider.java
deleted file mode 100644
index effb4a2e5256..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/RuntimeDebugInfoProvider.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime;
-
-import com.oracle.objectfile.debuginfo.DebugInfoProvider;
-import jdk.graal.compiler.debug.DebugContext;
-import jdk.vm.ci.meta.JavaConstant;
-import jdk.vm.ci.meta.JavaKind;
-import jdk.vm.ci.meta.ResolvedJavaMethod;
-import jdk.vm.ci.meta.ResolvedJavaType;
-
-import java.nio.file.Path;
-import java.util.List;
-import java.util.function.Consumer;
-
-/**
- * Interfaces used to allow a native image to communicate details of types, code and data to the
- * underlying object file so that the latter can insert appropriate debug info.
- */
-public interface RuntimeDebugInfoProvider {
-
-    boolean useHeapBase();
-
-    String getCompilationUnitName();
-
-    /**
-     * Number of bits oops are left shifted by when using compressed oops.
-     */
-    int oopCompressShift();
-
-    /**
-     * Mask selecting low order bits used for tagging oops.
-     */
-    int oopTagsMask();
-
-    /**
-     * Number of bytes used to store an oop reference.
-     */
-    int oopReferenceSize();
-
-    /**
-     * Number of bytes used to store a raw pointer.
-     */
-    int pointerSize();
-
-    /**
-     * Alignment of object memory area (and, therefore, of any oop) in bytes.
-     */
-    int oopAlignment();
-
-    int compiledCodeMax();
-
-    DebugInfoProvider.DebugCodeInfo codeInfoProvider();
-
-    DebugInfoProvider.DebugTypeInfo createDebugTypeInfo(ResolvedJavaType javaType);
-
-    Path getCachePath();
-
-    void recordActivity();
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfAbbrevSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfAbbrevSectionImpl.java
deleted file mode 100644
index 5024fb8ed738..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfAbbrevSectionImpl.java
+++ /dev/null
@@ -1,399 +0,0 @@
-/*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.dwarf;
-
-import com.oracle.objectfile.runtime.dwarf.RuntimeDwarfDebugInfo.AbbrevCode;
-import com.oracle.objectfile.elf.dwarf.constants.DwarfAttribute;
-import com.oracle.objectfile.elf.dwarf.constants.DwarfForm;
-import com.oracle.objectfile.elf.dwarf.constants.DwarfHasChildren;
-import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName;
-import com.oracle.objectfile.elf.dwarf.constants.DwarfTag;
-
-import jdk.graal.compiler.debug.DebugContext;
-
-public class RuntimeDwarfAbbrevSectionImpl extends RuntimeDwarfSectionImpl {
-
-    public RuntimeDwarfAbbrevSectionImpl(RuntimeDwarfDebugInfo dwarfSections) {
-        // abbrev section depends on ranges section
-        super(dwarfSections, DwarfSectionName.DW_ABBREV_SECTION, DwarfSectionName.DW_FRAME_SECTION);
-    }
-
-    @Override
-    public void createContent() {
-        assert !contentByteArrayCreated();
-        /*
-         */
-
-        int pos = 0;
-        pos = writeAbbrevs(null, null, pos);
-
-        byte[] buffer = new byte[pos];
-        super.setContent(buffer);
-    }
-
-    @Override
-    public void writeContent(DebugContext context) {
-        assert contentByteArrayCreated();
-
-        byte[] buffer = getContent();
-        int size = buffer.length;
-        int pos = 0;
-
-        enableLog(context, pos);
-
-        pos = writeAbbrevs(context, buffer, pos);
-
-        assert pos == size;
-    }
-
-    public int writeAbbrevs(DebugContext context, byte[] buffer, int p) {
-        int pos = p;
-        pos = writeCompileUnitAbbrev(context, buffer, pos);
-
-        pos = writeNamespaceAbbrev(context, buffer, pos);
-
-        pos = writeClassLayoutAbbrev(context, buffer, pos);
-
-        pos = writeMethodDeclarationAbbrevs(context, buffer, pos);
-
-        pos = writeMethodLocationAbbrev(context, buffer, pos);
-
-        pos = writeInlinedSubroutineAbbrev(buffer, pos);
-
-        pos = writeParameterDeclarationAbbrevs(context, buffer, pos);
-        pos = writeLocalDeclarationAbbrevs(context, buffer, pos);
-
-        pos = writeParameterLocationAbbrevs(context, buffer, pos);
-        pos = writeLocalLocationAbbrevs(context, buffer, pos);
-
-        /* write a null abbrev to terminate the sequence */
-        pos = writeNullAbbrev(context, buffer, pos);
-        return pos;
-    }
-
-    private int writeAttrType(DwarfAttribute attribute, byte[] buffer, int pos) {
-        return writeULEB(attribute.value(), buffer, pos);
-    }
-
-    private int writeAttrForm(DwarfForm dwarfForm, byte[] buffer, int pos) {
-        return writeULEB(dwarfForm.value(), buffer, pos);
-    }
-
-    private int writeHasChildren(DwarfHasChildren hasChildren, byte[] buffer, int pos) {
-        return writeByte(hasChildren.value(), buffer, pos);
-    }
-
-    private int writeCompileUnitAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
-        int pos = p;
-        pos = writeAbbrevCode(AbbrevCode.CLASS_UNIT, buffer, pos);
-        pos = writeTag(DwarfTag.DW_TAG_compile_unit, buffer, pos);
-        pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_yes, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_language, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_use_UTF8, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_comp_dir, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_low_pc, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_addr, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_hi_pc, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_addr, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_stmt_list, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_sec_offset, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_loclists_base, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_sec_offset, buffer, pos);
-        /*
-         * Now terminate.
-         */
-        pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos);
-        return pos;
-    }
-
-    private int writeNamespaceAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
-        int pos = p;
-        pos = writeAbbrevCode(AbbrevCode.NAMESPACE, buffer, pos);
-        pos = writeTag(DwarfTag.DW_TAG_namespace, buffer, pos);
-        pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_yes, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos);
-        /*
-         * Now terminate.
-         */
-        pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos);
-        return pos;
-    }
-
-    private int writeClassLayoutAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
-        int pos = p;
-
-        pos = writeAbbrevCode(AbbrevCode.CLASS_LAYOUT, buffer, pos);
-        pos = writeTag(DwarfTag.DW_TAG_class_type, buffer, pos);
-        pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_yes, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_declaration, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_signature, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_ref_sig8, buffer, pos);
-        /*
-         * Now terminate.
-         */
-        pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos);
-
-        return pos;
-    }
-
-    private int writeMethodDeclarationAbbrevs(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
-        int pos = p;
-        pos = writeMethodDeclarationAbbrev(context, AbbrevCode.METHOD_DECLARATION, buffer, pos);
-        pos = writeMethodDeclarationAbbrev(context, AbbrevCode.METHOD_DECLARATION_INLINE, buffer, pos);
-        pos = writeMethodDeclarationAbbrev(context, AbbrevCode.METHOD_DECLARATION_STATIC, buffer, pos);
-        pos = writeMethodDeclarationAbbrev(context, AbbrevCode.METHOD_DECLARATION_INLINE_STATIC, buffer, pos);
-        return pos;
-    }
-
-    private int writeMethodDeclarationAbbrev(@SuppressWarnings("unused") DebugContext context, AbbrevCode abbrevCode, byte[] buffer, int p) {
-        int pos = p;
-        pos = writeAbbrevCode(abbrevCode, buffer, pos);
-        pos = writeTag(DwarfTag.DW_TAG_subprogram, buffer, pos);
-        pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_yes, buffer, pos);
-        if (abbrevCode == AbbrevCode.METHOD_DECLARATION_INLINE || abbrevCode == AbbrevCode.METHOD_DECLARATION_INLINE_STATIC) {
-            pos = writeAttrType(DwarfAttribute.DW_AT_inline, buffer, pos);
-            pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos);
-        }
-        pos = writeAttrType(DwarfAttribute.DW_AT_external, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_decl_file, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_decl_line, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_linkage_name, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_type, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_ref_sig8, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_artificial, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_accessibility, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_declaration, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos);
-        /* This is not in DWARF2 */
-        // pos = writeAttrType(DW_AT_virtuality, buffer, pos);
-        // pos = writeAttrForm(DW_FORM_data1, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_containing_type, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_ref_sig8, buffer, pos);
-        if (abbrevCode == AbbrevCode.METHOD_DECLARATION || abbrevCode == AbbrevCode.METHOD_DECLARATION_INLINE) {
-            pos = writeAttrType(DwarfAttribute.DW_AT_object_pointer, buffer, pos);
-            pos = writeAttrForm(DwarfForm.DW_FORM_ref4, buffer, pos);
-        }
-        /*
-         * Now terminate.
-         */
-        pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos);
-        return pos;
-    }
-
-    private int writeMethodLocationAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
-        int pos = p;
-        pos = writeAbbrevCode(AbbrevCode.METHOD_LOCATION, buffer, pos);
-        pos = writeTag(DwarfTag.DW_TAG_subprogram, buffer, pos);
-        pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_yes, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_low_pc, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_addr, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_hi_pc, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_addr, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_external, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_specification, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_ref_addr, buffer, pos);
-        /*
-         * Now terminate.
-         */
-        pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos);
-        return pos;
-    }
-
-    private int writeParameterDeclarationAbbrevs(DebugContext context, byte[] buffer, int p) {
-        int pos = p;
-        pos = writeParameterDeclarationAbbrev(context, AbbrevCode.METHOD_PARAMETER_DECLARATION_1, buffer, pos);
-        pos = writeParameterDeclarationAbbrev(context, AbbrevCode.METHOD_PARAMETER_DECLARATION_2, buffer, pos);
-        pos = writeParameterDeclarationAbbrev(context, AbbrevCode.METHOD_PARAMETER_DECLARATION_3, buffer, pos);
-        return pos;
-    }
-
-    private int writeParameterDeclarationAbbrev(@SuppressWarnings("unused") DebugContext context, AbbrevCode abbrevCode, byte[] buffer, int p) {
-        int pos = p;
-        pos = writeAbbrevCode(abbrevCode, buffer, pos);
-        pos = writeTag(DwarfTag.DW_TAG_formal_parameter, buffer, pos);
-        pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_no, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos);
-        if (abbrevCode == AbbrevCode.METHOD_PARAMETER_DECLARATION_2) {
-            /* Line numbers for parameter declarations are not (yet?) available. */
-            pos = writeAttrType(DwarfAttribute.DW_AT_decl_file, buffer, pos);
-            pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos);
-            pos = writeAttrType(DwarfAttribute.DW_AT_decl_line, buffer, pos);
-            pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos);
-        }
-        pos = writeAttrType(DwarfAttribute.DW_AT_type, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_ref_sig8, buffer, pos);
-        if (abbrevCode == AbbrevCode.METHOD_PARAMETER_DECLARATION_1) {
-            /* Only this parameter is artificial and it has no line. */
-            pos = writeAttrType(DwarfAttribute.DW_AT_artificial, buffer, pos);
-            pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos);
-        }
-        pos = writeAttrType(DwarfAttribute.DW_AT_declaration, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos);
-        /*
-         * Now terminate.
-         */
-        pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos);
-        return pos;
-    }
-
-    private int writeLocalDeclarationAbbrevs(DebugContext context, byte[] buffer, int p) {
-        int pos = p;
-        pos = writeLocalDeclarationAbbrev(context, AbbrevCode.METHOD_LOCAL_DECLARATION_1, buffer, pos);
-        pos = writeLocalDeclarationAbbrev(context, AbbrevCode.METHOD_LOCAL_DECLARATION_2, buffer, pos);
-        return pos;
-    }
-
-    private int writeLocalDeclarationAbbrev(@SuppressWarnings("unused") DebugContext context, AbbrevCode abbrevCode, byte[] buffer, int p) {
-        int pos = p;
-        pos = writeAbbrevCode(abbrevCode, buffer, pos);
-        pos = writeTag(DwarfTag.DW_TAG_variable, buffer, pos);
-        pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_no, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos);
-        if (abbrevCode == AbbrevCode.METHOD_LOCAL_DECLARATION_1) {
-            /* Line numbers for parameter declarations are not (yet?) available. */
-            pos = writeAttrType(DwarfAttribute.DW_AT_decl_file, buffer, pos);
-            pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos);
-            pos = writeAttrType(DwarfAttribute.DW_AT_decl_line, buffer, pos);
-            pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos);
-        }
-        pos = writeAttrType(DwarfAttribute.DW_AT_type, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_ref_sig8, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_declaration, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos);
-        /*
-         * Now terminate.
-         */
-        pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos);
-        return pos;
-    }
-
-    private int writeParameterLocationAbbrevs(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
-        int pos = p;
-        pos = writeParameterLocationAbbrev(context, AbbrevCode.METHOD_PARAMETER_LOCATION_1, buffer, pos);
-        pos = writeParameterLocationAbbrev(context, AbbrevCode.METHOD_PARAMETER_LOCATION_2, buffer, pos);
-        return pos;
-    }
-
-    private int writeLocalLocationAbbrevs(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
-        int pos = p;
-        pos = writeLocalLocationAbbrev(context, AbbrevCode.METHOD_LOCAL_LOCATION_1, buffer, pos);
-        pos = writeLocalLocationAbbrev(context, AbbrevCode.METHOD_LOCAL_LOCATION_2, buffer, pos);
-        return pos;
-    }
-
-    private int writeParameterLocationAbbrev(@SuppressWarnings("unused") DebugContext context, AbbrevCode abbrevCode, byte[] buffer, int p) {
-        int pos = p;
-        pos = writeAbbrevCode(abbrevCode, buffer, pos);
-        pos = writeTag(DwarfTag.DW_TAG_formal_parameter, buffer, pos);
-        pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_no, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_abstract_origin, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_ref_addr, buffer, pos);
-        if (abbrevCode == AbbrevCode.METHOD_PARAMETER_LOCATION_2) {
-            pos = writeAttrType(DwarfAttribute.DW_AT_location, buffer, pos);
-            pos = writeAttrForm(DwarfForm.DW_FORM_loclistx, buffer, pos);
-        }
-        /*
-         * Now terminate.
-         */
-        pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos);
-        return pos;
-    }
-
-    private int writeLocalLocationAbbrev(@SuppressWarnings("unused") DebugContext context, AbbrevCode abbrevCode, byte[] buffer, int p) {
-        int pos = p;
-        pos = writeAbbrevCode(abbrevCode, buffer, pos);
-        pos = writeTag(DwarfTag.DW_TAG_variable, buffer, pos);
-        pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_no, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_abstract_origin, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_ref_addr, buffer, pos);
-        if (abbrevCode == AbbrevCode.METHOD_LOCAL_LOCATION_2) {
-            pos = writeAttrType(DwarfAttribute.DW_AT_location, buffer, pos);
-            pos = writeAttrForm(DwarfForm.DW_FORM_loclistx, buffer, pos);
-        }
-        /*
-         * Now terminate.
-         */
-        pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos);
-        return pos;
-    }
-
-    private int writeNullAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
-        int pos = p;
-        pos = writeAbbrevCode(AbbrevCode.NULL, buffer, pos);
-        return pos;
-    }
-
-    private int writeInlinedSubroutineAbbrev(byte[] buffer, int p) {
-        int pos = p;
-        pos = writeAbbrevCode(AbbrevCode.INLINED_SUBROUTINE_WITH_CHILDREN, buffer, pos);
-        pos = writeTag(DwarfTag.DW_TAG_inlined_subroutine, buffer, pos);
-        pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_yes, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_abstract_origin, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_ref4, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_low_pc, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_addr, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_hi_pc, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_addr, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_call_file, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_data4, buffer, pos);
-        pos = writeAttrType(DwarfAttribute.DW_AT_call_line, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_data4, buffer, pos);
-        /* Now terminate. */
-        pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos);
-        pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos);
-        return pos;
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfDebugInfo.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfDebugInfo.java
deleted file mode 100644
index 7da54a2f805d..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfDebugInfo.java
+++ /dev/null
@@ -1,524 +0,0 @@
-/*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.dwarf;
-
-import java.nio.ByteOrder;
-
-import com.oracle.objectfile.runtime.RuntimeDebugInfoBase;
-import org.graalvm.collections.EconomicMap;
-
-import com.oracle.objectfile.debugentry.ClassEntry;
-import com.oracle.objectfile.debugentry.MethodEntry;
-import com.oracle.objectfile.debugentry.StructureTypeEntry;
-import com.oracle.objectfile.debugentry.TypeEntry;
-import com.oracle.objectfile.debugentry.range.Range;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalInfo;
-import com.oracle.objectfile.elf.ELFMachine;
-import com.oracle.objectfile.elf.dwarf.constants.DwarfLanguage;
-
-/**
- * A class that models the debug info in an organization that facilitates generation of the required
- * DWARF sections. It groups common data and behaviours for use by the various subclasses of class
- * DwarfSectionImpl that take responsibility for generating content for a specific section type.
- */
-public class RuntimeDwarfDebugInfo extends RuntimeDebugInfoBase {
-
-    public static final String HEAP_BEGIN_NAME = "__svm_heap_begin";
-
-    /*
-     * Define all the abbrev section codes we need for our DIEs.
-     */
-    enum AbbrevCode {
-        /* null marker which must come first as its ordinal has to equal zero */
-        NULL,
-        /* Level 0 DIEs. */
-        CLASS_UNIT,
-        /* Level 1 DIEs. */
-        NAMESPACE,
-        CLASS_LAYOUT,
-        METHOD_LOCATION,
-        /* Level 2 DIEs. */
-        METHOD_DECLARATION,
-        METHOD_DECLARATION_INLINE,
-        METHOD_DECLARATION_STATIC,
-        METHOD_DECLARATION_INLINE_STATIC,
-        /* Level 2+K DIEs (where inline depth K >= 0) */
-        INLINED_SUBROUTINE_WITH_CHILDREN,
-        /* Level 3 DIEs. */
-        METHOD_PARAMETER_DECLARATION_1,
-        METHOD_PARAMETER_DECLARATION_2,
-        METHOD_PARAMETER_DECLARATION_3,
-        METHOD_LOCAL_DECLARATION_1,
-        METHOD_LOCAL_DECLARATION_2,
-        METHOD_PARAMETER_LOCATION_1,
-        METHOD_PARAMETER_LOCATION_2,
-        METHOD_LOCAL_LOCATION_1,
-        METHOD_LOCAL_LOCATION_2,
-    }
-
-    /**
-     * This field defines the value used for the DW_AT_language attribute of compile units.
-     *
-     */
-    public static final DwarfLanguage LANG_ENCODING = DwarfLanguage.DW_LANG_Java;
-
-    /* Register constants for AArch64. */
-    public static final byte rheapbase_aarch64 = (byte) 27;
-    public static final byte rthread_aarch64 = (byte) 28;
-    /* Register constants for x86. */
-    public static final byte rheapbase_x86 = (byte) 14;
-    public static final byte rthread_x86 = (byte) 15;
-
-    /*
-     * A prefix used to label indirect types used to ensure gdb performs oop reference --> raw
-     * address translation
-     */
-    public static final String INDIRECT_PREFIX = "_z_.";
-    /*
-     * A prefix used for type signature generation to generate unique type signatures for type
-     * layout type units
-     */
-    public static final String LAYOUT_PREFIX = "_layout_.";
-    /*
-     * The name of the type for header field hub which needs special case processing to remove tag
-     * bits
-     */
-    public static final String HUB_TYPE_NAME = "java.lang.Class";
-    /* Full byte/word values. */
-    private final RuntimeDwarfStrSectionImpl dwarfStrSection;
-    private final RuntimeDwarfAbbrevSectionImpl dwarfAbbrevSection;
-    private final RuntimeDwarfInfoSectionImpl dwarfInfoSection;
-    private final RuntimeDwarfLocSectionImpl dwarfLocSection;
-    private final RuntimeDwarfLineSectionImpl dwarfLineSection;
-    private final RuntimeDwarfFrameSectionImpl dwarfFameSection;
-    public final ELFMachine elfMachine;
-    /**
-     * Register used to hold the heap base.
-     */
-    private final byte heapbaseRegister;
-    /**
-     * Register used to hold the current thread.
-     */
-    private final byte threadRegister;
-
-    /**
-     * A collection of properties associated with each generated type record indexed by type name.
-     * n.b. this collection includes entries for the structure types used to define the object and
-     * array headers which do not have an associated TypeEntry.
-     */
-    private final EconomicMap<TypeEntry, DwarfClassProperties> classPropertiesIndex = EconomicMap.create();
-
-    /**
-     * A collection of method properties associated with each generated method record.
-     */
-    private final EconomicMap<MethodEntry, DwarfMethodProperties> methodPropertiesIndex = EconomicMap.create();
-
-    /**
-     * A collection of local variable properties associated with an inlined subrange.
-     */
-    private final EconomicMap<Range, DwarfLocalProperties> rangeLocalPropertiesIndex = EconomicMap.create();
-
-    @SuppressWarnings("this-escape")
-    public RuntimeDwarfDebugInfo(ELFMachine elfMachine, ByteOrder byteOrder) {
-        super(byteOrder);
-        this.elfMachine = elfMachine;
-        dwarfStrSection = new RuntimeDwarfStrSectionImpl(this);
-        dwarfAbbrevSection = new RuntimeDwarfAbbrevSectionImpl(this);
-        dwarfInfoSection = new RuntimeDwarfInfoSectionImpl(this);
-        dwarfLocSection = new RuntimeDwarfLocSectionImpl(this);
-        dwarfLineSection = new RuntimeDwarfLineSectionImpl(this);
-
-        if (elfMachine == ELFMachine.AArch64) {
-            dwarfFameSection = new RuntimeDwarfFrameSectionImplAArch64(this);
-            this.heapbaseRegister = rheapbase_aarch64;
-            this.threadRegister = rthread_aarch64;
-        } else {
-            dwarfFameSection = new RuntimeDwarfFrameSectionImplX86_64(this);
-            this.heapbaseRegister = rheapbase_x86;
-            this.threadRegister = rthread_x86;
-        }
-    }
-
-    public RuntimeDwarfStrSectionImpl getStrSectionImpl() {
-        return dwarfStrSection;
-    }
-
-    public RuntimeDwarfAbbrevSectionImpl getAbbrevSectionImpl() {
-        return dwarfAbbrevSection;
-    }
-
-    public RuntimeDwarfFrameSectionImpl getFrameSectionImpl() {
-        return dwarfFameSection;
-    }
-
-    public RuntimeDwarfInfoSectionImpl getInfoSectionImpl() {
-        return dwarfInfoSection;
-    }
-
-    public RuntimeDwarfLocSectionImpl getLocSectionImpl() {
-        return dwarfLocSection;
-    }
-
-    public RuntimeDwarfLineSectionImpl getLineSectionImpl() {
-        return dwarfLineSection;
-    }
-
-    public byte getHeapbaseRegister() {
-        return heapbaseRegister;
-    }
-
-    public byte getThreadRegister() {
-        return threadRegister;
-    }
-
-    /**
-     * A class used to associate extra properties with an instance class type.
-     */
-
-    static class DwarfClassProperties {
-        /**
-         * The type entry with which these properties are associated.
-         */
-        private final StructureTypeEntry typeEntry;
-        /**
-         * Index of the class entry's compile unit in the debug_info section.
-         */
-        private int cuIndex;
-        /**
-         * Index of the class entry's code ranges data in the debug_rnglists section.
-         */
-        private int codeRangesIndex;
-        /**
-         * Index of the class entry's code location data in the debug_loclists section.
-         */
-        private int locationListIndex;
-        /**
-         * Index of the class entry's line data in the debug_line section.
-         */
-        private int lineIndex;
-        /**
-         * Size of the class entry's prologue in the debug_line section.
-         */
-        private int linePrologueSize;
-        /**
-         * Map from field names to info section index for the field declaration.
-         */
-        private EconomicMap<String, Integer> fieldDeclarationIndex;
-
-        public StructureTypeEntry getTypeEntry() {
-            return typeEntry;
-        }
-
-        DwarfClassProperties(StructureTypeEntry typeEntry) {
-            this.typeEntry = typeEntry;
-            this.cuIndex = -1;
-            this.codeRangesIndex = -1;
-            this.locationListIndex = 0;
-            this.lineIndex = -1;
-            this.linePrologueSize = -1;
-            fieldDeclarationIndex = null;
-        }
-    }
-
-    /**
-     * A class used to associate properties with a specific method.
-     */
-    static class DwarfMethodProperties {
-        /**
-         * The index in the info section at which the method's declaration resides.
-         */
-        private int methodDeclarationIndex;
-
-        /**
-         * Per class map that identifies the info declarations for a top level method declaration or
-         * an abstract inline method declaration.
-         */
-        private EconomicMap<ClassEntry, DwarfLocalProperties> localPropertiesMap;
-
-        /**
-         * Per class map that identifies the info declaration for an abstract inline method.
-         */
-        private EconomicMap<ClassEntry, Integer> abstractInlineMethodIndex;
-
-        DwarfMethodProperties() {
-            methodDeclarationIndex = -1;
-            localPropertiesMap = null;
-            abstractInlineMethodIndex = null;
-        }
-
-        public int getMethodDeclarationIndex() {
-            assert methodDeclarationIndex >= 0 : "unset declaration index";
-            return methodDeclarationIndex;
-        }
-
-        public void setMethodDeclarationIndex(int pos) {
-            assert methodDeclarationIndex == -1 || methodDeclarationIndex == pos : "bad declaration index";
-            methodDeclarationIndex = pos;
-        }
-
-        public DwarfLocalProperties getLocalProperties(ClassEntry classEntry) {
-            if (localPropertiesMap == null) {
-                localPropertiesMap = EconomicMap.create();
-            }
-            DwarfLocalProperties localProperties = localPropertiesMap.get(classEntry);
-            if (localProperties == null) {
-                localProperties = new DwarfLocalProperties();
-                localPropertiesMap.put(classEntry, localProperties);
-            }
-            return localProperties;
-        }
-
-        public void setAbstractInlineMethodIndex(ClassEntry classEntry, int pos) {
-            if (abstractInlineMethodIndex == null) {
-                abstractInlineMethodIndex = EconomicMap.create();
-            }
-            // replace but check it did not change
-            Integer val = abstractInlineMethodIndex.put(classEntry, pos);
-            assert val == null || val == pos;
-        }
-
-        public int getAbstractInlineMethodIndex(ClassEntry classEntry) {
-            // should be set before we get here but an NPE will guard that
-            return abstractInlineMethodIndex.get(classEntry);
-        }
-    }
-
-    private DwarfClassProperties addClassProperties(StructureTypeEntry entry) {
-        DwarfClassProperties classProperties = new DwarfClassProperties(entry);
-        this.classPropertiesIndex.put(entry, classProperties);
-        return classProperties;
-    }
-
-    private DwarfMethodProperties addMethodProperties(MethodEntry methodEntry) {
-        assert methodPropertiesIndex.get(methodEntry) == null;
-        DwarfMethodProperties methodProperties = new DwarfMethodProperties();
-        this.methodPropertiesIndex.put(methodEntry, methodProperties);
-        return methodProperties;
-    }
-
-    private DwarfClassProperties lookupClassProperties(StructureTypeEntry entry) {
-        DwarfClassProperties classProperties = classPropertiesIndex.get(entry);
-        if (classProperties == null) {
-            classProperties = addClassProperties(entry);
-        }
-        return classProperties;
-    }
-
-    private DwarfMethodProperties lookupMethodProperties(MethodEntry methodEntry) {
-        DwarfMethodProperties methodProperties = methodPropertiesIndex.get(methodEntry);
-        if (methodProperties == null) {
-            methodProperties = addMethodProperties(methodEntry);
-        }
-        return methodProperties;
-    }
-
-    public void setCUIndex(ClassEntry classEntry, int idx) {
-        assert idx >= 0;
-        DwarfClassProperties classProperties = lookupClassProperties(classEntry);
-        assert classProperties.getTypeEntry() == classEntry;
-        assert classProperties.cuIndex == -1 || classProperties.cuIndex == idx;
-        classProperties.cuIndex = idx;
-    }
-
-    public int getCUIndex(ClassEntry classEntry) {
-        DwarfClassProperties classProperties;
-        classProperties = lookupClassProperties(classEntry);
-        assert classProperties.getTypeEntry() == classEntry;
-        assert classProperties.cuIndex >= 0;
-        return classProperties.cuIndex;
-    }
-
-    public void setCodeRangesIndex(ClassEntry classEntry, int idx) {
-        assert idx >= 0;
-        DwarfClassProperties classProperties = lookupClassProperties(classEntry);
-        assert classProperties.getTypeEntry() == classEntry;
-        assert classProperties.codeRangesIndex == -1 || classProperties.codeRangesIndex == idx;
-        classProperties.codeRangesIndex = idx;
-    }
-
-    public int getCodeRangesIndex(ClassEntry classEntry) {
-        DwarfClassProperties classProperties;
-        classProperties = lookupClassProperties(classEntry);
-        assert classProperties.getTypeEntry() == classEntry;
-        assert classProperties.codeRangesIndex >= 0;
-        return classProperties.codeRangesIndex;
-    }
-
-    public void setLocationListIndex(ClassEntry classEntry, int idx) {
-        assert idx >= 0;
-        DwarfClassProperties classProperties = lookupClassProperties(classEntry);
-        assert classProperties.getTypeEntry() == classEntry;
-        assert classProperties.locationListIndex == 0 || classProperties.locationListIndex == idx;
-        classProperties.locationListIndex = idx;
-    }
-
-    public int getLocationListIndex(ClassEntry classEntry) {
-        DwarfClassProperties classProperties;
-        classProperties = lookupClassProperties(classEntry);
-        assert classProperties.getTypeEntry() == classEntry;
-        return classProperties.locationListIndex;
-    }
-
-    public void setLineIndex(ClassEntry classEntry, int idx) {
-        assert idx >= 0;
-        DwarfClassProperties classProperties = lookupClassProperties(classEntry);
-        assert classProperties.getTypeEntry() == classEntry;
-        assert classProperties.lineIndex == -1 || classProperties.lineIndex == idx;
-        classProperties.lineIndex = idx;
-    }
-
-    public int getLineIndex(ClassEntry classEntry) {
-        DwarfClassProperties classProperties;
-        classProperties = lookupClassProperties(classEntry);
-        assert classProperties.getTypeEntry() == classEntry;
-        assert classProperties.lineIndex >= 0;
-        return classProperties.lineIndex;
-    }
-
-    public void setLinePrologueSize(ClassEntry classEntry, int size) {
-        assert size >= 0;
-        DwarfClassProperties classProperties = lookupClassProperties(classEntry);
-        assert classProperties.getTypeEntry() == classEntry;
-        assert classProperties.linePrologueSize == -1 || classProperties.linePrologueSize == size;
-        classProperties.linePrologueSize = size;
-    }
-
-    public int getLinePrologueSize(ClassEntry classEntry) {
-        DwarfClassProperties classProperties;
-        classProperties = lookupClassProperties(classEntry);
-        assert classProperties.getTypeEntry() == classEntry;
-        assert classProperties.linePrologueSize >= 0;
-        return classProperties.linePrologueSize;
-    }
-
-    public void setFieldDeclarationIndex(StructureTypeEntry entry, String fieldName, int pos) {
-        DwarfClassProperties classProperties;
-        classProperties = lookupClassProperties(entry);
-        assert classProperties.getTypeEntry() == entry;
-        EconomicMap<String, Integer> fieldDeclarationIndex = classProperties.fieldDeclarationIndex;
-        if (fieldDeclarationIndex == null) {
-            classProperties.fieldDeclarationIndex = fieldDeclarationIndex = EconomicMap.create();
-        }
-        if (fieldDeclarationIndex.get(fieldName) != null) {
-            assert fieldDeclarationIndex.get(fieldName) == pos : entry.getTypeName() + fieldName;
-        } else {
-            fieldDeclarationIndex.put(fieldName, pos);
-        }
-    }
-
-    public int getFieldDeclarationIndex(StructureTypeEntry entry, String fieldName) {
-        DwarfClassProperties classProperties;
-        classProperties = lookupClassProperties(entry);
-        assert classProperties.getTypeEntry() == entry;
-        EconomicMap<String, Integer> fieldDeclarationIndex = classProperties.fieldDeclarationIndex;
-        assert fieldDeclarationIndex != null : fieldName;
-        assert fieldDeclarationIndex.get(fieldName) != null : entry.getTypeName() + fieldName;
-        return fieldDeclarationIndex.get(fieldName);
-    }
-
-    public void setMethodDeclarationIndex(MethodEntry methodEntry, int pos) {
-        DwarfMethodProperties methodProperties = lookupMethodProperties(methodEntry);
-        methodProperties.setMethodDeclarationIndex(pos);
-    }
-
-    public int getMethodDeclarationIndex(MethodEntry methodEntry) {
-        DwarfMethodProperties methodProperties = lookupMethodProperties(methodEntry);
-        return methodProperties.getMethodDeclarationIndex();
-    }
-
-    /**
-     * A class used to associate properties with a specific param or local whether top level or
-     * inline.
-     */
-
-    static final class DwarfLocalProperties {
-        private EconomicMap<DebugLocalInfo, Integer> locals;
-
-        private DwarfLocalProperties() {
-            locals = EconomicMap.create();
-        }
-
-        int getIndex(DebugLocalInfo localInfo) {
-            return locals.get(localInfo);
-        }
-
-        void setIndex(DebugLocalInfo localInfo, int index) {
-            if (locals.get(localInfo) != null) {
-                assert locals.get(localInfo) == index;
-            } else {
-                locals.put(localInfo, index);
-            }
-        }
-    }
-
-    public void setAbstractInlineMethodIndex(ClassEntry classEntry, MethodEntry methodEntry, int pos) {
-        lookupMethodProperties(methodEntry).setAbstractInlineMethodIndex(classEntry, pos);
-    }
-
-    public int getAbstractInlineMethodIndex(ClassEntry classEntry, MethodEntry methodEntry) {
-        return lookupMethodProperties(methodEntry).getAbstractInlineMethodIndex(classEntry);
-    }
-
-    private DwarfLocalProperties addRangeLocalProperties(Range range) {
-        DwarfLocalProperties localProperties = new DwarfLocalProperties();
-        rangeLocalPropertiesIndex.put(range, localProperties);
-        return localProperties;
-    }
-
-    public DwarfLocalProperties lookupLocalProperties(ClassEntry classEntry, MethodEntry methodEntry) {
-        return lookupMethodProperties(methodEntry).getLocalProperties(classEntry);
-    }
-
-    public void setMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, DebugLocalInfo localInfo, int index) {
-        DwarfLocalProperties localProperties = lookupLocalProperties(classEntry, methodEntry);
-        localProperties.setIndex(localInfo, index);
-    }
-
-    public int getMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, DebugLocalInfo localInfo) {
-        DwarfLocalProperties localProperties = lookupLocalProperties(classEntry, methodEntry);
-        assert localProperties != null : "get of non-existent local index";
-        int index = localProperties.getIndex(localInfo);
-        assert index >= 0 : "get of local index before it was set";
-        return index;
-    }
-
-    public void setRangeLocalIndex(Range range, DebugLocalInfo localInfo, int index) {
-        DwarfLocalProperties rangeProperties = rangeLocalPropertiesIndex.get(range);
-        if (rangeProperties == null) {
-            rangeProperties = addRangeLocalProperties(range);
-        }
-        rangeProperties.setIndex(localInfo, index);
-    }
-
-    public int getRangeLocalIndex(Range range, DebugLocalInfo localinfo) {
-        DwarfLocalProperties rangeProperties = rangeLocalPropertiesIndex.get(range);
-        assert rangeProperties != null : "get of non-existent local index";
-        int index = rangeProperties.getIndex(localinfo);
-        assert index >= 0 : "get of local index before it was set";
-        return index;
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImpl.java
deleted file mode 100644
index 0927c8705115..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImpl.java
+++ /dev/null
@@ -1,303 +0,0 @@
-/*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.dwarf;
-
-import com.oracle.objectfile.debugentry.CompiledMethodEntry;
-import com.oracle.objectfile.debugentry.range.Range;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider;
-import com.oracle.objectfile.elf.dwarf.constants.DwarfFrameValue;
-import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName;
-import jdk.graal.compiler.debug.DebugContext;
-
-import java.util.List;
-
-/**
- * Section generic generator for debug_frame section.
- */
-public abstract class RuntimeDwarfFrameSectionImpl extends RuntimeDwarfSectionImpl {
-
-    private static final int PADDING_NOPS_ALIGNMENT = 8;
-
-    private static final int CFA_CIE_id_default = -1;
-
-    public RuntimeDwarfFrameSectionImpl(RuntimeDwarfDebugInfo dwarfSections) {
-        // debug_frame section depends on debug_line section
-        super(dwarfSections, DwarfSectionName.DW_FRAME_SECTION, DwarfSectionName.DW_LINE_SECTION);
-    }
-
-    @Override
-    public void createContent() {
-        assert !contentByteArrayCreated();
-
-        int pos = 0;
-
-        /*
-         * The frame section contains one CIE at offset 0 followed by an FIE for each compiled
-         * method.
-         */
-        pos = writeCIE(null, pos);
-        pos = writeMethodFrames(null, pos);
-
-        byte[] buffer = new byte[pos];
-        super.setContent(buffer);
-    }
-
-    @Override
-    public void writeContent(DebugContext context) {
-        assert contentByteArrayCreated();
-
-        byte[] buffer = getContent();
-        int size = buffer.length;
-        int pos = 0;
-
-        enableLog(context, pos);
-
-        /*
-         * There are entries for the prologue region where the stack is being built, the method body
-         * region(s) where the code executes with a fixed size frame and the epilogue region(s)
-         * where the stack is torn down.
-         */
-        pos = writeCIE(buffer, pos);
-        pos = writeMethodFrames(buffer, pos);
-
-        if (pos != size) {
-            System.out.format("pos = 0x%x  size = 0x%x", pos, size);
-        }
-        assert pos == size;
-    }
-
-    private int writeCIE(byte[] buffer, int p) {
-        /*
-         * We only need a vanilla CIE with default fields because we have to have at least one the
-         * layout is:
-         *
-         * <ul>
-         *
-         * <li><code>uint32 : length ............... length of remaining fields in this CIE</code>
-         *
-         * <li><code>uint32 : CIE_id ................ unique id for CIE == 0xffffffff</code>
-         *
-         * <li><code>uint8 : version ................ == 1</code>
-         *
-         * <li><code>uint8[] : augmentation ......... == "" so always 1 byte</code>
-         *
-         * <li><code>ULEB : code_alignment_factor ... == 1 (could use 4 for Aarch64)</code>
-         *
-         * <li><code>ULEB : data_alignment_factor ... == -8</code>
-         *
-         * <li><code>byte : ret_addr reg id ......... x86_64 => 16 AArch64 => 30</code>
-         *
-         * <li><code>byte[] : initial_instructions .. includes pad to 8-byte boundary</code>
-         *
-         * </ul>
-         */
-        int pos = p;
-        int lengthPos = pos;
-        pos = writeInt(0, buffer, pos);
-        pos = writeInt(CFA_CIE_id_default, buffer, pos);
-        pos = writeCIEVersion(buffer, pos);
-        pos = writeByte((byte) 0, buffer, pos);
-        pos = writeULEB(1, buffer, pos);
-        pos = writeSLEB(-8, buffer, pos);
-        pos = writeByte((byte) getReturnPCIdx(), buffer, pos);
-        /*
-         * Write insns to set up empty frame.
-         */
-        pos = writeInitialInstructions(buffer, pos);
-        /*
-         * Pad to word alignment.
-         */
-        pos = writePaddingNops(buffer, pos);
-        patchLength(lengthPos, buffer, pos);
-        return pos;
-    }
-
-    private int writeCIEVersion(byte[] buffer, int pos) {
-        return writeByte(DwarfFrameValue.DW_CFA_CIE_version.value(), buffer, pos);
-    }
-
-    private int writeMethodFrame(CompiledMethodEntry compiledEntry, byte[] buffer, int p) {
-        int pos = p;
-        int lengthPos = pos;
-        Range range = compiledEntry.getPrimary();
-        long lo = range.getLo();
-        long hi = range.getHi();
-        pos = writeFDEHeader(lo, hi, buffer, pos);
-        pos = writeFDEs(compiledEntry.getFrameSize(), compiledEntry.getFrameSizeInfos(), buffer, pos);
-        pos = writePaddingNops(buffer, pos);
-        patchLength(lengthPos, buffer, pos);
-        return pos;
-    }
-
-    private int writeMethodFrames(byte[] buffer, int p) {
-        int pos = p;
-        pos = writeMethodFrame(dwarfSections.getCompiledMethod(), buffer, pos);
-        return pos;
-    }
-
-    protected abstract int writeFDEs(int frameSize, List<DebugInfoProvider.DebugFrameSizeChange> frameSizeInfos, byte[] buffer, int pos);
-
-    private int writeFDEHeader(long lo, long hi, byte[] buffer, int p) {
-        /*
-         * We only need a vanilla FDE header with default fields the layout is:
-         *
-         * <ul>
-         *
-         * <li><code>uint32 : length ............ length of remaining fields in this FDE</code>
-         *
-         * <li><code>uint32 : CIE_offset ........ always 0 i.e. identifies our only CIE
-         * header</code>
-         *
-         * <li><code>uint64 : initial_location .. i.e. method lo address</code>
-         *
-         * <li><code>uint64 : address_range ..... i.e. method hi - lo</code>
-         *
-         * <li><code>byte[] : instructions ...... includes pad to 8-byte boundary</code>
-         *
-         * </ul>
-         */
-
-        int pos = p;
-        /* Dummy length. */
-        pos = writeInt(0, buffer, pos);
-        /* CIE_offset */
-        pos = writeInt(0, buffer, pos);
-        /* Initial address. */
-        pos = writeLong(lo, buffer, pos);
-        /* Address range. */
-        return writeLong(hi - lo, buffer, pos);
-    }
-
-    private int writePaddingNops(byte[] buffer, int p) {
-        int pos = p;
-        while ((pos & (PADDING_NOPS_ALIGNMENT - 1)) != 0) {
-            pos = writeByte(DwarfFrameValue.DW_CFA_nop.value(), buffer, pos);
-        }
-        return pos;
-    }
-
-    protected int writeDefCFA(int register, int offset, byte[] buffer, int p) {
-        int pos = p;
-        pos = writeByte(DwarfFrameValue.DW_CFA_def_cfa.value(), buffer, pos);
-        pos = writeULEB(register, buffer, pos);
-        return writeULEB(offset, buffer, pos);
-    }
-
-    protected int writeDefCFAOffset(int offset, byte[] buffer, int p) {
-        int pos = p;
-        byte op = DwarfFrameValue.DW_CFA_def_cfa_offset.value();
-        pos = writeByte(op, buffer, pos);
-        return writeULEB(offset, buffer, pos);
-    }
-
-    protected int writeAdvanceLoc(int offset, byte[] buffer, int pos) {
-        if (offset <= 0x3f) {
-            return writeAdvanceLoc0((byte) offset, buffer, pos);
-        } else if (offset <= 0xff) {
-            return writeAdvanceLoc1((byte) offset, buffer, pos);
-        } else if (offset <= 0xffff) {
-            return writeAdvanceLoc2((short) offset, buffer, pos);
-        } else {
-            return writeAdvanceLoc4(offset, buffer, pos);
-        }
-    }
-
-    protected int writeAdvanceLoc0(byte offset, byte[] buffer, int pos) {
-        byte op = advanceLoc0Op(offset);
-        return writeByte(op, buffer, pos);
-    }
-
-    protected int writeAdvanceLoc1(byte offset, byte[] buffer, int p) {
-        int pos = p;
-        byte op = DwarfFrameValue.DW_CFA_advance_loc1.value();
-        pos = writeByte(op, buffer, pos);
-        return writeByte(offset, buffer, pos);
-    }
-
-    protected int writeAdvanceLoc2(short offset, byte[] buffer, int p) {
-        byte op = DwarfFrameValue.DW_CFA_advance_loc2.value();
-        int pos = p;
-        pos = writeByte(op, buffer, pos);
-        return writeShort(offset, buffer, pos);
-    }
-
-    protected int writeAdvanceLoc4(int offset, byte[] buffer, int p) {
-        byte op = DwarfFrameValue.DW_CFA_advance_loc4.value();
-        int pos = p;
-        pos = writeByte(op, buffer, pos);
-        return writeInt(offset, buffer, pos);
-    }
-
-    protected int writeOffset(int register, int offset, byte[] buffer, int p) {
-        byte op = offsetOp(register);
-        int pos = p;
-        pos = writeByte(op, buffer, pos);
-        return writeULEB(offset, buffer, pos);
-    }
-
-    protected int writeRestore(int register, byte[] buffer, int p) {
-        byte op = restoreOp(register);
-        int pos = p;
-        return writeByte(op, buffer, pos);
-    }
-
-    @SuppressWarnings("unused")
-    protected int writeRegister(int savedReg, int savedToReg, byte[] buffer, int p) {
-        int pos = p;
-        byte op = DwarfFrameValue.DW_CFA_register.value();
-        pos = writeByte(op, buffer, pos);
-        pos = writeULEB(savedReg, buffer, pos);
-        return writeULEB(savedToReg, buffer, pos);
-    }
-
-    protected abstract int getReturnPCIdx();
-
-    @SuppressWarnings("unused")
-    protected abstract int getSPIdx();
-
-    protected abstract int writeInitialInstructions(byte[] buffer, int pos);
-
-    private static byte offsetOp(int register) {
-        byte op = DwarfFrameValue.DW_CFA_offset.value();
-        return encodeOp(op, register);
-    }
-
-    private static byte restoreOp(int register) {
-        byte op = DwarfFrameValue.DW_CFA_restore.value();
-        return encodeOp(op, register);
-    }
-
-    private static byte advanceLoc0Op(int offset) {
-        byte op = DwarfFrameValue.DW_CFA_advance_loc.value();
-        return encodeOp(op, offset);
-    }
-
-    private static byte encodeOp(byte op, int value) {
-        assert (value >> 6) == 0;
-        return (byte) ((op << 6) | value);
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImplAArch64.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImplAArch64.java
deleted file mode 100644
index 952675254824..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImplAArch64.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.dwarf;
-
-import com.oracle.objectfile.debuginfo.DebugInfoProvider;
-
-import java.util.List;
-
-/**
- * AArch64-specific section generator for debug_frame section that knows details of AArch64
- * registers and frame layout.
- */
-public class RuntimeDwarfFrameSectionImplAArch64 extends RuntimeDwarfFrameSectionImpl {
-    public static final int CFA_FP_IDX = 29;
-    private static final int CFA_LR_IDX = 30;
-    private static final int CFA_SP_IDX = 31;
-    @SuppressWarnings("unused") private static final int CFA_PC_IDX = 32;
-
-    public RuntimeDwarfFrameSectionImplAArch64(RuntimeDwarfDebugInfo dwarfSections) {
-        super(dwarfSections);
-    }
-
-    @Override
-    public int getReturnPCIdx() {
-        return CFA_LR_IDX;
-    }
-
-    @Override
-    public int getSPIdx() {
-        return CFA_SP_IDX;
-    }
-
-    @Override
-    public int writeInitialInstructions(byte[] buffer, int p) {
-        int pos = p;
-        /*
-         * Invariant: CFA identifies last word of caller stack.
-         *
-         * So initial cfa is at rsp + 0:
-         *
-         * <ul>
-         *
-         * <li><code>def_cfa r31 (sp) offset 0</code>
-         *
-         * </ul>
-         */
-        pos = writeDefCFA(CFA_SP_IDX, 0, buffer, pos);
-        return pos;
-    }
-
-    @Override
-    protected int writeFDEs(int frameSize, List<DebugInfoProvider.DebugFrameSizeChange> frameSizeInfos, byte[] buffer, int p) {
-        int pos = p;
-        int currentOffset = 0;
-        for (DebugInfoProvider.DebugFrameSizeChange debugFrameSizeInfo : frameSizeInfos) {
-            int advance = debugFrameSizeInfo.getOffset() - currentOffset;
-            currentOffset += advance;
-            pos = writeAdvanceLoc(advance, buffer, pos);
-            if (debugFrameSizeInfo.getType() == DebugInfoProvider.DebugFrameSizeChange.Type.EXTEND) {
-                /*
-                 * SP has been extended so rebase CFA using full frame.
-                 *
-                 * Invariant: CFA identifies last word of caller stack.
-                 */
-                pos = writeDefCFAOffset(frameSize, buffer, pos);
-                /*
-                 * Notify push of lr and fp to stack slots 1 and 2.
-                 *
-                 * Scaling by -8 is automatic.
-                 */
-                pos = writeOffset(CFA_LR_IDX, 1, buffer, pos);
-                pos = writeOffset(CFA_FP_IDX, 2, buffer, pos);
-            } else {
-                /*
-                 * SP will have been contracted so rebase CFA using empty frame.
-                 *
-                 * Invariant: CFA identifies last word of caller stack.
-                 */
-                pos = writeDefCFAOffset(0, buffer, pos);
-                /*
-                 * notify restore of fp and lr
-                 */
-                pos = writeRestore(CFA_FP_IDX, buffer, pos);
-                pos = writeRestore(CFA_LR_IDX, buffer, pos);
-            }
-        }
-        return pos;
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImplX86_64.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImplX86_64.java
deleted file mode 100644
index 9fbeaa3949d6..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfFrameSectionImplX86_64.java
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.dwarf;
-
-import com.oracle.objectfile.debuginfo.DebugInfoProvider;
-
-import java.util.List;
-
-/**
- * x86_64-specific section generator for debug_frame section that knows details of x86_64 registers
- * and frame layout.
- */
-public class RuntimeDwarfFrameSectionImplX86_64 extends RuntimeDwarfFrameSectionImpl {
-    private static final int CFA_RSP_IDX = 7;
-    private static final int CFA_RIP_IDX = 16;
-
-    public RuntimeDwarfFrameSectionImplX86_64(RuntimeDwarfDebugInfo dwarfSections) {
-        super(dwarfSections);
-    }
-
-    @Override
-    public int getReturnPCIdx() {
-        return CFA_RIP_IDX;
-    }
-
-    @Override
-    public int getSPIdx() {
-        return CFA_RSP_IDX;
-    }
-
-    @Override
-    public int writeInitialInstructions(byte[] buffer, int p) {
-        int pos = p;
-        /*
-         * Invariant: CFA identifies last word of caller stack.
-         *
-         * Register rsp points at the word containing the saved rip so the frame base (cfa) is at
-         * rsp + 8:
-         *
-         * <ul>
-         *
-         * <li><code>def_cfa r7 (sp) offset 8</code>
-         *
-         * </ul>
-         */
-        pos = writeDefCFA(CFA_RSP_IDX, 8, buffer, pos);
-        /*
-         * Register rip is saved in slot 1.
-         *
-         * Scaling by -8 is automatic.
-         *
-         * <ul>
-         *
-         * <li><code>offset r16 (rip) cfa - 8</code>
-         *
-         * </ul>
-         */
-        pos = writeOffset(CFA_RIP_IDX, 1, buffer, pos);
-        return pos;
-    }
-
-    @Override
-    protected int writeFDEs(int frameSize, List<DebugInfoProvider.DebugFrameSizeChange> frameSizeInfos, byte[] buffer, int p) {
-        int pos = p;
-        int currentOffset = 0;
-        for (DebugInfoProvider.DebugFrameSizeChange debugFrameSizeInfo : frameSizeInfos) {
-            int advance = debugFrameSizeInfo.getOffset() - currentOffset;
-            currentOffset += advance;
-            pos = writeAdvanceLoc(advance, buffer, pos);
-            if (debugFrameSizeInfo.getType() == DebugInfoProvider.DebugFrameSizeChange.Type.EXTEND) {
-                /*
-                 * SP has been extended so rebase CFA using full frame.
-                 *
-                 * Invariant: CFA identifies last word of caller stack.
-                 */
-                pos = writeDefCFAOffset(frameSize, buffer, pos);
-            } else {
-                /*
-                 * SP has been contracted so rebase CFA using empty frame.
-                 *
-                 * Invariant: CFA identifies last word of caller stack.
-                 */
-                pos = writeDefCFAOffset(8, buffer, pos);
-            }
-        }
-        return pos;
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfInfoSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfInfoSectionImpl.java
deleted file mode 100644
index 39e416bc66d1..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfInfoSectionImpl.java
+++ /dev/null
@@ -1,729 +0,0 @@
-/*
- * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.dwarf;
-
-import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-
-import org.graalvm.collections.EconomicSet;
-
-import com.oracle.objectfile.debugentry.ClassEntry;
-import com.oracle.objectfile.debugentry.CompiledMethodEntry;
-import com.oracle.objectfile.debugentry.FileEntry;
-import com.oracle.objectfile.debugentry.MethodEntry;
-import com.oracle.objectfile.debugentry.TypeEntry;
-import com.oracle.objectfile.debugentry.range.Range;
-import com.oracle.objectfile.debugentry.range.SubRange;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalValueInfo;
-import com.oracle.objectfile.runtime.dwarf.RuntimeDwarfDebugInfo.AbbrevCode;
-import com.oracle.objectfile.elf.dwarf.constants.DwarfAccess;
-import com.oracle.objectfile.elf.dwarf.constants.DwarfEncoding;
-import com.oracle.objectfile.elf.dwarf.constants.DwarfFlag;
-import com.oracle.objectfile.elf.dwarf.constants.DwarfInline;
-import com.oracle.objectfile.elf.dwarf.constants.DwarfLanguage;
-import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName;
-import com.oracle.objectfile.elf.dwarf.constants.DwarfUnitHeader;
-import com.oracle.objectfile.elf.dwarf.constants.DwarfVersion;
-
-import jdk.graal.compiler.debug.DebugContext;
-import jdk.vm.ci.meta.JavaConstant;
-import jdk.vm.ci.meta.JavaKind;
-import jdk.vm.ci.meta.PrimitiveConstant;
-
-/**
- * Section generator for debug_info section.
- */
-public class RuntimeDwarfInfoSectionImpl extends RuntimeDwarfSectionImpl {
-    /**
-     * An info header section always contains a fixed number of bytes.
-     */
-    private static final int CU_DIE_HEADER_SIZE = 12;
-
-    private int unitStart;
-
-    public RuntimeDwarfInfoSectionImpl(RuntimeDwarfDebugInfo dwarfSections) {
-        // debug_info section depends on loc section
-        super(dwarfSections, DwarfSectionName.DW_INFO_SECTION, DwarfSectionName.DW_LOCLISTS_SECTION);
-        // initialize CU start to an invalid value
-        unitStart = -1;
-    }
-
-    @Override
-    public void createContent() {
-        assert !contentByteArrayCreated();
-
-        byte[] buffer = null;
-        int len = generateContent(null, buffer);
-
-        buffer = new byte[len];
-        super.setContent(buffer);
-    }
-
-    @Override
-    public void writeContent(DebugContext context) {
-        assert contentByteArrayCreated();
-
-        byte[] buffer = getContent();
-        int size = buffer.length;
-        int pos = 0;
-
-        enableLog(context, pos);
-        log(context, "  [0x%08x] DEBUG_INFO", pos);
-        log(context, "  [0x%08x] size = 0x%08x", pos, size);
-
-        pos = generateContent(context, buffer);
-        assert pos == size;
-    }
-
-    public int generateContent(DebugContext context, byte[] buffer) {
-        int pos = 0;
-
-        CompiledMethodEntry compiledMethodEntry = compiledMethod();
-        ClassEntry classEntry = compiledMethodEntry.getClassEntry();
-
-        // Write a Java class unit header
-        int lengthPos = pos;
-        log(context, "  [0x%08x] Instance class unit", pos);
-        pos = writeCUHeader(buffer, pos);
-        assert pos == lengthPos + CU_DIE_HEADER_SIZE;
-        AbbrevCode abbrevCode = AbbrevCode.CLASS_UNIT;
-        log(context, "  [0x%08x] <0> Abbrev Number %d", pos, abbrevCode.ordinal());
-        pos = writeAbbrevCode(abbrevCode, buffer, pos);
-        log(context, "  [0x%08x]     language  %s", pos, "DW_LANG_Java");
-        pos = writeAttrLanguage(RuntimeDwarfDebugInfo.LANG_ENCODING, buffer, pos);
-        log(context, "  [0x%08x]     use_UTF8", pos);
-        pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos);
-        String name = classEntry.getFullFileName();
-        if (name == null) {
-            name = classEntry.getTypeName().replace('.', '/') + ".java";
-        }
-        log(context, "  [0x%08x]     name  0x%x (%s)", pos, debugStringIndex(name), name);
-        pos = writeStrSectionOffset(uniqueDebugString(name), buffer, pos);
-        String compilationDirectory = dwarfSections.getCachePath();
-        log(context, "  [0x%08x]     comp_dir  0x%x (%s)", pos, debugStringIndex(compilationDirectory), compilationDirectory);
-        pos = writeStrSectionOffset(compilationDirectory, buffer, pos);
-        long lo = classEntry.lowpc();
-        long hi = classEntry.hipc();
-        log(context, "  [0x%08x]     low_pc  0x%x", pos, lo);
-        pos = writeLong(lo, buffer, pos);
-        log(context, "  [0x%08x]     hi_pc  0x%x", pos, hi);
-        pos = writeLong(hi, buffer, pos);
-        int lineIndex = getLineIndex(classEntry);
-        log(context, "  [0x%08x]     stmt_list  0x%x", pos, lineIndex);
-        pos = writeLineSectionOffset(lineIndex, buffer, pos);
-        int locationListIndex = getLocationListIndex(classEntry);
-        log(context, "  [0x%08x]     loclists_base  0x%x", pos, locationListIndex);
-        pos = writeLocSectionOffset(locationListIndex, buffer, pos);
-        /* if the class has a loader then embed the children in a namespace */
-        String loaderId = classEntry.getLoaderId();
-        if (!loaderId.isEmpty()) {
-            pos = writeNameSpace(context, loaderId, buffer, pos);
-        }
-
-        /* Now write the child DIEs starting with the layout and pointer type. */
-
-        // this works for interfaces, foreign types and classes, entry kind specifics are in the
-        // type units
-        pos = writeSkeletonClassLayout(context, classEntry, compiledMethodEntry, buffer, pos);
-
-        /* Write all compiled code locations */
-
-        pos = writeMethodLocation(context, classEntry, compiledMethodEntry, buffer, pos);
-
-        /* Write abstract inline methods. */
-
-        pos = writeAbstractInlineMethods(context, classEntry, compiledMethodEntry, buffer, pos);
-
-        /* if we opened a namespace then terminate its children */
-        if (!loaderId.isEmpty()) {
-            pos = writeAttrNull(buffer, pos);
-        }
-
-        /*
-         * Write a terminating null attribute.
-         */
-        pos = writeAttrNull(buffer, pos);
-
-        /* Fix up the CU length. */
-        patchLength(lengthPos, buffer, pos);
-        return pos;
-    }
-
-    private int writeSkeletonClassLayout(DebugContext context, ClassEntry classEntry, CompiledMethodEntry compiledMethodEntry, byte[] buffer, int p) {
-        int pos = p;
-        log(context, "  [0x%08x] class layout", pos);
-        AbbrevCode abbrevCode = AbbrevCode.CLASS_LAYOUT;
-        log(context, "  [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal());
-        pos = writeAbbrevCode(abbrevCode, buffer, pos);
-        String name = classEntry.getTypeName();
-        log(context, "  [0x%08x]     name  0x%x (%s)", pos, debugStringIndex(name), name);
-        pos = writeStrSectionOffset(name, buffer, pos);
-        log(context, "  [0x%08x]     declaration true", pos);
-        pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos);
-        long typeSignature = classEntry.getLayoutTypeSignature();
-        log(context, "  [0x%08x]     type specification 0x%x", pos, typeSignature);
-        pos = writeTypeSignature(typeSignature, buffer, pos);
-
-        pos = writeMethodDeclaration(context, classEntry, compiledMethodEntry.getPrimary().getMethodEntry(), false, buffer, pos);
-        /*
-         * Write a terminating null attribute.
-         */
-        return writeAttrNull(buffer, pos);
-    }
-
-    private int writeNameSpace(DebugContext context, String id, byte[] buffer, int p) {
-        int pos = p;
-        String name = uniqueDebugString(id);
-        assert !id.isEmpty();
-        log(context, "  [0x%08x] namespace %s", pos, name);
-        AbbrevCode abbrevCode = AbbrevCode.NAMESPACE;
-        log(context, "  [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal());
-        pos = writeAbbrevCode(abbrevCode, buffer, pos);
-        log(context, "  [0x%08x]     name  0x%x (%s)", pos, debugStringIndex(name), name);
-        pos = writeStrSectionOffset(name, buffer, pos);
-        return pos;
-    }
-
-    private int writeMethodDeclaration(DebugContext context, ClassEntry classEntry, MethodEntry method, boolean isInlined, byte[] buffer, int p) {
-        int pos = p;
-        String methodKey = method.getSymbolName();
-        String linkageName = uniqueDebugString(methodKey);
-        setMethodDeclarationIndex(method, pos);
-        int modifiers = method.getModifiers();
-        boolean isStatic = Modifier.isStatic(modifiers);
-        log(context, "  [0x%08x] method declaration %s::%s", pos, classEntry.getTypeName(), method.methodName());
-        AbbrevCode abbrevCode;
-        if (isInlined) {
-            abbrevCode = (isStatic ? AbbrevCode.METHOD_DECLARATION_INLINE_STATIC : AbbrevCode.METHOD_DECLARATION_INLINE);
-        } else {
-            abbrevCode = (isStatic ? AbbrevCode.METHOD_DECLARATION_STATIC : AbbrevCode.METHOD_DECLARATION);
-        }
-        log(context, "  [0x%08x] <2> Abbrev Number %d", pos, abbrevCode.ordinal());
-        pos = writeAbbrevCode(abbrevCode, buffer, pos);
-        if (isInlined) {
-            log(context, "  [0x%08x]     inline  0x%x", pos, DwarfInline.DW_INL_inlined.value());
-            pos = writeAttrInline(DwarfInline.DW_INL_inlined, buffer, pos);
-        }
-        log(context, "  [0x%08x]     external  true", pos);
-        pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos);
-        String name = uniqueDebugString(method.methodName());
-        log(context, "  [0x%08x]     name 0x%x (%s)", pos, debugStringIndex(name), name);
-        pos = writeStrSectionOffset(name, buffer, pos);
-        FileEntry fileEntry = method.getFileEntry();
-        if (fileEntry == null) {
-            fileEntry = classEntry.getFileEntry();
-        }
-        assert fileEntry != null;
-        int fileIdx = classEntry.getFileIdx(fileEntry);
-        log(context, "  [0x%08x]     file 0x%x (%s)", pos, fileIdx, fileEntry.getFullName());
-        pos = writeAttrData2((short) fileIdx, buffer, pos);
-        int line = method.getLine();
-        log(context, "  [0x%08x]     line 0x%x", pos, line);
-        pos = writeAttrData2((short) line, buffer, pos);
-        log(context, "  [0x%08x]     linkage_name %s", pos, linkageName);
-        pos = writeStrSectionOffset(linkageName, buffer, pos);
-        TypeEntry returnType = method.getValueType();
-        long retTypeSignature = returnType.getTypeSignature();
-        log(context, "  [0x%08x]     type 0x%x (%s)", pos, retTypeSignature, returnType.getTypeName());
-        pos = writeTypeSignature(retTypeSignature, buffer, pos);
-        log(context, "  [0x%08x]     artificial %s", pos, method.isDeopt() ? "true" : "false");
-        pos = writeFlag((method.isDeopt() ? DwarfFlag.DW_FLAG_true : DwarfFlag.DW_FLAG_false), buffer, pos);
-        log(context, "  [0x%08x]     accessibility %s", pos, "public");
-        pos = writeAttrAccessibility(modifiers, buffer, pos);
-        log(context, "  [0x%08x]     declaration true", pos);
-        pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos);
-        long typeSignature = classEntry.getLayoutTypeSignature();
-        log(context, "  [0x%08x]     containing_type 0x%x (%s)", pos, typeSignature, classEntry.getTypeName());
-        pos = writeTypeSignature(typeSignature, buffer, pos);
-        if (abbrevCode == AbbrevCode.METHOD_DECLARATION || abbrevCode == AbbrevCode.METHOD_DECLARATION_INLINE) {
-            /* Record the current position so we can back patch the object pointer. */
-            int objectPointerIndex = pos;
-            /*
-             * Write a dummy ref address to move pos on to where the first parameter gets written.
-             */
-            pos = writeAttrRef4(0, null, pos);
-            /*
-             * Now backpatch object pointer slot with current pos, identifying the first parameter.
-             */
-            log(context, "  [0x%08x]     object_pointer 0x%x", objectPointerIndex, pos);
-            writeAttrRef4(pos, buffer, objectPointerIndex);
-        }
-        /* Write method parameter declarations. */
-        pos = writeMethodParameterDeclarations(context, classEntry, method, fileIdx, 3, buffer, pos);
-        /* write method local declarations */
-        pos = writeMethodLocalDeclarations(context, classEntry, method, fileIdx, 3, buffer, pos);
-        /*
-         * Write a terminating null attribute.
-         */
-        return writeAttrNull(buffer, pos);
-    }
-
-    private int writeMethodParameterDeclarations(DebugContext context, ClassEntry classEntry, MethodEntry method, int fileIdx, int level, byte[] buffer, int p) {
-        int pos = p;
-        int refAddr;
-        if (!Modifier.isStatic(method.getModifiers())) {
-            refAddr = pos;
-            DebugLocalInfo paramInfo = method.getThisParam();
-            setMethodLocalIndex(classEntry, method, paramInfo, refAddr);
-            pos = writeMethodParameterDeclaration(context, paramInfo, fileIdx, true, level, buffer, pos);
-        }
-        for (int i = 0; i < method.getParamCount(); i++) {
-            refAddr = pos;
-            DebugLocalInfo paramInfo = method.getParam(i);
-            setMethodLocalIndex(classEntry, method, paramInfo, refAddr);
-            pos = writeMethodParameterDeclaration(context, paramInfo, fileIdx, false, level, buffer, pos);
-        }
-        return pos;
-    }
-
-    private int writeMethodParameterDeclaration(DebugContext context, DebugLocalInfo paramInfo, int fileIdx, boolean artificial, int level, byte[] buffer,
-                                                int p) {
-        int pos = p;
-        log(context, "  [0x%08x] method parameter declaration", pos);
-        AbbrevCode abbrevCode;
-        String paramName = paramInfo.name();
-        TypeEntry paramType = lookupType(paramInfo.valueType());
-        int line = paramInfo.line();
-        if (artificial) {
-            abbrevCode = AbbrevCode.METHOD_PARAMETER_DECLARATION_1;
-        } else if (line >= 0) {
-            abbrevCode = AbbrevCode.METHOD_PARAMETER_DECLARATION_2;
-        } else {
-            abbrevCode = AbbrevCode.METHOD_PARAMETER_DECLARATION_3;
-        }
-        log(context, "  [0x%08x] <%d> Abbrev Number %d", pos, level, abbrevCode.ordinal());
-        pos = writeAbbrevCode(abbrevCode, buffer, pos);
-        log(context, "  [0x%08x]     name %s", pos, paramName);
-        pos = writeStrSectionOffset(uniqueDebugString(paramName), buffer, pos);
-        if (abbrevCode == AbbrevCode.METHOD_PARAMETER_DECLARATION_2) {
-            log(context, "  [0x%08x]     file 0x%x", pos, fileIdx);
-            pos = writeAttrData2((short) fileIdx, buffer, pos);
-            log(context, "  [0x%08x]     line 0x%x", pos, line);
-            pos = writeAttrData2((short) line, buffer, pos);
-        }
-        long typeSignature = paramType.getTypeSignature();
-        log(context, "  [0x%08x]     type 0x%x (%s)", pos, typeSignature, paramType.getTypeName());
-        pos = writeTypeSignature(typeSignature, buffer, pos);
-        if (abbrevCode == AbbrevCode.METHOD_PARAMETER_DECLARATION_1) {
-            log(context, "  [0x%08x]     artificial true", pos);
-            pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos);
-        }
-        log(context, "  [0x%08x]     declaration true", pos);
-        pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos);
-        return pos;
-    }
-
-    private int writeMethodLocalDeclarations(DebugContext context, ClassEntry classEntry, MethodEntry method, int fileIdx, int level, byte[] buffer, int p) {
-        int pos = p;
-        int refAddr;
-        for (int i = 0; i < method.getLocalCount(); i++) {
-            refAddr = pos;
-            DebugLocalInfo localInfo = method.getLocal(i);
-            setMethodLocalIndex(classEntry, method, localInfo, refAddr);
-            pos = writeMethodLocalDeclaration(context, localInfo, fileIdx, level, buffer, pos);
-        }
-        return pos;
-    }
-
-    private int writeMethodLocalDeclaration(DebugContext context, DebugLocalInfo paramInfo, int fileIdx, int level, byte[] buffer,
-                                            int p) {
-        int pos = p;
-        log(context, "  [0x%08x] method local declaration", pos);
-        AbbrevCode abbrevCode;
-        String paramName = paramInfo.name();
-        TypeEntry paramType = lookupType(paramInfo.valueType());
-        int line = paramInfo.line();
-        if (line >= 0) {
-            abbrevCode = AbbrevCode.METHOD_LOCAL_DECLARATION_1;
-        } else {
-            abbrevCode = AbbrevCode.METHOD_LOCAL_DECLARATION_2;
-        }
-        log(context, "  [0x%08x] <%d> Abbrev Number %d", pos, level, abbrevCode.ordinal());
-        pos = writeAbbrevCode(abbrevCode, buffer, pos);
-        log(context, "  [0x%08x]     name %s", pos, paramName);
-        pos = writeStrSectionOffset(uniqueDebugString(paramName), buffer, pos);
-        if (abbrevCode == AbbrevCode.METHOD_LOCAL_DECLARATION_1) {
-            log(context, "  [0x%08x]     file 0x%x", pos, fileIdx);
-            pos = writeAttrData2((short) fileIdx, buffer, pos);
-            log(context, "  [0x%08x]     line 0x%x", pos, line);
-            pos = writeAttrData2((short) line, buffer, pos);
-        }
-        long typeSignature = paramType.getTypeSignature();
-        log(context, "  [0x%08x]     type 0x%x (%s)", pos, typeSignature, paramType.getTypeName());
-        pos = writeTypeSignature(typeSignature, buffer, pos);
-        log(context, "  [0x%08x]     declaration true", pos);
-        pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos);
-        return pos;
-    }
-
-    private int writeMethodLocation(DebugContext context, ClassEntry classEntry, CompiledMethodEntry compiledEntry, byte[] buffer, int p) {
-        int pos = p;
-        Range primary = compiledEntry.getPrimary();
-        log(context, "  [0x%08x] method location", pos);
-        AbbrevCode abbrevCode = AbbrevCode.METHOD_LOCATION;
-        log(context, "  [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal());
-        pos = writeAbbrevCode(abbrevCode, buffer, pos);
-        log(context, "  [0x%08x]     lo_pc  0x%08x", pos, primary.getLo());
-        pos = writeLong(primary.getLo(), buffer, pos);
-        log(context, "  [0x%08x]     hi_pc  0x%08x", pos, primary.getHi());
-        pos = writeLong(primary.getHi(), buffer, pos);
-        /*
-         * Should pass true only if method is non-private.
-         */
-        log(context, "  [0x%08x]     external  true", pos);
-        pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos);
-        String methodKey = primary.getSymbolName();
-        int methodSpecOffset = getMethodDeclarationIndex(primary.getMethodEntry());
-        log(context, "  [0x%08x]     specification  0x%x (%s)", pos, methodSpecOffset, methodKey);
-        pos = writeInfoSectionOffset(methodSpecOffset, buffer, pos);
-        HashMap<DebugLocalInfo, List<SubRange>> varRangeMap = primary.getVarRangeMap();
-        pos = writeMethodParameterLocations(context, classEntry, varRangeMap, primary, 2, buffer, pos);
-        pos = writeMethodLocalLocations(context, classEntry, varRangeMap, primary, 2, buffer, pos);
-        if (primary.includesInlineRanges()) {
-            /*
-             * the method has inlined ranges so write concrete inlined method entries as its
-             * children
-             */
-            pos = generateConcreteInlinedMethods(context, classEntry, compiledEntry, buffer, pos);
-        }
-        /*
-         * Write a terminating null attribute.
-         */
-        return writeAttrNull(buffer, pos);
-    }
-
-    private int writeMethodParameterLocations(DebugContext context, ClassEntry classEntry, HashMap<DebugLocalInfo, List<SubRange>> varRangeMap, Range range, int depth, byte[] buffer, int p) {
-        int pos = p;
-        MethodEntry methodEntry;
-        if (range.isPrimary()) {
-            methodEntry = range.getMethodEntry();
-        } else {
-            assert !range.isLeaf() : "should only be looking up var ranges for inlined calls";
-            methodEntry = range.getFirstCallee().getMethodEntry();
-        }
-        if (!Modifier.isStatic(methodEntry.getModifiers())) {
-            DebugLocalInfo thisParamInfo = methodEntry.getThisParam();
-            int refAddr = getMethodLocalIndex(classEntry, methodEntry, thisParamInfo);
-            List<SubRange> ranges = varRangeMap.get(thisParamInfo);
-            pos = writeMethodLocalLocation(context, range, thisParamInfo, refAddr, ranges, depth, true, buffer, pos);
-        }
-        for (int i = 0; i < methodEntry.getParamCount(); i++) {
-            DebugLocalInfo paramInfo = methodEntry.getParam(i);
-            int refAddr = getMethodLocalIndex(classEntry, methodEntry, paramInfo);
-            List<SubRange> ranges = varRangeMap.get(paramInfo);
-            pos = writeMethodLocalLocation(context, range, paramInfo, refAddr, ranges, depth, true, buffer, pos);
-        }
-        return pos;
-    }
-
-    private int writeMethodLocalLocations(DebugContext context, ClassEntry classEntry, HashMap<DebugLocalInfo, List<SubRange>> varRangeMap, Range range, int depth, byte[] buffer, int p) {
-        int pos = p;
-        MethodEntry methodEntry;
-        if (range.isPrimary()) {
-            methodEntry = range.getMethodEntry();
-        } else {
-            assert !range.isLeaf() : "should only be looking up var ranges for inlined calls";
-            methodEntry = range.getFirstCallee().getMethodEntry();
-        }
-        int count = methodEntry.getLocalCount();
-        for (int i = 0; i < count; i++) {
-            DebugLocalInfo localInfo = methodEntry.getLocal(i);
-            int refAddr = getMethodLocalIndex(classEntry, methodEntry, localInfo);
-            List<SubRange> ranges = varRangeMap.get(localInfo);
-            pos = writeMethodLocalLocation(context, range, localInfo, refAddr, ranges, depth, false, buffer, pos);
-        }
-        return pos;
-    }
-
-    private int writeMethodLocalLocation(DebugContext context, Range range, DebugLocalInfo localInfo, int refAddr, List<SubRange> ranges, int depth, boolean isParam, byte[] buffer,
-                                         int p) {
-        int pos = p;
-        log(context, "  [0x%08x] method %s location %s:%s", pos, (isParam ? "parameter" : "local"), localInfo.name(), localInfo.typeName());
-        List<DebugLocalValueInfo> localValues = new ArrayList<>();
-        for (SubRange subrange : ranges) {
-            DebugLocalValueInfo value = subrange.lookupValue(localInfo);
-            if (value != null) {
-                log(context, "  [0x%08x]     local  %s:%s [0x%x, 0x%x] = %s", pos, value.name(), value.typeName(), subrange.getLo(), subrange.getHi(), formatValue(value));
-                switch (value.localKind()) {
-                    case REGISTER:
-                    case STACKSLOT:
-                        localValues.add(value);
-                        break;
-                    case CONSTANT:
-                        JavaConstant constant = value.constantValue();
-                        // can only handle primitive or null constants just now
-                        if (constant instanceof PrimitiveConstant || constant.getJavaKind() == JavaKind.Object) {
-                            localValues.add(value);
-                        }
-                        break;
-                    default:
-                        break;
-                }
-            }
-        }
-        AbbrevCode abbrevCode;
-        if (localValues.isEmpty()) {
-            abbrevCode = (isParam ? AbbrevCode.METHOD_PARAMETER_LOCATION_1 : AbbrevCode.METHOD_LOCAL_LOCATION_1);
-        } else {
-            abbrevCode = (isParam ? AbbrevCode.METHOD_PARAMETER_LOCATION_2 : AbbrevCode.METHOD_LOCAL_LOCATION_2);
-        }
-        log(context, "  [0x%08x] <%d> Abbrev Number %d", pos, depth, abbrevCode.ordinal());
-        pos = writeAbbrevCode(abbrevCode, buffer, pos);
-        log(context, "  [0x%08x]     specification  0x%x", pos, refAddr);
-        pos = writeInfoSectionOffset(refAddr, buffer, pos);
-        if (abbrevCode == AbbrevCode.METHOD_LOCAL_LOCATION_2 ||
-                abbrevCode == AbbrevCode.METHOD_PARAMETER_LOCATION_2) {
-            int locRefOffset = getRangeLocalIndex(range, localInfo);
-            log(context, "  [0x%08x]     loc list  0x%x", pos, locRefOffset);
-            pos = writeULEB(locRefOffset, buffer, pos);
-        }
-        return pos;
-    }
-
-    /**
-     * Go through the subranges and generate concrete debug entries for inlined methods.
-     */
-    private int generateConcreteInlinedMethods(DebugContext context, ClassEntry classEntry, CompiledMethodEntry compiledEntry, byte[] buffer, int p) {
-        Range primary = compiledEntry.getPrimary();
-        if (primary.isLeaf()) {
-            return p;
-        }
-        int pos = p;
-        log(context, "  [0x%08x] concrete entries [0x%x,0x%x] %s", pos, primary.getLo(), primary.getHi(), primary.getFullMethodName());
-        int depth = 0;
-        Iterator<SubRange> iterator = compiledEntry.topDownRangeIterator();
-        while (iterator.hasNext()) {
-            SubRange subrange = iterator.next();
-            if (subrange.isLeaf()) {
-                // we only generate concrete methods for non-leaf entries
-                continue;
-            }
-            // if we just stepped out of a child range write nulls for each step up
-            while (depth > subrange.getDepth()) {
-                pos = writeAttrNull(buffer, pos);
-                depth--;
-            }
-            depth = subrange.getDepth();
-            pos = writeInlineSubroutine(context, classEntry, subrange, depth + 2, buffer, pos);
-            HashMap<DebugLocalInfo, List<SubRange>> varRangeMap = subrange.getVarRangeMap();
-            // increment depth to account for parameter and method locations
-            depth++;
-            pos = writeMethodParameterLocations(context, classEntry, varRangeMap, subrange, depth + 2, buffer, pos);
-            pos = writeMethodLocalLocations(context, classEntry, varRangeMap, subrange, depth + 2, buffer, pos);
-        }
-        // if we just stepped out of a child range write nulls for each step up
-        while (depth > 0) {
-            pos = writeAttrNull(buffer, pos);
-            depth--;
-        }
-        return pos;
-    }
-
-    private int writeInlineSubroutine(DebugContext context, ClassEntry classEntry, SubRange caller, int depth, byte[] buffer, int p) {
-        assert !caller.isLeaf();
-        // the supplied range covers an inline call and references the caller method entry. its
-        // child ranges all reference the same inlined called method. leaf children cover code for
-        // that inlined method. non-leaf children cover code for recursively inlined methods.
-        // identify the inlined method by looking at the first callee
-        Range callee = caller.getFirstCallee();
-        MethodEntry methodEntry = callee.getMethodEntry();
-        String methodKey = methodEntry.getSymbolName();
-        /* the abstract index was written in the method's class entry */
-        int abstractOriginIndex = getAbstractInlineMethodIndex(classEntry, methodEntry);
-
-        int pos = p;
-        log(context, "  [0x%08x] concrete inline subroutine [0x%x, 0x%x] %s", pos, caller.getLo(), caller.getHi(), methodKey);
-
-        int callLine = caller.getLine();
-        assert callLine >= -1 : callLine;
-        int fileIndex;
-        if (callLine == -1) {
-            log(context, "  Unable to retrieve call line for inlined method %s", callee.getFullMethodName());
-            /* continue with line 0 and fileIndex 1 as we must insert a tree node */
-            callLine = 0;
-            fileIndex = classEntry.getFileIdx();
-        } else {
-            FileEntry subFileEntry = caller.getFileEntry();
-            if (subFileEntry != null) {
-                fileIndex = classEntry.getFileIdx(subFileEntry);
-            } else {
-                log(context, "  Unable to retrieve caller FileEntry for inlined method %s (caller method %s)", callee.getFullMethodName(), caller.getFullMethodName());
-                fileIndex = classEntry.getFileIdx();
-            }
-        }
-        final AbbrevCode abbrevCode = AbbrevCode.INLINED_SUBROUTINE_WITH_CHILDREN;
-        log(context, "  [0x%08x] <%d> Abbrev Number %d", pos, depth, abbrevCode.ordinal());
-        pos = writeAbbrevCode(abbrevCode, buffer, pos);
-        log(context, "  [0x%08x]     abstract_origin  0x%x", pos, abstractOriginIndex);
-        pos = writeAttrRef4(abstractOriginIndex, buffer, pos);
-        log(context, "  [0x%08x]     lo_pc  0x%08x", pos, caller.getLo());
-        pos = writeLong(caller.getLo(), buffer, pos);
-        log(context, "  [0x%08x]     hi_pc  0x%08x", pos, caller.getHi());
-        pos = writeLong(caller.getHi(), buffer, pos);
-        log(context, "  [0x%08x]     call_file  %d", pos, fileIndex);
-        pos = writeAttrData4(fileIndex, buffer, pos);
-        log(context, "  [0x%08x]     call_line  %d", pos, callLine);
-        pos = writeAttrData4(callLine, buffer, pos);
-        return pos;
-    }
-
-    private int writeAbstractInlineMethods(DebugContext context, ClassEntry classEntry, CompiledMethodEntry compiledMethodEntry, byte[] buffer, int p) {
-        EconomicSet<MethodEntry> inlinedMethods = collectInlinedMethods(context, compiledMethodEntry, p);
-        int pos = p;
-        for (MethodEntry methodEntry : inlinedMethods) {
-            // n.b. class entry used to index the method belongs to the inlining method
-            // not the inlined method
-            setAbstractInlineMethodIndex(classEntry, methodEntry, pos);
-            // we need the full method declaration for inlined methods
-            pos = writeMethodDeclaration(context, classEntry, methodEntry, true, buffer, pos);
-            //writeAbstractInlineMethod(context, classEntry, methodEntry, buffer, pos);
-        }
-        return pos;
-    }
-
-    private EconomicSet<MethodEntry> collectInlinedMethods(DebugContext context, CompiledMethodEntry compiledMethodEntry, int p) {
-        final EconomicSet<MethodEntry> methods = EconomicSet.create();
-        addInlinedMethods(context, compiledMethodEntry, compiledMethodEntry.getPrimary(), methods, p);
-        return methods;
-    }
-
-    private void addInlinedMethods(DebugContext context, CompiledMethodEntry compiledEntry, Range primary, EconomicSet<MethodEntry> hashSet, int p) {
-        if (primary.isLeaf()) {
-            return;
-        }
-        verboseLog(context, "  [0x%08x] collect abstract inlined methods %s", p, primary.getFullMethodName());
-        Iterator<SubRange> iterator = compiledEntry.topDownRangeIterator();
-        while (iterator.hasNext()) {
-            SubRange subrange = iterator.next();
-            if (subrange.isLeaf()) {
-                // we only generate abstract inline methods for non-leaf entries
-                continue;
-            }
-            // the subrange covers an inline call and references the caller method entry. its
-            // child ranges all reference the same inlined called method. leaf children cover code
-            // for
-            // that inlined method. non-leaf children cover code for recursively inlined methods.
-            // identify the inlined method by looking at the first callee
-            Range callee = subrange.getFirstCallee();
-            MethodEntry methodEntry = callee.getMethodEntry();
-            if (hashSet.add(methodEntry)) {
-                verboseLog(context, "  [0x%08x]   add abstract inlined method %s", p, methodEntry.getSymbolName());
-            }
-        }
-    }
-
-    private int writeAbstractInlineMethod(DebugContext context, ClassEntry classEntry, MethodEntry method, byte[] buffer, int p) {
-        int pos = p;
-        log(context, "  [0x%08x] abstract inline method %s::%s", pos, classEntry.getTypeName(), method.methodName());
-        AbbrevCode abbrevCode = AbbrevCode.NULL;
-        log(context, "  [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal());
-        pos = writeAbbrevCode(abbrevCode, buffer, pos);
-        log(context, "  [0x%08x]     inline  0x%x", pos, DwarfInline.DW_INL_inlined.value());
-        pos = writeAttrInline(DwarfInline.DW_INL_inlined, buffer, pos);
-        /*
-         * Should pass true only if method is non-private.
-         */
-        log(context, "  [0x%08x]     external  true", pos);
-        pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos);
-        int methodSpecOffset = getMethodDeclarationIndex(method);
-        log(context, "  [0x%08x]     specification  0x%x", pos, methodSpecOffset);
-        pos = writeInfoSectionOffset(methodSpecOffset, buffer, pos);
-        FileEntry fileEntry = method.getFileEntry();
-        if (fileEntry == null) {
-            fileEntry = classEntry.getFileEntry();
-        }
-        assert fileEntry != null;
-        int fileIdx = classEntry.getFileIdx(fileEntry);
-        int level = 3;
-        // n.b. class entry used to index the params belongs to the inlining method
-        // not the inlined method
-        pos = writeMethodParameterDeclarations(context, classEntry, method, fileIdx, level, buffer, pos);
-        pos = writeMethodLocalDeclarations(context, classEntry, method, fileIdx, level, buffer, pos);
-        /*
-         * Write a terminating null attribute.
-         */
-        return writeAttrNull(buffer, pos);
-    }
-
-    private int writeAttrRef4(int reference, byte[] buffer, int p) {
-        // make sure we have actually started writing a CU
-        assert unitStart >= 0;
-        // writes a CU-relative offset
-        return writeAttrData4(reference - unitStart, buffer, p);
-    }
-
-    private int writeCUHeader(byte[] buffer, int p) {
-        int pos = p;
-        unitStart = pos;
-        /* CU length. */
-        pos = writeInt(0, buffer, pos);
-        /* DWARF version. */
-        pos = writeDwarfVersion(DwarfVersion.DW_VERSION_5, buffer, pos);
-        /* unit type */
-        pos = writeDwarfUnitHeader(DwarfUnitHeader.DW_UT_compile, buffer, pos);
-        /* Address size. */
-        pos = writeByte((byte) 8, buffer, pos);
-        /* Abbrev offset. */
-        return writeAbbrevSectionOffset(0, buffer, pos);
-    }
-
-    @SuppressWarnings("unused")
-    public int writeAttrString(String value, byte[] buffer, int p) {
-        return writeUTF8StringBytes(value, buffer, p);
-    }
-
-    public int writeAttrLanguage(DwarfLanguage language, byte[] buffer, int p) {
-        return writeByte(language.value(), buffer, p);
-    }
-
-    public int writeAttrEncoding(DwarfEncoding encoding, byte[] buffer, int p) {
-        return writeByte(encoding.value(), buffer, p);
-    }
-
-    public int writeAttrInline(DwarfInline inline, byte[] buffer, int p) {
-        return writeByte(inline.value(), buffer, p);
-    }
-
-    public int writeAttrAccessibility(int modifiers, byte[] buffer, int p) {
-        DwarfAccess access;
-        if (Modifier.isPublic(modifiers)) {
-            access = DwarfAccess.DW_ACCESS_public;
-        } else if (Modifier.isProtected(modifiers)) {
-            access = DwarfAccess.DW_ACCESS_protected;
-        } else if (Modifier.isPrivate(modifiers)) {
-            access = DwarfAccess.DW_ACCESS_private;
-        } else {
-            /* Actually package private -- make it public for now. */
-            access = DwarfAccess.DW_ACCESS_public;
-        }
-        return writeByte(access.value(), buffer, p);
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfLineSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfLineSectionImpl.java
deleted file mode 100644
index f89f5f4d29fd..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfLineSectionImpl.java
+++ /dev/null
@@ -1,827 +0,0 @@
-/*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.dwarf;
-
-import com.oracle.objectfile.LayoutDecision;
-import com.oracle.objectfile.LayoutDecisionMap;
-import com.oracle.objectfile.ObjectFile;
-import com.oracle.objectfile.debugentry.ClassEntry;
-import com.oracle.objectfile.debugentry.CompiledMethodEntry;
-import com.oracle.objectfile.debugentry.DirEntry;
-import com.oracle.objectfile.debugentry.FileEntry;
-import com.oracle.objectfile.debugentry.range.Range;
-import com.oracle.objectfile.debugentry.range.SubRange;
-import com.oracle.objectfile.elf.dwarf.DwarfSectionImpl;
-import com.oracle.objectfile.elf.dwarf.constants.DwarfLineOpcode;
-import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName;
-import com.oracle.objectfile.elf.dwarf.constants.DwarfVersion;
-import jdk.graal.compiler.debug.DebugContext;
-
-import java.util.Iterator;
-import java.util.Map;
-
-/**
- * Section generator for debug_line section.
- */
-public class RuntimeDwarfLineSectionImpl extends RuntimeDwarfSectionImpl {
-    /**
-     * 0 is used to indicate an invalid opcode.
-     */
-    private static final int LN_undefined = 0;
-
-    /**
-     * Line header section always contains fixed number of bytes.
-     */
-    private static final int LN_HEADER_SIZE = 28;
-    /**
-     * Current generator follows C++ with line base -5.
-     */
-    private static final int LN_LINE_BASE = -5;
-    /**
-     * Current generator follows C++ with line range 14 giving full range -5 to 8.
-     */
-    private static final int LN_LINE_RANGE = 14;
-    /**
-     * Current generator uses opcode base of 13 which must equal DW_LNS_set_isa + 1.
-     */
-    private static final int LN_OPCODE_BASE = 13;
-
-    RuntimeDwarfLineSectionImpl(RuntimeDwarfDebugInfo dwarfSections) {
-        // line section depends on string section
-        super(dwarfSections, DwarfSectionName.DW_LINE_SECTION, DwarfSectionName.DW_STR_SECTION);
-    }
-
-    private int linePrologueSize;
-
-    @Override
-    public void createContent() {
-        assert !contentByteArrayCreated();
-
-        /*
-         * We need to create a header, dir table, file table and line number table encoding for each
-         * class CU that contains compiled methods.
-         */
-
-        CompiledMethodEntry compiledEntry = compiledMethod();
-        setLineIndex(compiledEntry.getClassEntry(), 0);
-        int headerSize = headerSize();
-        int dirTableSize = computeDirTableSize(compiledEntry);
-        int fileTableSize = computeFileTableSize(compiledEntry);
-        linePrologueSize = headerSize + dirTableSize + fileTableSize;
-        // mark the start of the line table for this entry
-        int lineNumberTableSize = computeLineNumberTableSize(compiledEntry);
-        int totalSize = linePrologueSize + lineNumberTableSize;
-
-        byte[] buffer = new byte[totalSize];
-        super.setContent(buffer);
-    }
-
-    private static int headerSize() {
-        /*
-         * Header size is standard 31 bytes:
-         *
-         * <ul>
-         *
-         * <li><code>uint32 total_length</code>
-         *
-         * <li><code>uint16 version</code>
-         *
-         * <li><code>uint32 header_length</code>
-         *
-         * <li><code>uint8 min_insn_length</code>
-         *
-         * <li><code>uint8 max_operations_per_instruction</code>
-         *
-         * <li><code>uint8 default_is_stmt</code>
-         *
-         * <li><code>int8 line_base</code>
-         *
-         * <li><code>uint8 line_range</code>
-         *
-         * <li><code>uint8 opcode_base</code>
-         *
-         * <li><code>uint8[opcode_base-1] standard_opcode_lengths</code>
-         *
-         * </ul>
-         */
-
-        return LN_HEADER_SIZE;
-    }
-
-    private int computeDirTableSize(CompiledMethodEntry compiledEntry) {
-        /*
-         * Table contains a sequence of 'nul'-terminated UTF8 dir name bytes followed by an extra
-         * 'nul'.
-         */
-        // DirEntry dirEntry = compiledEntry.getPrimary().getFileEntry().getDirEntry();
-        // int length = countUTF8Bytes(dirEntry.getPathString()) + 1;
-        // assert length > 1; // no empty strings
-
-
-        Cursor cursor = new Cursor();
-        compiledEntry.getClassEntry().dirStream().forEachOrdered(dirEntry -> {
-            int length = countUTF8Bytes(dirEntry.getPathString());
-            // We should never have a null or zero length entry in local dirs
-            assert length > 0;
-            cursor.add(length + 1);
-        });
-        /*
-         * Allow for terminator nul.
-         */
-        cursor.add(1);
-        return cursor.get();
-    }
-
-    private int computeFileTableSize(CompiledMethodEntry compiledEntry) {
-        /*
-         * Table contains a sequence of file entries followed by an extra 'nul'
-         *
-         * each file entry consists of a 'nul'-terminated UTF8 file name, a dir entry idx and two 0
-         * time stamps
-         */
-        // TODO also check subranges
-//        FileEntry fileEntry = compiledEntry.getPrimary().getFileEntry();
-//        int length = countUTF8Bytes(fileEntry.getFileName()) + 1;
-//        assert length > 1;
-//        // The dir index gets written as a ULEB
-//        int dirIdx = 1;
-//        length += writeULEB(dirIdx, scratch, 0);
-//        // The two zero timestamps require 1 byte each
-//        length += 2;
-
-        ClassEntry classEntry = compiledEntry.getClassEntry();
-
-        Cursor cursor = new Cursor();
-        classEntry.fileStream().forEachOrdered(fileEntry -> {
-            // We want the file base name excluding path.
-            String baseName = fileEntry.getFileName();
-            int length = countUTF8Bytes(baseName);
-            // We should never have a null or zero length entry in local files.
-            assert length > 0;
-            cursor.add(length + 1);
-            // The dir index gets written as a ULEB
-            int dirIdx = classEntry.getDirIdx(fileEntry);
-            cursor.add(writeULEB(dirIdx, scratch, 0));
-            // The two zero timestamps require 1 byte each
-            cursor.add(2);
-        });
-        /*
-         * Allow for terminator nul.
-         */
-        cursor.add(1);
-        return cursor.get();
-    }
-
-    @Override
-    public byte[] getOrDecideContent(Map<ObjectFile.Element, LayoutDecisionMap> alreadyDecided, byte[] contentHint) {
-        ObjectFile.Element textElement = getElement().getOwner().elementForName(".text");
-        LayoutDecisionMap decisionMap = alreadyDecided.get(textElement);
-        if (decisionMap != null) {
-            Object valueObj = decisionMap.getDecidedValue(LayoutDecision.Kind.VADDR);
-            if (valueObj != null && valueObj instanceof Number) {
-                /*
-                 * This may not be the final vaddr for the text segment but it will be close enough
-                 * to make debug easier i.e. to within a 4k page or two.
-                 */
-                debugTextBase = ((Number) valueObj).longValue();
-            }
-        }
-        return super.getOrDecideContent(alreadyDecided, contentHint);
-    }
-
-    @Override
-    public void writeContent(DebugContext context) {
-        assert contentByteArrayCreated();
-
-        byte[] buffer = getContent();
-        int pos = 0;
-
-        CompiledMethodEntry compiledEntry = compiledMethod();
-
-        enableLog(context, pos);
-        log(context, "  [0x%08x] DEBUG_LINE", pos);
-        setLineIndex(compiledEntry.getClassEntry(), pos);
-        int lengthPos = pos;
-        pos = writeHeader(buffer, pos);
-        log(context, "  [0x%08x] headerSize = 0x%08x", pos, pos);
-        int dirTablePos = pos;
-        pos = writeDirTable(context, compiledEntry, buffer, pos);
-        log(context, "  [0x%08x] dirTableSize = 0x%08x", pos, pos - dirTablePos);
-        int fileTablePos = pos;
-        pos = writeFileTable(context, compiledEntry, buffer, pos);
-        log(context, "  [0x%08x] fileTableSize = 0x%08x", pos, pos - fileTablePos);
-        int lineNumberTablePos = pos;
-        pos = writeLineNumberTable(context, compiledEntry, buffer, pos);
-        log(context, "  [0x%08x] lineNumberTableSize = 0x%x", pos, pos - lineNumberTablePos);
-        log(context, "  [0x%08x] size = 0x%x", pos, pos - lengthPos);
-        patchLength(lengthPos, buffer, pos);
-        assert pos == buffer.length;
-    }
-
-    private int writeHeader(byte[] buffer, int p) {
-        int pos = p;
-        /*
-         * write dummy 4 ubyte length field.
-         */
-        pos = writeInt(0, buffer, pos);
-        /*
-         * 2 ubyte version is always 2.
-         */
-        pos = writeDwarfVersion(DwarfVersion.DW_VERSION_4, buffer, pos);
-        /*
-         * 4 ubyte prologue length includes rest of header and dir + file table section.
-         */
-        int prologueSize = linePrologueSize - (4 + 2 + 4);
-        pos = writeInt(prologueSize, buffer, pos);
-        /*
-         * 1 ubyte min instruction length is always 1.
-         */
-        pos = writeByte((byte) 1, buffer, pos);
-        /*
-         * 1 ubyte max operations per instruction is always 1.
-         */
-        pos = writeByte((byte) 1, buffer, pos);
-        /*
-         * 1 byte default is_stmt is always 1.
-         */
-        pos = writeByte((byte) 1, buffer, pos);
-        /*
-         * 1 byte line base is always -5.
-         */
-        pos = writeByte((byte) LN_LINE_BASE, buffer, pos);
-        /*
-         * 1 ubyte line range is always 14 giving range -5 to 8.
-         */
-        pos = writeByte((byte) LN_LINE_RANGE, buffer, pos);
-        /*
-         * 1 ubyte opcode base is always 13.
-         */
-        pos = writeByte((byte) LN_OPCODE_BASE, buffer, pos);
-        /*
-         * specify opcode arg sizes for the standard opcodes.
-         */
-        /* DW_LNS_copy */
-        writeByte((byte) 0, buffer, pos);
-        /* DW_LNS_advance_pc */
-        writeByte((byte) 1, buffer, pos + 1);
-        /* DW_LNS_advance_line */
-        writeByte((byte) 1, buffer, pos + 2);
-        /* DW_LNS_set_file */
-        writeByte((byte) 1, buffer, pos + 3);
-        /* DW_LNS_set_column */
-        writeByte((byte) 1, buffer, pos + 4);
-        /* DW_LNS_negate_stmt */
-        writeByte((byte) 0, buffer, pos + 5);
-        /* DW_LNS_set_basic_block */
-        writeByte((byte) 0, buffer, pos + 6);
-        /* DW_LNS_const_add_pc */
-        writeByte((byte) 0, buffer, pos + 7);
-        /* DW_LNS_fixed_advance_pc */
-        writeByte((byte) 1, buffer, pos + 8);
-        /* DW_LNS_set_prologue_end */
-        writeByte((byte) 0, buffer, pos + 9);
-        /* DW_LNS_set_epilogue_begin */
-        writeByte((byte) 0, buffer, pos + 10);
-        /* DW_LNS_set_isa */
-        pos = writeByte((byte) 1, buffer, pos + 11);
-        return pos;
-    }
-
-    private int writeDirTable(DebugContext context, CompiledMethodEntry compiledEntry, byte[] buffer, int p) {
-        verboseLog(context, "  [0x%08x] Dir Name", p);
-        /*
-         * Write out the list of dirs
-         */
-//        int pos = p;
-//        int idx = 1;
-//
-//        // TODO: foreach compiledEntry.dirs() - also check subranges
-//        DirEntry dirEntry = compiledEntry.getPrimary().getMethodEntry().getFileEntry().getDirEntry();
-//        String dirPath = dirEntry.getPathString();
-//        pos = writeUTF8StringBytes(dirPath, buffer, pos);
-//        idx++;
-
-        ClassEntry classEntry = compiledEntry.getClassEntry();
-
-        Cursor cursor = new Cursor(p);
-        Cursor idx = new Cursor(1);
-        classEntry.dirStream().forEach(dirEntry -> {
-            int dirIdx = idx.get();
-            assert (classEntry.getDirIdx(dirEntry) == dirIdx);
-            String dirPath = dirEntry.getPathString();
-            verboseLog(context, "  [0x%08x] %-4d %s", cursor.get(), dirIdx, dirPath);
-            cursor.set(writeUTF8StringBytes(dirPath, buffer, cursor.get()));
-            idx.add(1);
-        });
-        /*
-         * Separate dirs from files with a nul.
-         */
-        cursor.set(writeByte((byte) 0, buffer, cursor.get()));
-        return cursor.get();
-    }
-
-    private int writeFileTable(DebugContext context, CompiledMethodEntry compiledEntry, byte[] buffer, int p) {
-        verboseLog(context, "  [0x%08x] Entry Dir  Name", p);
-        /*
-         * Write out the list of files
-         */
-//        int pos = p;
-//        int idx = 1;
-//
-//        // TODO: foreach compiledEntry.files() - also check subranges
-//
-//        FileEntry fileEntry = compiledEntry.getPrimary().getMethodEntry().getFileEntry();
-//        String baseName = fileEntry.getFileName();
-//        pos = writeUTF8StringBytes(baseName, buffer, pos);
-//        pos = writeULEB(1, buffer, pos);  // TODO set correct dir index, if more than 1 dir in subranges
-//        pos = writeULEB(0, buffer, pos);
-//        pos = writeULEB(0, buffer, pos);
-//        idx++;
-
-        ClassEntry classEntry = compiledEntry.getClassEntry();
-
-        Cursor cursor = new Cursor(p);
-        Cursor idx = new Cursor(1);
-        classEntry.fileStream().forEach(fileEntry -> {
-            int pos = cursor.get();
-            int fileIdx = idx.get();
-            assert classEntry.getFileIdx(fileEntry) == fileIdx;
-            int dirIdx = classEntry.getDirIdx(fileEntry);
-            String baseName = fileEntry.getFileName();
-            verboseLog(context, "  [0x%08x] %-5d %-5d %s", pos, fileIdx, dirIdx, baseName);
-            pos = writeUTF8StringBytes(baseName, buffer, pos);
-            pos = writeULEB(dirIdx, buffer, pos);
-            pos = writeULEB(0, buffer, pos);
-            pos = writeULEB(0, buffer, pos);
-            cursor.set(pos);
-            idx.add(1);
-        });
-        /*
-         * Terminate files with a nul.
-         */
-        cursor.set(writeByte((byte) 0, buffer, cursor.get()));
-        return cursor.get();
-    }
-
-    private long debugLine = 1;
-    private int debugCopyCount = 0;
-
-    private int writeCompiledMethodLineInfo(DebugContext context, CompiledMethodEntry compiledEntry, byte[] buffer, int p) {
-        int pos = p;
-        Range primaryRange = compiledEntry.getPrimary();
-        ClassEntry classEntry = compiledEntry.getClassEntry();
-        // the compiled method might be a substitution and not in the file of the class entry
-        FileEntry fileEntry = primaryRange.getFileEntry();
-        if (fileEntry == null) {
-            log(context, "  [0x%08x] primary range [0x%08x, 0x%08x] skipped (no file) %s", pos, debugTextBase + primaryRange.getLo(), debugTextBase + primaryRange.getHi(),
-                            primaryRange.getFullMethodNameWithParams());
-            return pos;
-        }
-        String file = fileEntry.getFileName();
-        int fileIdx = classEntry.getFileIdx(fileEntry);
-        /*
-         * Each primary represents a method i.e. a contiguous sequence of subranges. For normal
-         * methods we expect the first leaf range to start at offset 0 covering the method prologue.
-         * In that case we can rely on it to set the initial file, line and address for the state
-         * machine. Otherwise we need to default the initial state and copy it to the file.
-         */
-        long line = primaryRange.getLine();
-        long address = primaryRange.getLo();
-        Range prologueRange = prologueLeafRange(compiledEntry);
-        if (prologueRange != null) {
-            // use the line for the range and use its file if available
-            line = prologueRange.getLine();
-            if (line > 0) {
-                FileEntry firstFileEntry = prologueRange.getFileEntry();
-                if (firstFileEntry != null) {
-                    fileIdx = classEntry.getFileIdx(firstFileEntry);
-                }
-            }
-        }
-        if (line < 0) {
-            // never emit a negative line
-            line = 0;
-        }
-
-        /*
-         * Set state for primary.
-         */
-        log(context, "  [0x%08x] primary range [0x%08x, 0x%08x] %s %s:%d", pos, debugTextBase + primaryRange.getLo(), debugTextBase + primaryRange.getHi(),
-                        primaryRange.getFullMethodNameWithParams(),
-                        file, primaryRange.getLine());
-
-        /*
-         * Initialize and write a row for the start of the compiled method.
-         */
-        pos = writeSetFileOp(context, file, fileIdx, buffer, pos);
-        pos = writeSetBasicBlockOp(context, buffer, pos);
-        /*
-         * Address is currently at offset 0.
-         */
-        pos = writeSetAddressOp(context, address, buffer, pos);
-        /*
-         * State machine value of line is currently 1 increment to desired line.
-         */
-        if (line != 1) {
-            pos = writeAdvanceLineOp(context, line - 1, buffer, pos);
-        }
-        pos = writeCopyOp(context, buffer, pos);
-
-        /*
-         * Now write a row for each subrange lo and hi.
-         */
-        Iterator<SubRange> iterator = compiledEntry.leafRangeIterator();
-        if (prologueRange != null) {
-            // skip already processed range
-            SubRange first = iterator.next();
-            assert first == prologueRange;
-        }
-        while (iterator.hasNext()) {
-            SubRange subrange = iterator.next();
-            assert subrange.getLo() >= primaryRange.getLo();
-            assert subrange.getHi() <= primaryRange.getHi();
-            FileEntry subFileEntry = subrange.getFileEntry();
-            if (subFileEntry == null) {
-                continue;
-            }
-            String subfile = subFileEntry.getFileName();
-            int subFileIdx = classEntry.getFileIdx(subFileEntry);
-            assert subFileIdx > 0;
-            long subLine = subrange.getLine();
-            long subAddressLo = subrange.getLo();
-            long subAddressHi = subrange.getHi();
-            log(context, "  [0x%08x] sub range [0x%08x, 0x%08x] %s %s:%d", pos, debugTextBase + subAddressLo, debugTextBase + subAddressHi, subrange.getFullMethodNameWithParams(), subfile,
-                            subLine);
-            if (subLine < 0) {
-                /*
-                 * No line info so stay at previous file:line.
-                 */
-                subLine = line;
-                subfile = file;
-                subFileIdx = fileIdx;
-                verboseLog(context, "  [0x%08x] missing line info - staying put at %s:%d", pos, file, line);
-            }
-            /*
-             * There is a temptation to append end sequence at here when the hiAddress lies strictly
-             * between the current address and the start of the next subrange because, ostensibly,
-             * we have void space between the end of the current subrange and the start of the next
-             * one. however, debug works better if we treat all the insns up to the next range start
-             * as belonging to the current line.
-             *
-             * If we have to update to a new file then do so.
-             */
-            if (subFileIdx != fileIdx) {
-                /*
-                 * Update the current file.
-                 */
-                pos = writeSetFileOp(context, subfile, subFileIdx, buffer, pos);
-                file = subfile;
-                fileIdx = subFileIdx;
-            }
-            long lineDelta = subLine - line;
-            long addressDelta = subAddressLo - address;
-            /*
-             * Check if we can advance line and/or address in one byte with a special opcode.
-             */
-            byte opcode = isSpecialOpcode(addressDelta, lineDelta);
-            if (opcode != LN_undefined) {
-                /*
-                 * Ignore pointless write when addressDelta == lineDelta == 0.
-                 */
-                if (addressDelta != 0 || lineDelta != 0) {
-                    pos = writeSpecialOpcode(context, opcode, buffer, pos);
-                }
-            } else {
-                /*
-                 * Does it help to divide and conquer using a fixed address increment.
-                 */
-                int remainder = isConstAddPC(addressDelta);
-                if (remainder > 0) {
-                    pos = writeConstAddPCOp(context, buffer, pos);
-                    /*
-                     * The remaining address can be handled with a special opcode but what about the
-                     * line delta.
-                     */
-                    opcode = isSpecialOpcode(remainder, lineDelta);
-                    if (opcode != LN_undefined) {
-                        /*
-                         * Address remainder and line now fit.
-                         */
-                        pos = writeSpecialOpcode(context, opcode, buffer, pos);
-                    } else {
-                        /*
-                         * Ok, bump the line separately then use a special opcode for the address
-                         * remainder.
-                         */
-                        opcode = isSpecialOpcode(remainder, 0);
-                        assert opcode != LN_undefined;
-                        pos = writeAdvanceLineOp(context, lineDelta, buffer, pos);
-                        pos = writeSpecialOpcode(context, opcode, buffer, pos);
-                    }
-                } else {
-                    /*
-                     * Increment line and pc separately.
-                     */
-                    if (lineDelta != 0) {
-                        pos = writeAdvanceLineOp(context, lineDelta, buffer, pos);
-                    }
-                    /*
-                     * n.b. we might just have had an out of range line increment with a zero
-                     * address increment.
-                     */
-                    if (addressDelta > 0) {
-                        /*
-                         * See if we can use a ushort for the increment.
-                         */
-                        if (isFixedAdvancePC(addressDelta)) {
-                            pos = writeFixedAdvancePCOp(context, (short) addressDelta, buffer, pos);
-                        } else {
-                            pos = writeAdvancePCOp(context, addressDelta, buffer, pos);
-                        }
-                    }
-                    pos = writeCopyOp(context, buffer, pos);
-                }
-            }
-            /*
-             * Move line and address range on.
-             */
-            line += lineDelta;
-            address += addressDelta;
-        }
-        /*
-         * Append a final end sequence just below the next primary range.
-         */
-        if (address < primaryRange.getHi()) {
-            long addressDelta = primaryRange.getHi() - address;
-            /*
-             * Increment address before we write the end sequence.
-             */
-            pos = writeAdvancePCOp(context, addressDelta, buffer, pos);
-        }
-        pos = writeEndSequenceOp(context, buffer, pos);
-
-        return pos;
-    }
-
-    private int computeLineNumberTableSize(CompiledMethodEntry compiledEntry) {
-        /*
-         * Sigh -- we have to do this by generating the content even though we cannot write it into
-         * a byte[].
-         */
-        return writeLineNumberTable(null, compiledEntry, null, 0);
-    }
-
-    private int writeLineNumberTable(DebugContext context, CompiledMethodEntry compiledEntry, byte[] buffer, int p) {
-        int pos = p;
-        String methodName = compiledEntry.getPrimary().getFullMethodNameWithParams();
-        String fileName = compiledEntry.getClassEntry().getTypeName();
-        log(context, "  [0x%08x] %s %s", pos, methodName, fileName);
-        pos = writeCompiledMethodLineInfo(context, compiledEntry, buffer, pos);
-        return pos;
-    }
-
-    private static SubRange prologueLeafRange(CompiledMethodEntry compiledEntry) {
-        Iterator<SubRange> iterator = compiledEntry.leafRangeIterator();
-        if (iterator.hasNext()) {
-            SubRange range = iterator.next();
-            if (range.getLo() == compiledEntry.getPrimary().getLo()) {
-                return range;
-            }
-        }
-        return null;
-    }
-
-    private int writeCopyOp(DebugContext context, byte[] buffer, int p) {
-        DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNS_copy;
-        int pos = p;
-        debugCopyCount++;
-        verboseLog(context, "  [0x%08x] Copy %d", pos, debugCopyCount);
-        return writeLineOpcode(opcode, buffer, pos);
-    }
-
-    private int writeAdvancePCOp(DebugContext context, long uleb, byte[] buffer, int p) {
-        DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNS_advance_pc;
-        int pos = p;
-        debugAddress += uleb;
-        verboseLog(context, "  [0x%08x] Advance PC by %d to 0x%08x", pos, uleb, debugAddress);
-        pos = writeLineOpcode(opcode, buffer, pos);
-        return writeULEB(uleb, buffer, pos);
-    }
-
-    private int writeAdvanceLineOp(DebugContext context, long sleb, byte[] buffer, int p) {
-        DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNS_advance_line;
-        int pos = p;
-        debugLine += sleb;
-        verboseLog(context, "  [0x%08x] Advance Line by %d to %d", pos, sleb, debugLine);
-        pos = writeLineOpcode(opcode, buffer, pos);
-        return writeSLEB(sleb, buffer, pos);
-    }
-
-    private int writeSetFileOp(DebugContext context, String file, long uleb, byte[] buffer, int p) {
-        DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNS_set_file;
-        int pos = p;
-        verboseLog(context, "  [0x%08x] Set File Name to entry %d in the File Name Table (%s)", pos, uleb, file);
-        pos = writeLineOpcode(opcode, buffer, pos);
-        return writeULEB(uleb, buffer, pos);
-    }
-
-    @SuppressWarnings("unused")
-    private int writeSetColumnOp(DebugContext context, long uleb, byte[] buffer, int p) {
-        DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNS_set_column;
-        int pos = p;
-        pos = writeLineOpcode(opcode, buffer, pos);
-        return writeULEB(uleb, buffer, pos);
-    }
-
-    @SuppressWarnings("unused")
-    private int writeNegateStmtOp(DebugContext context, byte[] buffer, int p) {
-        DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNS_negate_stmt;
-        int pos = p;
-        return writeLineOpcode(opcode, buffer, pos);
-    }
-
-    private int writeSetBasicBlockOp(DebugContext context, byte[] buffer, int p) {
-        DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNS_set_basic_block;
-        int pos = p;
-        verboseLog(context, "  [0x%08x] Set basic block", pos);
-        return writeLineOpcode(opcode, buffer, pos);
-    }
-
-    private int writeConstAddPCOp(DebugContext context, byte[] buffer, int p) {
-        DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNS_const_add_pc;
-        int pos = p;
-        int advance = opcodeAddress((byte) 255);
-        debugAddress += advance;
-        verboseLog(context, "  [0x%08x] Advance PC by constant %d to 0x%08x", pos, advance, debugAddress);
-        return writeLineOpcode(opcode, buffer, pos);
-    }
-
-    private int writeFixedAdvancePCOp(DebugContext context, short arg, byte[] buffer, int p) {
-        DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNS_fixed_advance_pc;
-        int pos = p;
-        debugAddress += arg;
-        verboseLog(context, "  [0x%08x] Fixed advance Address by %d to 0x%08x", pos, arg, debugAddress);
-        pos = writeLineOpcode(opcode, buffer, pos);
-        return writeShort(arg, buffer, pos);
-    }
-
-    private int writeEndSequenceOp(DebugContext context, byte[] buffer, int p) {
-        DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNE_end_sequence;
-        int pos = p;
-        verboseLog(context, "  [0x%08x] Extended opcode 1: End sequence", pos);
-        debugAddress = debugTextBase;
-        debugLine = 1;
-        debugCopyCount = 0;
-        pos = writePrefixOpcode(buffer, pos);
-        /*
-         * Insert extended insn byte count as ULEB.
-         */
-        pos = writeULEB(1, buffer, pos);
-        return writeLineOpcode(opcode, buffer, pos);
-    }
-
-    private int writeSetAddressOp(DebugContext context, long arg, byte[] buffer, int p) {
-        DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNE_set_address;
-        int pos = p;
-        debugAddress = debugTextBase + (int) arg;
-        verboseLog(context, "  [0x%08x] Extended opcode 2: Set Address to 0x%08x", pos, debugAddress);
-        pos = writePrefixOpcode(buffer, pos);
-        /*
-         * Insert extended insn byte count as ULEB.
-         */
-        pos = writeULEB(9, buffer, pos);
-        pos = writeLineOpcode(opcode, buffer, pos);
-        return writeLong(arg, buffer, pos);
-    }
-
-    @SuppressWarnings("unused")
-    private int writeDefineFileOp(DebugContext context, String file, long uleb1, long uleb2, long uleb3, byte[] buffer, int p) {
-        DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNE_define_file;
-        int pos = p;
-        /*
-         * Calculate bytes needed for opcode + args.
-         */
-        int fileBytes = countUTF8Bytes(file) + 1;
-        long insnBytes = 1;
-        insnBytes += fileBytes;
-        insnBytes += writeULEB(uleb1, scratch, 0);
-        insnBytes += writeULEB(uleb2, scratch, 0);
-        insnBytes += writeULEB(uleb3, scratch, 0);
-        verboseLog(context, "  [0x%08x] Extended opcode 3: Define File %s idx %d ts1 %d ts2 %d", pos, file, uleb1, uleb2, uleb3);
-        pos = writePrefixOpcode(buffer, pos);
-        /*
-         * Insert insn length as uleb.
-         */
-        pos = writeULEB(insnBytes, buffer, pos);
-        /*
-         * Insert opcode and args.
-         */
-        pos = writeLineOpcode(opcode, buffer, pos);
-        pos = writeUTF8StringBytes(file, buffer, pos);
-        pos = writeULEB(uleb1, buffer, pos);
-        pos = writeULEB(uleb2, buffer, pos);
-        return writeULEB(uleb3, buffer, pos);
-    }
-
-    private int writePrefixOpcode(byte[] buffer, int p) {
-        return writeLineOpcode(DwarfLineOpcode.DW_LNS_extended_prefix, buffer, p);
-    }
-
-    private int writeLineOpcode(DwarfLineOpcode opcode, byte[] buffer, int p) {
-        return writeByte(opcode.value(), buffer, p);
-    }
-
-    private static int opcodeId(byte opcode) {
-        int iopcode = opcode & 0xff;
-        return iopcode - LN_OPCODE_BASE;
-    }
-
-    private static int opcodeAddress(byte opcode) {
-        int iopcode = opcode & 0xff;
-        return (iopcode - LN_OPCODE_BASE) / LN_LINE_RANGE;
-    }
-
-    private static int opcodeLine(byte opcode) {
-        int iopcode = opcode & 0xff;
-        return ((iopcode - LN_OPCODE_BASE) % LN_LINE_RANGE) + LN_LINE_BASE;
-    }
-
-    private int writeSpecialOpcode(DebugContext context, byte opcode, byte[] buffer, int p) {
-        int pos = p;
-        if (debug && opcode == 0) {
-            verboseLog(context, "  [0x%08x] ERROR Special Opcode %d: Address 0x%08x Line %d", debugAddress, debugLine);
-        }
-        debugAddress += opcodeAddress(opcode);
-        debugLine += opcodeLine(opcode);
-        verboseLog(context, "  [0x%08x] Special Opcode %d: advance Address by %d to 0x%08x and Line by %d to %d",
-                        pos, opcodeId(opcode), opcodeAddress(opcode), debugAddress, opcodeLine(opcode), debugLine);
-        return writeByte(opcode, buffer, pos);
-    }
-
-    private static final int MAX_ADDRESS_ONLY_DELTA = (0xff - LN_OPCODE_BASE) / LN_LINE_RANGE;
-    private static final int MAX_ADDPC_DELTA = MAX_ADDRESS_ONLY_DELTA + (MAX_ADDRESS_ONLY_DELTA - 1);
-
-    private static byte isSpecialOpcode(long addressDelta, long lineDelta) {
-        if (addressDelta < 0) {
-            return LN_undefined;
-        }
-        if (lineDelta >= LN_LINE_BASE) {
-            long offsetLineDelta = lineDelta - LN_LINE_BASE;
-            if (offsetLineDelta < LN_LINE_RANGE) {
-                /*
-                 * The line delta can be encoded. Check if address is ok.
-                 */
-                if (addressDelta <= MAX_ADDRESS_ONLY_DELTA) {
-                    long opcode = LN_OPCODE_BASE + (addressDelta * LN_LINE_RANGE) + offsetLineDelta;
-                    if (opcode <= 255) {
-                        return (byte) opcode;
-                    }
-                }
-            }
-        }
-
-        /*
-         * Answer no by returning an invalid opcode.
-         */
-        return LN_undefined;
-    }
-
-    private static int isConstAddPC(long addressDelta) {
-        if (addressDelta < MAX_ADDRESS_ONLY_DELTA) {
-            return 0;
-        }
-        if (addressDelta <= MAX_ADDPC_DELTA) {
-            return (int) (addressDelta - MAX_ADDRESS_ONLY_DELTA);
-        } else {
-            return 0;
-        }
-    }
-
-    private static boolean isFixedAdvancePC(long addressDiff) {
-        return addressDiff >= 0 && addressDiff < 0xffff;
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfLocSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfLocSectionImpl.java
deleted file mode 100644
index b45e505a185c..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfLocSectionImpl.java
+++ /dev/null
@@ -1,648 +0,0 @@
-/*
- * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2022, 2022, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.dwarf;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import com.oracle.objectfile.BuildDependency;
-import com.oracle.objectfile.LayoutDecision;
-import com.oracle.objectfile.LayoutDecisionMap;
-import com.oracle.objectfile.ObjectFile;
-import com.oracle.objectfile.debugentry.ClassEntry;
-import com.oracle.objectfile.debugentry.CompiledMethodEntry;
-import com.oracle.objectfile.debugentry.range.Range;
-import com.oracle.objectfile.debugentry.range.SubRange;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalValueInfo;
-import com.oracle.objectfile.elf.ELFMachine;
-import com.oracle.objectfile.elf.ELFObjectFile;
-import com.oracle.objectfile.elf.dwarf.constants.DwarfExpressionOpcode;
-import com.oracle.objectfile.elf.dwarf.constants.DwarfLocationListEntry;
-import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName;
-import com.oracle.objectfile.elf.dwarf.constants.DwarfVersion;
-
-import jdk.graal.compiler.debug.DebugContext;
-import jdk.vm.ci.aarch64.AArch64;
-import jdk.vm.ci.amd64.AMD64;
-import jdk.vm.ci.meta.JavaConstant;
-import jdk.vm.ci.meta.JavaKind;
-import jdk.vm.ci.meta.PrimitiveConstant;
-
-/**
- * Section generator for debug_loc section.
- */
-public class RuntimeDwarfLocSectionImpl extends RuntimeDwarfSectionImpl {
-
-    /*
-     * array used to map compiler register indices to the indices expected by DWARF
-     */
-    private int[] dwarfRegMap;
-
-    /*
-     * index used by DWARF for the stack pointer register
-     */
-    private int dwarfStackRegister;
-
-    private static final LayoutDecision.Kind[] targetLayoutKinds = {
-            LayoutDecision.Kind.CONTENT,
-            LayoutDecision.Kind.SIZE,
-            /* Add this so we can use the text section base address for debug. */
-            LayoutDecision.Kind.VADDR};
-
-    public RuntimeDwarfLocSectionImpl(RuntimeDwarfDebugInfo dwarfSections) {
-        // debug_loc section depends on text section
-        super(dwarfSections, DwarfSectionName.DW_LOCLISTS_SECTION, DwarfSectionName.TEXT_SECTION, targetLayoutKinds);
-        initDwarfRegMap();
-    }
-
-    @Override
-    public Set<BuildDependency> getDependencies(Map<ObjectFile.Element, LayoutDecisionMap> decisions) {
-        Set<BuildDependency> deps = super.getDependencies(decisions);
-        LayoutDecision ourContent = decisions.get(getElement()).getDecision(LayoutDecision.Kind.CONTENT);
-        /*
-         * Order all content decisions after all size decisions by making loc section content depend
-         * on abbrev section size.
-         */
-        String abbrevSectionName = dwarfSections.getAbbrevSectionImpl().getSectionName();
-        ELFObjectFile.ELFSection abbrevSection = (ELFObjectFile.ELFSection) getElement().getOwner().elementForName(abbrevSectionName);
-        LayoutDecision sizeDecision = decisions.get(abbrevSection).getDecision(LayoutDecision.Kind.SIZE);
-        deps.add(BuildDependency.createOrGet(ourContent, sizeDecision));
-        return deps;
-    }
-
-    @Override
-    public void createContent() {
-        assert !contentByteArrayCreated();
-
-        int len = generateContent(null, null);
-
-        byte[] buffer = new byte[len];
-        super.setContent(buffer);
-    }
-
-    @Override
-    public void writeContent(DebugContext context) {
-        assert contentByteArrayCreated();
-
-        byte[] buffer = getContent();
-        int size = buffer.length;
-        int pos = 0;
-
-        enableLog(context, pos);
-        log(context, "  [0x%08x] DEBUG_LOC", pos);
-        log(context, "  [0x%08x] size = 0x%08x", pos, size);
-
-        pos = generateContent(context, buffer);
-        assert pos == size;
-    }
-
-    private int generateContent(DebugContext context, byte[] buffer) {
-        Cursor cursor = new Cursor();
-        CompiledMethodEntry compiledMethodEntry = compiledMethod();
-
-
-        List<LocationListEntry> locationListEntries = getLocationListEntries(compiledMethodEntry);
-        int entryCount = locationListEntries.size();
-
-        int lengthPos = cursor.get();
-        cursor.set(writeLocationListsHeader(entryCount, buffer, cursor.get()));
-
-        int baseOffset = cursor.get();
-        setLocationListIndex(compiledMethodEntry.getClassEntry(), baseOffset);
-        cursor.add(entryCount * 4);  // space for offset array
-
-        int index = 0;
-        for (LocationListEntry entry : locationListEntries) {
-            setRangeLocalIndex(entry.range(), entry.local(), index);
-            writeInt(cursor.get() - baseOffset, buffer, baseOffset + index * 4);
-            index++;
-            cursor.set(writeVarLocations(context, entry.local(), entry.base(), entry.rangeList(), buffer, cursor.get()));
-        }
-
-        /* Fix up location list length */
-        patchLength(lengthPos, buffer, cursor.get());
-
-        return cursor.get();
-    }
-
-    private int writeLocationListsHeader(int offsetEntries, byte[] buffer, int p) {
-        int pos = p;
-        /* Loclists length. */
-        pos = writeInt(0, buffer, pos);
-        /* DWARF version. */
-        pos = writeDwarfVersion(DwarfVersion.DW_VERSION_5, buffer, pos);
-        /* Address size. */
-        pos = writeByte((byte) 8, buffer, pos);
-        /* Segment selector size. */
-        pos = writeByte((byte) 0, buffer, pos);
-        /* Offset entry count */
-        return writeInt(offsetEntries, buffer, pos);
-    }
-
-    private record LocationListEntry(Range range, long base, DebugLocalInfo local, List<SubRange> rangeList) {
-    }
-
-    private List<LocationListEntry> getLocationListEntries(CompiledMethodEntry compiledMethodEntry) {
-
-        Range primary = compiledMethodEntry.getPrimary();
-        /*
-         * Note that offsets are written relative to the primary range base. This requires
-         * writing a base address entry before each of the location list ranges. It is possible
-         * to default the base address to the low_pc value of the compile unit for the compiled
-         * method's owning class, saving two words per location list. However, that forces the
-         * debugger to do a lot more up-front cross-referencing of CUs when it needs to resolve
-         * code addresses e.g. to set a breakpoint, leading to a very slow response for the
-         * user.
-         */
-        long base = primary.getLo();
-        // location list entries for primary range
-        List<LocationListEntry> locationListEntries = new ArrayList<>(getRangeLocationListEntries(primary, base));
-        // location list entries for inlined calls
-        if (!primary.isLeaf()) {
-            Iterator<SubRange> iterator = compiledMethodEntry.topDownRangeIterator();
-            while (iterator.hasNext()) {
-                SubRange subrange = iterator.next();
-                if (subrange.isLeaf()) {
-                    continue;
-                }
-                locationListEntries.addAll(getRangeLocationListEntries(subrange, base));
-            }
-        }
-        return locationListEntries;
-    }
-
-    private List<LocationListEntry> getRangeLocationListEntries(Range range, long base) {
-        List<LocationListEntry> locationListEntries = new ArrayList<>();
-
-        for (Map.Entry<DebugLocalInfo, List<SubRange>> entry : range.getVarRangeMap().entrySet()) {
-            if (!entry.getValue().isEmpty()) {
-                locationListEntries.add(new LocationListEntry(range, base, entry.getKey(), entry.getValue()));
-            }
-        }
-
-        return locationListEntries;
-    }
-
-    private int writeVarLocations(DebugContext context, DebugLocalInfo local, long base, List<SubRange> rangeList, byte[] buffer, int p) {
-        assert !rangeList.isEmpty();
-        int pos = p;
-        // collect ranges and values, merging adjacent ranges that have equal value
-        List<LocalValueExtent> extents = LocalValueExtent.coalesce(local, rangeList);
-
-        // write start of primary range as base address - see comment above for reasons why
-        // we choose ot do this rather than use the relevant compile unit low_pc
-        pos = writeLocationListEntry(DwarfLocationListEntry.DW_LLE_base_address, buffer, pos);
-        pos = writeLong(base, buffer, pos);
-        // write ranges as offsets from base
-        for (LocalValueExtent extent : extents) {
-            DebugLocalValueInfo value = extent.value;
-            assert (value != null);
-            log(context, "  [0x%08x]     local  %s:%s [0x%x, 0x%x] = %s", pos, value.name(), value.typeName(), extent.getLo(), extent.getHi(), formatValue(value));
-            pos = writeLocationListEntry(DwarfLocationListEntry.DW_LLE_offset_pair, buffer, pos);
-            pos = writeULEB(extent.getLo() - base, buffer, pos);
-            pos = writeULEB(extent.getHi() - base, buffer, pos);
-            switch (value.localKind()) {
-                case REGISTER:
-                    pos = writeRegisterLocation(context, value.regIndex(), buffer, pos);
-                    break;
-                case STACKSLOT:
-                    pos = writeStackLocation(context, value.stackSlot(), buffer, pos);
-                    break;
-                case CONSTANT:
-                    JavaConstant constant = value.constantValue();
-                    if (constant instanceof PrimitiveConstant) {
-                        pos = writePrimitiveConstantLocation(context, value.constantValue(), buffer, pos);
-                    } else if (constant.isNull()) {
-                        pos = writeNullConstantLocation(context, value.constantValue(), buffer, pos);
-                    } else {
-                        pos = writeObjectConstantLocation(context, value.constantValue(), value.heapOffset(), buffer, pos);
-                    }
-                    break;
-                default:
-                    assert false : "Should not reach here!";
-                    break;
-            }
-        }
-        // write list terminator
-        return writeLocationListEntry(DwarfLocationListEntry.DW_LLE_end_of_list, buffer, pos);
-    }
-
-    private int writeRegisterLocation(DebugContext context, int regIndex, byte[] buffer, int p) {
-        int targetIdx = mapToDwarfReg(regIndex);
-        assert targetIdx >= 0;
-        int pos = p;
-        if (targetIdx < 0x20) {
-            // can write using DW_OP_reg<n>
-            int byteCount = 1;
-            byte reg = (byte) targetIdx;
-            pos = writeULEB(byteCount, buffer, pos);
-            pos = writeExprOpcodeReg(reg, buffer, pos);
-            verboseLog(context, "  [0x%08x]     REGOP count %d op 0x%x", pos, byteCount, DwarfExpressionOpcode.DW_OP_reg0.value() + reg);
-        } else {
-            // have to write using DW_OP_regx + LEB operand
-            assert targetIdx < 128 : "unexpectedly high reg index!";
-            int byteCount = 2;
-            pos = writeULEB(byteCount, buffer, pos);
-            pos = writeExprOpcode(DwarfExpressionOpcode.DW_OP_regx, buffer, pos);
-            pos = writeULEB(targetIdx, buffer, pos);
-            verboseLog(context, "  [0x%08x]     REGOP count %d op 0x%x reg %d", pos, byteCount, DwarfExpressionOpcode.DW_OP_regx.value(), targetIdx);
-            // byte count and target idx written as ULEB should fit in one byte
-            assert pos == p + 3 : "wrote the wrong number of bytes!";
-        }
-        return pos;
-    }
-
-    private int writeStackLocation(DebugContext context, int offset, byte[] buffer, int p) {
-        int pos = p;
-        int byteCount = 0;
-        byte sp = (byte) getDwarfStackRegister();
-        int patchPos = pos;
-        pos = writeULEB(byteCount, buffer, pos);
-        int zeroPos = pos;
-        if (sp < 0x20) {
-            // fold the base reg index into the op
-            pos = writeExprOpcodeBReg(sp, buffer, pos);
-        } else {
-            pos = writeExprOpcode(DwarfExpressionOpcode.DW_OP_bregx, buffer, pos);
-            // pass base reg index as a ULEB operand
-            pos = writeULEB(sp, buffer, pos);
-        }
-        pos = writeSLEB(offset, buffer, pos);
-        // now backpatch the byte count
-        byteCount = (pos - zeroPos);
-        writeULEB(byteCount, buffer, patchPos);
-        if (sp < 0x20) {
-            verboseLog(context, "  [0x%08x]     STACKOP count %d op 0x%x offset %d", pos, byteCount, (DwarfExpressionOpcode.DW_OP_breg0.value() + sp), 0 - offset);
-        } else {
-            verboseLog(context, "  [0x%08x]     STACKOP count %d op 0x%x reg %d offset %d", pos, byteCount, DwarfExpressionOpcode.DW_OP_bregx.value(), sp, 0 - offset);
-        }
-        return pos;
-    }
-
-    private int writePrimitiveConstantLocation(DebugContext context, JavaConstant constant, byte[] buffer, int p) {
-        assert constant instanceof PrimitiveConstant;
-        int pos = p;
-        DwarfExpressionOpcode op = DwarfExpressionOpcode.DW_OP_implicit_value;
-        JavaKind kind = constant.getJavaKind();
-        int dataByteCount = kind.getByteCount();
-        // total bytes is op + uleb + dataByteCount
-        int byteCount = 1 + 1 + dataByteCount;
-        pos = writeULEB(byteCount, buffer, pos);
-        pos = writeExprOpcode(op, buffer, pos);
-        pos = writeULEB(dataByteCount, buffer, pos);
-        if (dataByteCount == 1) {
-            if (kind == JavaKind.Boolean) {
-                pos = writeByte((byte) (constant.asBoolean() ? 1 : 0), buffer, pos);
-            } else {
-                pos = writeByte((byte) constant.asInt(), buffer, pos);
-            }
-        } else if (dataByteCount == 2) {
-            pos = writeShort((short) constant.asInt(), buffer, pos);
-        } else if (dataByteCount == 4) {
-            int i = (kind == JavaKind.Int ? constant.asInt() : Float.floatToRawIntBits(constant.asFloat()));
-            pos = writeInt(i, buffer, pos);
-        } else {
-            long l = (kind == JavaKind.Long ? constant.asLong() : Double.doubleToRawLongBits(constant.asDouble()));
-            pos = writeLong(l, buffer, pos);
-        }
-        verboseLog(context, "  [0x%08x]     CONSTANT (primitive) %s", pos, constant.toValueString());
-        return pos;
-    }
-
-    private int writeNullConstantLocation(DebugContext context, JavaConstant constant, byte[] buffer, int p) {
-        assert constant.isNull();
-        int pos = p;
-        DwarfExpressionOpcode op = DwarfExpressionOpcode.DW_OP_implicit_value;
-        int dataByteCount = 8;
-        // total bytes is op + uleb + dataByteCount
-        int byteCount = 1 + 1 + dataByteCount;
-        pos = writeULEB(byteCount, buffer, pos);
-        pos = writeExprOpcode(op, buffer, pos);
-        pos = writeULEB(dataByteCount, buffer, pos);
-        pos = writeAttrData8(0, buffer, pos);
-        verboseLog(context, "  [0x%08x]     CONSTANT (null) %s", pos, constant.toValueString());
-        return pos;
-    }
-
-    private int writeObjectConstantLocation(DebugContext context, JavaConstant constant, long heapOffset, byte[] buffer, int p) {
-        assert constant.getJavaKind() == JavaKind.Object && !constant.isNull();
-        int pos = p;
-        pos = writeHeapLocationLocList(heapOffset, buffer, pos);
-        verboseLog(context, "  [0x%08x]     CONSTANT (object) %s", pos, constant.toValueString());
-        return pos;
-    }
-
-    // auxiliary class used to collect per-range locations for a given local
-    // merging adjacent ranges with the same location
-    static class LocalValueExtent {
-        long lo;
-        long hi;
-        DebugLocalValueInfo value;
-
-        LocalValueExtent(long lo, long hi, DebugLocalValueInfo value) {
-            this.lo = lo;
-            this.hi = hi;
-            this.value = value;
-        }
-
-        @SuppressWarnings("unused")
-        boolean shouldMerge(long otherLo, long otherHi, DebugLocalValueInfo otherValue) {
-            // ranges need to be contiguous to merge
-            if (hi != otherLo) {
-                return false;
-            }
-            return value.equals(otherValue);
-        }
-
-        private LocalValueExtent maybeMerge(long otherLo, long otherHi, DebugLocalValueInfo otherValue) {
-            if (shouldMerge(otherLo, otherHi, otherValue)) {
-                // We can extend the current extent to cover the next one.
-                this.hi = otherHi;
-                return null;
-            } else {
-                // we need a new extent
-                return new LocalValueExtent(otherLo, otherHi, otherValue);
-            }
-        }
-
-        public long getLo() {
-            return lo;
-        }
-
-        public long getHi() {
-            return hi;
-        }
-
-        public DebugLocalValueInfo getValue() {
-            return value;
-        }
-
-        public static List<LocalValueExtent> coalesce(DebugLocalInfo local, List<SubRange> rangeList) {
-            List<LocalValueExtent> extents = new ArrayList<>();
-            LocalValueExtent current = null;
-            for (SubRange range : rangeList) {
-                if (current == null) {
-                    current = new LocalValueExtent(range.getLo(), range.getHi(), range.lookupValue(local));
-                    extents.add(current);
-                } else {
-                    LocalValueExtent toAdd = current.maybeMerge(range.getLo(), range.getHi(), range.lookupValue(local));
-                    if (toAdd != null) {
-                        extents.add(toAdd);
-                        current = toAdd;
-                    }
-                }
-            }
-            return extents;
-        }
-    }
-
-    private int getDwarfStackRegister() {
-        return dwarfStackRegister;
-    }
-
-    private int mapToDwarfReg(int regIdx) {
-        if (regIdx < 0) {
-            throw new AssertionError("Requesting dwarf register number for negative register index");
-        }
-        if (regIdx >= dwarfRegMap.length) {
-            throw new AssertionError("Register index " + regIdx + " exceeds map range " + dwarfRegMap.length);
-        }
-        int dwarfRegNum = dwarfRegMap[regIdx];
-        if (dwarfRegNum < 0) {
-            throw new AssertionError("Register index " + regIdx + " does not map to valid dwarf register number");
-        }
-        return dwarfRegNum;
-    }
-
-    private void initDwarfRegMap() {
-        if (dwarfSections.elfMachine == ELFMachine.AArch64) {
-            dwarfRegMap = graalToDwarfRegMap(DwarfRegEncodingAArch64.values());
-            dwarfStackRegister = DwarfRegEncodingAArch64.SP.getDwarfEncoding();
-        } else {
-            assert dwarfSections.elfMachine == ELFMachine.X86_64 : "must be";
-            dwarfRegMap = graalToDwarfRegMap(DwarfRegEncodingAMD64.values());
-            dwarfStackRegister = DwarfRegEncodingAMD64.RSP.getDwarfEncoding();
-        }
-    }
-
-    private interface DwarfRegEncoding {
-
-        int getDwarfEncoding();
-
-        int getGraalEncoding();
-    }
-
-    // Register numbers used by DWARF for AArch64 registers encoded
-    // along with their respective GraalVM compiler number.
-    public enum DwarfRegEncodingAArch64 implements DwarfRegEncoding {
-        R0(0, AArch64.r0.number),
-        R1(1, AArch64.r1.number),
-        R2(2, AArch64.r2.number),
-        R3(3, AArch64.r3.number),
-        R4(4, AArch64.r4.number),
-        R5(5, AArch64.r5.number),
-        R6(6, AArch64.r6.number),
-        R7(7, AArch64.r7.number),
-        R8(8, AArch64.r8.number),
-        R9(9, AArch64.r9.number),
-        R10(10, AArch64.r10.number),
-        R11(11, AArch64.r11.number),
-        R12(12, AArch64.r12.number),
-        R13(13, AArch64.r13.number),
-        R14(14, AArch64.r14.number),
-        R15(15, AArch64.r15.number),
-        R16(16, AArch64.r16.number),
-        R17(17, AArch64.r17.number),
-        R18(18, AArch64.r18.number),
-        R19(19, AArch64.r19.number),
-        R20(20, AArch64.r20.number),
-        R21(21, AArch64.r21.number),
-        R22(22, AArch64.r22.number),
-        R23(23, AArch64.r23.number),
-        R24(24, AArch64.r24.number),
-        R25(25, AArch64.r25.number),
-        R26(26, AArch64.r26.number),
-        R27(27, AArch64.r27.number),
-        R28(28, AArch64.r28.number),
-        R29(29, AArch64.r29.number),
-        R30(30, AArch64.r30.number),
-        R31(31, AArch64.r31.number),
-        ZR(31, AArch64.zr.number),
-        SP(31, AArch64.sp.number),
-        V0(64, AArch64.v0.number),
-        V1(65, AArch64.v1.number),
-        V2(66, AArch64.v2.number),
-        V3(67, AArch64.v3.number),
-        V4(68, AArch64.v4.number),
-        V5(69, AArch64.v5.number),
-        V6(70, AArch64.v6.number),
-        V7(71, AArch64.v7.number),
-        V8(72, AArch64.v8.number),
-        V9(73, AArch64.v9.number),
-        V10(74, AArch64.v10.number),
-        V11(75, AArch64.v11.number),
-        V12(76, AArch64.v12.number),
-        V13(77, AArch64.v13.number),
-        V14(78, AArch64.v14.number),
-        V15(79, AArch64.v15.number),
-        V16(80, AArch64.v16.number),
-        V17(81, AArch64.v17.number),
-        V18(82, AArch64.v18.number),
-        V19(83, AArch64.v19.number),
-        V20(84, AArch64.v20.number),
-        V21(85, AArch64.v21.number),
-        V22(86, AArch64.v22.number),
-        V23(87, AArch64.v23.number),
-        V24(88, AArch64.v24.number),
-        V25(89, AArch64.v25.number),
-        V26(90, AArch64.v26.number),
-        V27(91, AArch64.v27.number),
-        V28(92, AArch64.v28.number),
-        V29(93, AArch64.v29.number),
-        V30(94, AArch64.v30.number),
-        V31(95, AArch64.v31.number);
-
-        private final int dwarfEncoding;
-        private final int graalEncoding;
-
-        DwarfRegEncodingAArch64(int dwarfEncoding, int graalEncoding) {
-            this.dwarfEncoding = dwarfEncoding;
-            this.graalEncoding = graalEncoding;
-        }
-
-        @Override
-        public int getDwarfEncoding() {
-            return dwarfEncoding;
-        }
-
-        @Override
-        public int getGraalEncoding() {
-            return graalEncoding;
-        }
-    }
-
-    // Register numbers used by DWARF for AMD64 registers encoded
-    // along with their respective GraalVM compiler number. n.b. some of the initial
-    // 8 general purpose registers have different Dwarf and GraalVM encodings. For
-    // example the compiler number for RDX is 3 while the DWARF number for RDX is 1.
-    public enum DwarfRegEncodingAMD64 implements DwarfRegEncoding {
-        RAX(0, AMD64.rax.number),
-        RDX(1, AMD64.rdx.number),
-        RCX(2, AMD64.rcx.number),
-        RBX(3, AMD64.rbx.number),
-        RSI(4, AMD64.rsi.number),
-        RDI(5, AMD64.rdi.number),
-        RBP(6, AMD64.rbp.number),
-        RSP(7, AMD64.rsp.number),
-        R8(8, AMD64.r8.number),
-        R9(9, AMD64.r9.number),
-        R10(10, AMD64.r10.number),
-        R11(11, AMD64.r11.number),
-        R12(12, AMD64.r12.number),
-        R13(13, AMD64.r13.number),
-        R14(14, AMD64.r14.number),
-        R15(15, AMD64.r15.number),
-        XMM0(17, AMD64.xmm0.number),
-        XMM1(18, AMD64.xmm1.number),
-        XMM2(19, AMD64.xmm2.number),
-        XMM3(20, AMD64.xmm3.number),
-        XMM4(21, AMD64.xmm4.number),
-        XMM5(22, AMD64.xmm5.number),
-        XMM6(23, AMD64.xmm6.number),
-        XMM7(24, AMD64.xmm7.number),
-        XMM8(25, AMD64.xmm8.number),
-        XMM9(26, AMD64.xmm9.number),
-        XMM10(27, AMD64.xmm10.number),
-        XMM11(28, AMD64.xmm11.number),
-        XMM12(29, AMD64.xmm12.number),
-        XMM13(30, AMD64.xmm13.number),
-        XMM14(31, AMD64.xmm14.number),
-        XMM15(32, AMD64.xmm15.number),
-        XMM16(60, AMD64.xmm16.number),
-        XMM17(61, AMD64.xmm17.number),
-        XMM18(62, AMD64.xmm18.number),
-        XMM19(63, AMD64.xmm19.number),
-        XMM20(64, AMD64.xmm20.number),
-        XMM21(65, AMD64.xmm21.number),
-        XMM22(66, AMD64.xmm22.number),
-        XMM23(67, AMD64.xmm23.number),
-        XMM24(68, AMD64.xmm24.number),
-        XMM25(69, AMD64.xmm25.number),
-        XMM26(70, AMD64.xmm26.number),
-        XMM27(71, AMD64.xmm27.number),
-        XMM28(72, AMD64.xmm28.number),
-        XMM29(73, AMD64.xmm29.number),
-        XMM30(74, AMD64.xmm30.number),
-        XMM31(75, AMD64.xmm31.number),
-        K0(118, AMD64.k0.number),
-        K1(119, AMD64.k1.number),
-        K2(120, AMD64.k2.number),
-        K3(121, AMD64.k3.number),
-        K4(122, AMD64.k4.number),
-        K5(123, AMD64.k5.number),
-        K6(124, AMD64.k6.number),
-        K7(125, AMD64.k7.number);
-
-        private final int dwarfEncoding;
-        private final int graalEncoding;
-
-        DwarfRegEncodingAMD64(int dwarfEncoding, int graalEncoding) {
-            this.dwarfEncoding = dwarfEncoding;
-            this.graalEncoding = graalEncoding;
-        }
-
-        public static int graalOrder(DwarfRegEncodingAMD64 e1, DwarfRegEncodingAMD64 e2) {
-            return Integer.compare(e1.graalEncoding, e2.graalEncoding);
-        }
-
-        @Override
-        public int getDwarfEncoding() {
-            return dwarfEncoding;
-        }
-
-        @Override
-        public int getGraalEncoding() {
-            return graalEncoding;
-        }
-    }
-
-    // Map from compiler register numbers to corresponding DWARF register numbers.
-    private static int[] graalToDwarfRegMap(DwarfRegEncoding[] encoding) {
-        int size = Arrays.stream(encoding).mapToInt(DwarfRegEncoding::getGraalEncoding).max().orElseThrow() + 1;
-        int[] regMap = new int[size];
-        Arrays.fill(regMap, -1);
-        for (DwarfRegEncoding regEncoding : encoding) {
-            regMap[regEncoding.getGraalEncoding()] = regEncoding.getDwarfEncoding();
-        }
-        return regMap;
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfSectionImpl.java
deleted file mode 100644
index a7d4a25d035e..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfSectionImpl.java
+++ /dev/null
@@ -1,952 +0,0 @@
-/*
- * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.dwarf;
-
-import java.nio.ByteOrder;
-import java.nio.charset.StandardCharsets;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Stream;
-
-import com.oracle.objectfile.BasicProgbitsSectionImpl;
-import com.oracle.objectfile.BuildDependency;
-import com.oracle.objectfile.LayoutDecision;
-import com.oracle.objectfile.LayoutDecisionMap;
-import com.oracle.objectfile.ObjectFile;
-import com.oracle.objectfile.debugentry.ArrayTypeEntry;
-import com.oracle.objectfile.debugentry.ClassEntry;
-import com.oracle.objectfile.debugentry.CompiledMethodEntry;
-import com.oracle.objectfile.debugentry.DirEntry;
-import com.oracle.objectfile.debugentry.FileEntry;
-import com.oracle.objectfile.debugentry.MethodEntry;
-import com.oracle.objectfile.debugentry.PrimitiveTypeEntry;
-import com.oracle.objectfile.debugentry.StructureTypeEntry;
-import com.oracle.objectfile.debugentry.TypeEntry;
-import com.oracle.objectfile.debugentry.range.Range;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalInfo;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalValueInfo;
-import com.oracle.objectfile.elf.ELFMachine;
-import com.oracle.objectfile.elf.ELFObjectFile;
-import com.oracle.objectfile.runtime.dwarf.RuntimeDwarfDebugInfo.AbbrevCode;
-import com.oracle.objectfile.elf.dwarf.constants.DwarfExpressionOpcode;
-import com.oracle.objectfile.elf.dwarf.constants.DwarfFlag;
-import com.oracle.objectfile.elf.dwarf.constants.DwarfLocationListEntry;
-import com.oracle.objectfile.elf.dwarf.constants.DwarfRangeListEntry;
-import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName;
-import com.oracle.objectfile.elf.dwarf.constants.DwarfTag;
-import com.oracle.objectfile.elf.dwarf.constants.DwarfUnitHeader;
-import com.oracle.objectfile.elf.dwarf.constants.DwarfVersion;
-
-import jdk.graal.compiler.debug.DebugContext;
-import jdk.vm.ci.meta.ResolvedJavaType;
-
-/**
- * A class from which all DWARF debug sections inherit providing common behaviours.
- */
-public abstract class RuntimeDwarfSectionImpl extends BasicProgbitsSectionImpl {
-    // auxiliary class used to track byte array positions
-    protected class Cursor {
-        private int pos;
-
-        public Cursor() {
-            this(0);
-        }
-
-        public Cursor(int p) {
-            assert p >= 0;
-            set(p);
-        }
-
-        public void set(int p) {
-            assert p >= 0;
-            pos = p;
-        }
-
-        public int add(int d) {
-            assert pos + d >= 0;
-            pos += d;
-            return pos;
-        }
-
-        public int get() {
-            return pos;
-        }
-    }
-
-    protected final RuntimeDwarfDebugInfo dwarfSections;
-    protected boolean debug = false;
-    protected long debugTextBase = 0;
-    protected long debugAddress = 0;
-    protected int debugBase = 0;
-
-    /**
-     * The name of this section.
-     */
-    private final DwarfSectionName sectionName;
-
-    /**
-     * The name of the section which needs to have been created prior to creating this section.
-     */
-    private final DwarfSectionName targetSectionName;
-
-    /**
-     * The layout properties of the target section which need to have been decided before the
-     * contents of this section can be created.
-     */
-    private final LayoutDecision.Kind[] targetSectionKinds;
-    /**
-     * The default layout properties.
-     */
-    private static final LayoutDecision.Kind[] defaultTargetSectionKinds = {
-            LayoutDecision.Kind.CONTENT,
-            LayoutDecision.Kind.SIZE
-    };
-
-    public RuntimeDwarfSectionImpl(RuntimeDwarfDebugInfo dwarfSections, DwarfSectionName name, DwarfSectionName targetName) {
-        this(dwarfSections, name, targetName, defaultTargetSectionKinds);
-    }
-
-    public RuntimeDwarfSectionImpl(RuntimeDwarfDebugInfo dwarfSections, DwarfSectionName sectionName, DwarfSectionName targetSectionName, LayoutDecision.Kind[] targetKinds) {
-        this.dwarfSections = dwarfSections;
-        this.sectionName = sectionName;
-        this.targetSectionName = targetSectionName;
-        this.targetSectionKinds = targetKinds;
-    }
-
-    public boolean isAArch64() {
-        return dwarfSections.elfMachine == ELFMachine.AArch64;
-    }
-
-    /**
-     * Creates the target byte[] array used to define the section contents.
-     *
-     * The main task of this method is to precompute the size of the debug section. given the
-     * complexity of the data layouts that invariably requires performing a dummy write of the
-     * contents, inserting bytes into a small, scratch buffer only when absolutely necessary.
-     * subclasses may also cache some information for use when writing the contents.
-     */
-    public abstract void createContent();
-
-    /**
-     * Populates the byte[] array used to contain the section contents.
-     *
-     * In most cases this task reruns the operations performed under createContent but this time
-     * actually writing data to the target byte[].
-     */
-    public abstract void writeContent(DebugContext debugContext);
-
-    /**
-     * Check whether the contents byte array has been sized and created. n.b. this does not imply
-     * that data has been written to the byte array.
-     *
-     * @return true if the contents byte array has been sized and created otherwise false.
-     */
-    public boolean contentByteArrayCreated() {
-        return getContent() != null;
-    }
-
-    @Override
-    public boolean isLoadable() {
-        /*
-         * Even though we're a progbits section impl we're not actually loadable.
-         */
-        return false;
-    }
-
-    private String debugSectionLogName() {
-        /*
-         * Use prefix dwarf plus the section name (which already includes a dot separator) for the
-         * context key. For example messages for info section will be keyed using dwarf.debug_info.
-         * Other info formats use their own format-specific prefix.
-         */
-        assert getSectionName().startsWith(".debug");
-        return "dwarf" + getSectionName();
-    }
-
-    protected void enableLog(DebugContext context, int pos) {
-        /*
-         * Debug output is disabled during the first pass where we size the buffer. this is called
-         * to enable it during the second pass where the buffer gets written, but only if the scope
-         * is enabled.
-         */
-        assert contentByteArrayCreated();
-
-        if (context.areScopesEnabled()) {
-            debug = true;
-            debugBase = pos;
-            debugAddress = debugTextBase;
-        }
-    }
-
-    protected void log(DebugContext context, String format, Object... args) {
-        if (debug) {
-            context.logv(DebugContext.INFO_LEVEL, format, args);
-        }
-    }
-
-    protected void verboseLog(DebugContext context, String format, Object... args) {
-        if (debug) {
-            context.logv(DebugContext.VERBOSE_LEVEL, format, args);
-        }
-    }
-
-    protected boolean littleEndian() {
-        return dwarfSections.getByteOrder() == ByteOrder.LITTLE_ENDIAN;
-    }
-
-    /*
-     * Base level put methods that assume a non-null buffer.
-     */
-
-    protected int putByte(byte b, byte[] buffer, int p) {
-        int pos = p;
-        buffer[pos++] = b;
-        return pos;
-    }
-
-    protected int putShort(short s, byte[] buffer, int p) {
-        int pos = p;
-        if (littleEndian()) {
-            buffer[pos++] = (byte) (s & 0xff);
-            buffer[pos++] = (byte) ((s >> 8) & 0xff);
-        } else {
-            buffer[pos++] = (byte) ((s >> 8) & 0xff);
-            buffer[pos++] = (byte) (s & 0xff);
-        }
-        return pos;
-    }
-
-    protected int putInt(int i, byte[] buffer, int p) {
-        int pos = p;
-        if (littleEndian()) {
-            buffer[pos++] = (byte) (i & 0xff);
-            buffer[pos++] = (byte) ((i >> 8) & 0xff);
-            buffer[pos++] = (byte) ((i >> 16) & 0xff);
-            buffer[pos++] = (byte) ((i >> 24) & 0xff);
-        } else {
-            buffer[pos++] = (byte) ((i >> 24) & 0xff);
-            buffer[pos++] = (byte) ((i >> 16) & 0xff);
-            buffer[pos++] = (byte) ((i >> 8) & 0xff);
-            buffer[pos++] = (byte) (i & 0xff);
-        }
-        return pos;
-    }
-
-    protected int putLong(long l, byte[] buffer, int p) {
-        int pos = p;
-        if (littleEndian()) {
-            buffer[pos++] = (byte) (l & 0xff);
-            buffer[pos++] = (byte) ((l >> 8) & 0xff);
-            buffer[pos++] = (byte) ((l >> 16) & 0xff);
-            buffer[pos++] = (byte) ((l >> 24) & 0xff);
-            buffer[pos++] = (byte) ((l >> 32) & 0xff);
-            buffer[pos++] = (byte) ((l >> 40) & 0xff);
-            buffer[pos++] = (byte) ((l >> 48) & 0xff);
-            buffer[pos++] = (byte) ((l >> 56) & 0xff);
-        } else {
-            buffer[pos++] = (byte) ((l >> 56) & 0xff);
-            buffer[pos++] = (byte) ((l >> 48) & 0xff);
-            buffer[pos++] = (byte) ((l >> 40) & 0xff);
-            buffer[pos++] = (byte) ((l >> 32) & 0xff);
-            buffer[pos++] = (byte) ((l >> 16) & 0xff);
-            buffer[pos++] = (byte) ((l >> 24) & 0xff);
-            buffer[pos++] = (byte) ((l >> 8) & 0xff);
-            buffer[pos++] = (byte) (l & 0xff);
-        }
-        return pos;
-    }
-
-    protected int putRelocatableCodeOffset(long l, byte[] buffer, int p) {
-        int pos = p;
-        /*
-         * Mark address so it is relocated relative to the start of the text segment.
-         */
-        markRelocationSite(pos, ObjectFile.RelocationKind.DIRECT_8, DwarfSectionName.TEXT_SECTION.value(), l);
-        pos = writeLong(0, buffer, pos);
-        return pos;
-    }
-
-    protected int putRelocatableHeapOffset(long l, byte[] buffer, int p) {
-        int pos = p;
-        /*
-         * Mark address so it is relocated relative to the start of the heap.
-         */
-        markRelocationSite(pos, ObjectFile.RelocationKind.DIRECT_8, RuntimeDwarfDebugInfo.HEAP_BEGIN_NAME, l);
-        pos = writeLong(0, buffer, pos);
-        return pos;
-    }
-
-    protected int putRelocatableDwarfSectionOffset(int offset, byte[] buffer, String referencedSectionName, int p) {
-        int pos = p;
-        /*
-         * Mark address so it is relocated relative to the start of the desired section.
-         */
-        markRelocationSite(pos, ObjectFile.RelocationKind.DIRECT_4, referencedSectionName, offset);
-        pos = writeInt(0, buffer, pos);
-        return pos;
-    }
-
-    protected int putULEB(long val, byte[] buffer, int p) {
-        int pos = p;
-        long l = val;
-        for (int i = 0; i < 9; i++) {
-            byte b = (byte) (l & 0x7f);
-            l = l >>> 7;
-            boolean done = (l == 0);
-            if (!done) {
-                b = (byte) (b | 0x80);
-            }
-            pos = writeByte(b, buffer, pos);
-            if (done) {
-                break;
-            }
-        }
-        return pos;
-    }
-
-    protected int putSLEB(long val, byte[] buffer, int p) {
-        int pos = p;
-        long l = val;
-        for (int i = 0; i < 9; i++) {
-            byte b = (byte) (l & 0x7f);
-            l = l >> 7;
-            boolean bIsSigned = (b & 0x40) != 0;
-            boolean done = ((bIsSigned && l == -1) || (!bIsSigned && l == 0));
-            if (!done) {
-                b = (byte) (b | 0x80);
-            }
-            pos = writeByte(b, buffer, pos);
-            if (done) {
-                break;
-            }
-        }
-        return pos;
-    }
-
-    protected static int countUTF8Bytes(String s) {
-        return countUTF8Bytes(s, 0);
-    }
-
-    protected static int countUTF8Bytes(String s, int startChar) {
-        byte[] bytes = s.substring(startChar).getBytes(StandardCharsets.UTF_8);
-        return bytes.length;
-    }
-
-    protected int putUTF8StringBytes(String s, int startChar, byte[] buffer, int p) {
-        int pos = p;
-        byte[] bytes = s.substring(startChar).getBytes(StandardCharsets.UTF_8);
-        System.arraycopy(bytes, 0, buffer, pos, bytes.length);
-        pos += bytes.length;
-        buffer[pos++] = '\0';
-        return pos;
-    }
-
-    /*
-     * Common write methods that check for a null buffer.
-     */
-
-    protected int writeByte(byte b, byte[] buffer, int p) {
-        if (buffer != null) {
-            return putByte(b, buffer, p);
-        } else {
-            return p + 1;
-        }
-    }
-
-    protected int writeShort(short s, byte[] buffer, int p) {
-        if (buffer != null) {
-            return putShort(s, buffer, p);
-        } else {
-            return p + 2;
-        }
-    }
-
-    protected int writeInt(int i, byte[] buffer, int p) {
-        if (buffer != null) {
-            return putInt(i, buffer, p);
-        } else {
-            return p + 4;
-        }
-    }
-
-    protected int writeLong(long l, byte[] buffer, int p) {
-        if (buffer != null) {
-            return putLong(l, buffer, p);
-        } else {
-            return p + 8;
-        }
-    }
-
-    protected int writeRelocatableCodeOffset(long l, byte[] buffer, int p) {
-        if (buffer != null) {
-            return putRelocatableCodeOffset(l, buffer, p);
-        } else {
-            return p + 8;
-        }
-    }
-
-    protected int writeRelocatableHeapOffset(long l, byte[] buffer, int p) {
-        if (buffer != null) {
-            return putRelocatableHeapOffset(l, buffer, p);
-        } else {
-            return p + 8;
-        }
-    }
-
-    protected int writeULEB(long val, byte[] buffer, int p) {
-        if (buffer != null) {
-            // write to the buffer at the supplied position
-            return putULEB(val, buffer, p);
-        } else {
-            // write to a scratch buffer at position 0 then offset from initial pos
-            return p + putULEB(val, scratch, 0);
-        }
-    }
-
-    protected int writeSLEB(long val, byte[] buffer, int p) {
-        if (buffer != null) {
-            // write to the buffer at the supplied position
-            return putSLEB(val, buffer, p);
-        } else {
-            // write to a scratch buffer at position 0 then offset from initial pos
-            return p + putSLEB(val, scratch, 0);
-        }
-    }
-
-    protected int writeUTF8StringBytes(String s, byte[] buffer, int pos) {
-        return writeUTF8StringBytes(s, 0, buffer, pos);
-    }
-
-    protected int writeUTF8StringBytes(String s, int startChar, byte[] buffer, int p) {
-        if (buffer != null) {
-            return putUTF8StringBytes(s, startChar, buffer, p);
-        } else {
-            return s.substring(startChar).getBytes(StandardCharsets.UTF_8).length;
-        }
-    }
-
-    protected int writeExprOpcode(DwarfExpressionOpcode opcode, byte[] buffer, int p) {
-        return writeByte(opcode.value(), buffer, p);
-    }
-
-    protected int writeExprOpcodeLiteral(int offset, byte[] buffer, int p) {
-        byte value = DwarfExpressionOpcode.DW_OP_lit0.value();
-        assert offset >= 0 && offset < 0x20;
-        value = (byte) (value + offset);
-        return writeByte(value, buffer, p);
-    }
-
-    protected int writeExprOpcodeReg(byte reg, byte[] buffer, int p) {
-        byte value = DwarfExpressionOpcode.DW_OP_reg0.value();
-        assert reg >= 0 && reg < 0x20;
-        value += reg;
-        return writeByte(value, buffer, p);
-    }
-
-    protected int writeExprOpcodeBReg(byte reg, byte[] buffer, int p) {
-        byte value = DwarfExpressionOpcode.DW_OP_breg0.value();
-        assert reg >= 0 && reg < 0x20;
-        value += reg;
-        return writeByte(value, buffer, p);
-    }
-
-    /*
-     * Common write methods that rely on called methods to handle a null buffer
-     */
-
-    protected void patchLength(int lengthPos, byte[] buffer, int pos) {
-        int length = pos - (lengthPos + 4);
-        writeInt(length, buffer, lengthPos);
-    }
-
-    protected int writeAbbrevCode(AbbrevCode code, byte[] buffer, int pos) {
-        return writeSLEB(code.ordinal(), buffer, pos);
-    }
-
-    protected int writeRangeListEntry(DwarfRangeListEntry rangeListEntry, byte[] buffer, int pos) {
-        return writeByte(rangeListEntry.value(), buffer, pos);
-    }
-
-    protected int writeLocationListEntry(DwarfLocationListEntry locationListEntry, byte[] buffer, int pos) {
-        return writeByte(locationListEntry.value(), buffer, pos);
-    }
-
-    protected int writeTag(DwarfTag dwarfTag, byte[] buffer, int pos) {
-        int code = dwarfTag.value();
-        if (code == 0) {
-            return writeByte((byte) 0, buffer, pos);
-        } else {
-            return writeSLEB(code, buffer, pos);
-        }
-    }
-
-    protected int writeDwarfVersion(DwarfVersion dwarfVersion, byte[] buffer, int pos) {
-        return writeShort(dwarfVersion.value(), buffer, pos);
-    }
-
-    protected int writeDwarfUnitHeader(DwarfUnitHeader dwarfUnitHeader, byte[] buffer, int pos) {
-        return writeByte((byte) dwarfUnitHeader.value(), buffer, pos);
-    }
-
-    protected int writeTypeSignature(long typeSignature, byte[] buffer, int pos) {
-        return writeLong(typeSignature, buffer, pos);
-    }
-
-    protected int writeFlag(DwarfFlag flag, byte[] buffer, int pos) {
-        return writeByte(flag.value(), buffer, pos);
-    }
-
-    protected int writeAttrAddress(long address, byte[] buffer, int pos) {
-        return writeRelocatableCodeOffset(address, buffer, pos);
-    }
-
-    @SuppressWarnings("unused")
-    protected int writeAttrData8(long value, byte[] buffer, int pos) {
-        return writeLong(value, buffer, pos);
-    }
-
-    protected int writeAttrData4(int value, byte[] buffer, int pos) {
-        return writeInt(value, buffer, pos);
-    }
-
-    protected int writeAttrData2(short value, byte[] buffer, int pos) {
-        return writeShort(value, buffer, pos);
-    }
-
-    protected int writeAttrData1(byte value, byte[] buffer, int pos) {
-        return writeByte(value, buffer, pos);
-    }
-
-    protected int writeInfoSectionOffset(int offset, byte[] buffer, int pos) {
-        return writeDwarfSectionOffset(offset, buffer, DwarfSectionName.DW_INFO_SECTION, pos);
-    }
-
-    protected int writeLineSectionOffset(int offset, byte[] buffer, int pos) {
-        return writeDwarfSectionOffset(offset, buffer, DwarfSectionName.DW_LINE_SECTION, pos);
-    }
-
-    protected int writeAbbrevSectionOffset(int offset, byte[] buffer, int pos) {
-        return writeDwarfSectionOffset(offset, buffer, DwarfSectionName.DW_ABBREV_SECTION, pos);
-    }
-
-    protected int writeStrSectionOffset(String value, byte[] buffer, int p) {
-        int pos = p;
-        int idx = debugStringIndex(value);
-        return writeStrSectionOffset(idx, buffer, pos);
-    }
-
-    private int writeStrSectionOffset(int offset, byte[] buffer, int pos) {
-        return writeDwarfSectionOffset(offset, buffer, DwarfSectionName.DW_STR_SECTION, pos);
-    }
-
-    protected int writeLocSectionOffset(int offset, byte[] buffer, int pos) {
-        return writeDwarfSectionOffset(offset, buffer, DwarfSectionName.DW_LOCLISTS_SECTION, pos);
-    }
-
-    protected int writeDwarfSectionOffset(int offset, byte[] buffer, DwarfSectionName referencedSectionName, int pos) {
-        // offsets to abbrev section DIEs need a relocation
-        // the linker uses this to update the offset when info sections are merged
-        if (buffer != null) {
-            return putRelocatableDwarfSectionOffset(offset, buffer, referencedSectionName.value(), pos);
-        } else {
-            return pos + 4;
-        }
-    }
-
-    protected int writeAttrNull(byte[] buffer, int pos) {
-        // A null attribute is just a zero tag.
-        return writeTag(DwarfTag.DW_TAG_null, buffer, pos);
-    }
-
-    /*
-     * Write a heap location expression preceded by a ULEB block size count as appropriate for an
-     * attribute with FORM exprloc. If a heapbase register is in use the generated expression
-     * computes the location as a constant offset from the runtime heap base register. If a heapbase
-     * register is not in use it computes the location as a fixed, relocatable offset from the
-     * link-time heap base address.
-     */
-    protected int writeHeapLocationExprLoc(long offset, byte[] buffer, int p) {
-        int pos = p;
-        /*
-         * We have to size the DWARF location expression by writing it to the scratch buffer so we
-         * can write its size as a ULEB before the expression itself.
-         */
-        int size = writeHeapLocation(offset, null, 0);
-
-        /* Write the size and expression into the output buffer. */
-        pos = writeULEB(size, buffer, pos);
-        return writeHeapLocation(offset, buffer, pos);
-    }
-
-    /*
-     * Write a heap location expression preceded by a ULEB block size count as appropriate for
-     * location list in the debug_loc section. If a heapbase register is in use the generated
-     * expression computes the location as a constant offset from the runtime heap base register. If
-     * a heapbase register is not in use it computes the location as a fixed, relocatable offset
-     * from the link-time heap base address.
-     */
-    protected int writeHeapLocationLocList(long offset, byte[] buffer, int p) {
-        int pos = p;
-        int len = 0;
-        int lenPos = pos;
-        // write dummy length
-        pos = writeULEB(len, buffer, pos);
-        int zeroPos = pos;
-        pos = writeHeapLocation(offset, buffer, pos);
-        pos = writeExprOpcode(DwarfExpressionOpcode.DW_OP_stack_value, buffer, pos);
-        // backpatch length
-        len = pos - zeroPos;
-        writeULEB(len, buffer, lenPos);
-        return pos;
-    }
-
-    /*
-     * Write a bare heap location expression as appropriate for a single location. If useHeapBase is
-     * true the generated expression computes the location as a constant offset from the runtime
-     * heap base register. If useHeapBase is false it computes the location as a fixed, relocatable
-     * offset from the link-time heap base address.
-     */
-    protected int writeHeapLocation(long offset, byte[] buffer, int p) {
-        if (dwarfSections.useHeapBase()) {
-            return writeHeapLocationBaseRelative(offset, buffer, p);
-        } else {
-            return writeHeapLocationRelocatable(offset, buffer, p);
-        }
-    }
-
-    private int writeHeapLocationBaseRelative(long offset, byte[] buffer, int p) {
-        int pos = p;
-        /* Write a location rebasing the offset relative to the heapbase register. */
-        pos = writeExprOpcodeBReg(dwarfSections.getHeapbaseRegister(), buffer, pos);
-        return writeSLEB(offset, buffer, pos);
-    }
-
-    private int writeHeapLocationRelocatable(long offset, byte[] buffer, int p) {
-        int pos = p;
-        /* Write a relocatable address relative to the heap section start. */
-        pos = writeExprOpcode(DwarfExpressionOpcode.DW_OP_addr, buffer, pos);
-        return writeRelocatableHeapOffset(offset, buffer, pos);
-    }
-
-    protected static String formatValue(DebugLocalValueInfo value) {
-        switch (value.localKind()) {
-            case REGISTER:
-                return "REG:" + value.regIndex();
-            case STACKSLOT:
-                return "STACK:" + value.stackSlot();
-            case CONSTANT:
-                return "CONST:" + value.constantValue() + "[" + Long.toHexString(value.heapOffset()) + "]";
-            case UNDEFINED:
-            default:
-                return "-";
-        }
-    }
-
-    /**
-     * Identify the section after which this debug section needs to be ordered when sizing and
-     * creating content.
-     *
-     * @return the name of the preceding section.
-     */
-    public final String targetName() {
-        return targetSectionName.value();
-    }
-
-    /**
-     * Identify this debug section by name.
-     *
-     * @return the name of the debug section.
-     */
-    public final String getSectionName() {
-        return sectionName.value();
-    }
-
-    @Override
-    public int getOrDecideSize(Map<ObjectFile.Element, LayoutDecisionMap> alreadyDecided, int sizeHint) {
-
-        if (targetName().startsWith(".debug")) {
-            ObjectFile.Element previousElement = this.getElement().getOwner().elementForName(targetName());
-            RuntimeDwarfSectionImpl previousSection = (RuntimeDwarfSectionImpl) previousElement.getImpl();
-            assert previousSection.contentByteArrayCreated();
-        }
-        createContent();
-
-        return getContent().length;
-    }
-
-    @Override
-    public byte[] getOrDecideContent(Map<ObjectFile.Element, LayoutDecisionMap> alreadyDecided, byte[] contentHint) {
-        assert contentByteArrayCreated();
-        /*
-         * Ensure content byte[] has been written before calling super method.
-         *
-         * we do this in a nested debug scope derived from the one set up under the object file
-         * write
-         */
-        getOwner().debugContext(debugSectionLogName(), this::writeContent);
-
-        return super.getOrDecideContent(alreadyDecided, contentHint);
-    }
-
-    @Override
-    public Set<BuildDependency> getDependencies(Map<ObjectFile.Element, LayoutDecisionMap> decisions) {
-        Set<BuildDependency> deps = super.getDependencies(decisions);
-        String targetName = targetName();
-        ELFObjectFile.ELFSection targetSection = (ELFObjectFile.ELFSection) getElement().getOwner().elementForName(targetName);
-        LayoutDecision ourContent = decisions.get(getElement()).getDecision(LayoutDecision.Kind.CONTENT);
-        LayoutDecision ourSize = decisions.get(getElement()).getDecision(LayoutDecision.Kind.SIZE);
-
-        for (LayoutDecision.Kind targetKind : targetSectionKinds) {
-            if (targetKind == LayoutDecision.Kind.SIZE) {
-                /* Make our size depend on the target size so we compute sizes in order. */
-                LayoutDecision targetDecision = decisions.get(targetSection).getDecision(targetKind);
-                deps.add(BuildDependency.createOrGet(ourSize, targetDecision));
-            } else if (targetKind == LayoutDecision.Kind.CONTENT) {
-                /* Make our content depend on the target content so we compute contents in order. */
-                LayoutDecision targetDecision = decisions.get(targetSection).getDecision(targetKind);
-                deps.add(BuildDependency.createOrGet(ourContent, targetDecision));
-            } else {
-                /* Make our size depend on the relevant target's property. */
-                LayoutDecision targetDecision = decisions.get(targetSection).getDecision(targetKind);
-                deps.add(BuildDependency.createOrGet(ourSize, targetDecision));
-            }
-        }
-        return deps;
-    }
-
-    /**
-     * A scratch buffer used during computation of a section's size.
-     */
-    protected static final byte[] scratch = new byte[10];
-
-    /**
-     * Retrieve a stream of all types notified via the DebugTypeInfo API.
-     *
-     * @return a stream of all types notified via the DebugTypeInfo API.
-     */
-    protected Stream<TypeEntry> typeStream() {
-        return dwarfSections.getTypes().stream();
-    }
-
-    /**
-     * Retrieve a stream of all primitive types notified via the DebugTypeInfo API.
-     *
-     * @return a stream of all primitive types notified via the DebugTypeInfo API.
-     */
-    protected Stream<PrimitiveTypeEntry> primitiveTypeStream() {
-        return typeStream().filter(TypeEntry::isPrimitive).map(entry -> ((PrimitiveTypeEntry) entry));
-    }
-
-    /**
-     * Retrieve a stream of all array types notified via the DebugTypeInfo API.
-     *
-     * @return a stream of all array types notified via the DebugTypeInfo API.
-     */
-    protected Stream<ArrayTypeEntry> arrayTypeStream() {
-        return typeStream().filter(TypeEntry::isArray).map(entry -> ((ArrayTypeEntry) entry));
-    }
-
-    /**
-     * Retrieve a stream of all compiled methods notified via the DebugTypeInfo API.
-     *
-     * @return a stream of all compiled methods notified via the DebugTypeInfo API.
-     */
-    protected CompiledMethodEntry compiledMethod() {
-        return dwarfSections.getCompiledMethod();
-    }
-
-    protected Iterable<FileEntry> files() {
-        return dwarfSections.getFiles();
-    }
-
-    protected int fileCount() {
-        return dwarfSections.getFiles().size();
-    }
-
-    protected Iterable<DirEntry> dirs() {
-        return dwarfSections.getDirs();
-    }
-
-    protected int dirCount() {
-        return dwarfSections.getDirs().size();
-    }
-
-    protected int debugStringIndex(String str) {
-        if (!contentByteArrayCreated()) {
-            return 0;
-        }
-        return dwarfSections.debugStringIndex(str);
-    }
-
-    protected String uniqueDebugString(String str) {
-        return dwarfSections.uniqueDebugString(str);
-    }
-
-    protected TypeEntry lookupType(ResolvedJavaType type) {
-        return dwarfSections.lookupTypeEntry(type);
-    }
-
-    protected int getCUIndex(ClassEntry classEntry) {
-        if (!contentByteArrayCreated()) {
-            return 0;
-        }
-        return dwarfSections.getCUIndex(classEntry);
-    }
-
-    protected void setCUIndex(ClassEntry classEntry, int idx) {
-        dwarfSections.setCUIndex(classEntry, idx);
-    }
-
-    protected void setCodeRangesIndex(ClassEntry classEntry, int pos) {
-        dwarfSections.setCodeRangesIndex(classEntry, pos);
-    }
-
-    protected int getCodeRangesIndex(ClassEntry classEntry) {
-        if (!contentByteArrayCreated()) {
-            return 0;
-        }
-        return dwarfSections.getCodeRangesIndex(classEntry);
-    }
-
-    protected void setLocationListIndex(ClassEntry classEntry, int pos) {
-        dwarfSections.setLocationListIndex(classEntry, pos);
-    }
-
-    protected int getLocationListIndex(ClassEntry classEntry) {
-        return dwarfSections.getLocationListIndex(classEntry);
-    }
-
-    protected void setLineIndex(ClassEntry classEntry, int pos) {
-        dwarfSections.setLineIndex(classEntry, pos);
-    }
-
-    protected int getLineIndex(ClassEntry classEntry) {
-        if (!contentByteArrayCreated()) {
-            return 0;
-        }
-        return dwarfSections.getLineIndex(classEntry);
-    }
-
-    protected void setLinePrologueSize(ClassEntry classEntry, int pos) {
-        dwarfSections.setLinePrologueSize(classEntry, pos);
-    }
-
-    protected int getLinePrologueSize(ClassEntry classEntry) {
-        if (!contentByteArrayCreated()) {
-            return 0;
-        }
-        return dwarfSections.getLinePrologueSize(classEntry);
-    }
-
-    protected void setFieldDeclarationIndex(StructureTypeEntry entry, String fieldName, int pos) {
-        dwarfSections.setFieldDeclarationIndex(entry, fieldName, pos);
-    }
-
-    protected int getFieldDeclarationIndex(StructureTypeEntry entry, String fieldName) {
-        if (!contentByteArrayCreated()) {
-            return 0;
-        }
-        return dwarfSections.getFieldDeclarationIndex(entry, fieldName);
-    }
-
-    protected void setMethodDeclarationIndex(MethodEntry methodEntry, int pos) {
-        dwarfSections.setMethodDeclarationIndex(methodEntry, pos);
-    }
-
-    protected int getMethodDeclarationIndex(MethodEntry methodEntry) {
-        if (!contentByteArrayCreated()) {
-            return 0;
-        }
-        return dwarfSections.getMethodDeclarationIndex(methodEntry);
-    }
-
-    protected void setAbstractInlineMethodIndex(ClassEntry classEntry, MethodEntry methodEntry, int pos) {
-        dwarfSections.setAbstractInlineMethodIndex(classEntry, methodEntry, pos);
-    }
-
-    protected int getAbstractInlineMethodIndex(ClassEntry classEntry, MethodEntry methodEntry) {
-        if (!contentByteArrayCreated()) {
-            return 0;
-        }
-        return dwarfSections.getAbstractInlineMethodIndex(classEntry, methodEntry);
-    }
-
-    /**
-     * Record the info section offset of a local (or parameter) declaration DIE appearing as a child
-     * of a standard method declaration or an abstract inline method declaration.
-     *
-     * @param classEntry the class of the top level method being declared or inlined into
-     * @param methodEntry the method being declared or inlined.
-     * @param localInfo the local or param whose index is to be recorded.
-     * @param index the info section offset to be recorded.
-     */
-    protected void setMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, DebugLocalInfo localInfo, int index) {
-        dwarfSections.setMethodLocalIndex(classEntry, methodEntry, localInfo, index);
-    }
-
-    /**
-     * Retrieve the info section offset of a local (or parameter) declaration DIE appearing as a
-     * child of a standard method declaration or an abstract inline method declaration.
-     *
-     * @param classEntry the class of the top level method being declared or inlined into
-     * @param methodEntry the method being declared or imported
-     * @param localInfo the local or param whose index is to be retrieved.
-     * @return the associated info section offset.
-     */
-    protected int getMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, DebugLocalInfo localInfo) {
-        if (!contentByteArrayCreated()) {
-            return 0;
-        }
-        return dwarfSections.getMethodLocalIndex(classEntry, methodEntry, localInfo);
-    }
-
-    /**
-     * Record the info section offset of a local (or parameter) location DIE associated with a top
-     * level (primary) or inline method range.
-     *
-     * @param range the top level (primary) or inline range to which the local (or parameter)
-     *            belongs.
-     * @param localInfo the local or param whose index is to be recorded.
-     * @param index the info section offset index to be recorded.
-     */
-    protected void setRangeLocalIndex(Range range, DebugLocalInfo localInfo, int index) {
-        dwarfSections.setRangeLocalIndex(range, localInfo, index);
-    }
-
-    /**
-     * Retrieve the info section offset of a local (or parameter) location DIE associated with a top
-     * level (primary) or inline method range.
-     *
-     * @param range the top level (primary) or inline range to which the local (or parameter)
-     *            belongs.
-     * @param localInfo the local or param whose index is to be retrieved.
-     * @return the associated info section offset.
-     */
-    protected int getRangeLocalIndex(Range range, DebugLocalInfo localInfo) {
-        return dwarfSections.getRangeLocalIndex(range, localInfo);
-    }
-}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfStrSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfStrSectionImpl.java
deleted file mode 100644
index 101ae05cb34c..000000000000
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/runtime/dwarf/RuntimeDwarfStrSectionImpl.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2020, 2020, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-
-package com.oracle.objectfile.runtime.dwarf;
-
-import com.oracle.objectfile.debugentry.StringEntry;
-import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName;
-import jdk.graal.compiler.debug.DebugContext;
-
-/**
- * Generator for debug_str section.
- */
-public class RuntimeDwarfStrSectionImpl extends RuntimeDwarfSectionImpl {
-    public RuntimeDwarfStrSectionImpl(RuntimeDwarfDebugInfo dwarfSections) {
-        // debug_str section depends on info section
-        super(dwarfSections, DwarfSectionName.DW_STR_SECTION, DwarfSectionName.DW_INFO_SECTION);
-    }
-
-    @Override
-    public void createContent() {
-        assert !contentByteArrayCreated();
-
-        int pos = 0;
-        for (StringEntry stringEntry : dwarfSections.getStringTable()) {
-            if (stringEntry.isAddToStrSection()) {
-                stringEntry.setOffset(pos);
-                String string = stringEntry.getString();
-                pos += countUTF8Bytes(string) + 1;
-            }
-        }
-        byte[] buffer = new byte[pos];
-        super.setContent(buffer);
-    }
-
-    @Override
-    public void writeContent(DebugContext context) {
-        assert contentByteArrayCreated();
-
-        byte[] buffer = getContent();
-        int size = buffer.length;
-        int pos = 0;
-
-        enableLog(context, pos);
-
-        verboseLog(context, " [0x%08x] DEBUG_STR", pos);
-        for (StringEntry stringEntry : dwarfSections.getStringTable()) {
-            if (stringEntry.isAddToStrSection()) {
-                assert stringEntry.getOffset() == pos;
-                String string = stringEntry.getString();
-                pos = writeUTF8StringBytes(string, buffer, pos);
-                verboseLog(context, " [0x%08x] string = %s", pos, string);
-            }
-        }
-        assert pos == size;
-    }
-}
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
new file mode 100644
index 000000000000..88ed01705f2f
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SharedDebugInfoProvider.java
@@ -0,0 +1,1078 @@
+package com.oracle.svm.core.debug;
+
+import com.oracle.objectfile.debugentry.ClassEntry;
+import com.oracle.objectfile.debugentry.CompiledMethodEntry;
+import com.oracle.objectfile.debugentry.DirEntry;
+import com.oracle.objectfile.debugentry.FieldEntry;
+import com.oracle.objectfile.debugentry.FileEntry;
+import com.oracle.objectfile.debugentry.FrameSizeChangeEntry;
+import com.oracle.objectfile.debugentry.HeaderTypeEntry;
+import com.oracle.objectfile.debugentry.LoaderEntry;
+import com.oracle.objectfile.debugentry.LocalEntry;
+import com.oracle.objectfile.debugentry.LocalValueEntry;
+import com.oracle.objectfile.debugentry.MethodEntry;
+import com.oracle.objectfile.debugentry.StringTable;
+import com.oracle.objectfile.debugentry.StructureTypeEntry;
+import com.oracle.objectfile.debugentry.TypeEntry;
+import com.oracle.objectfile.debugentry.range.CallRange;
+import com.oracle.objectfile.debugentry.range.PrimaryRange;
+import com.oracle.objectfile.debugentry.range.Range;
+import com.oracle.objectfile.debuginfo.DebugInfoProvider;
+import com.oracle.svm.core.UniqueShortNameProvider;
+import com.oracle.svm.core.code.CompilationResultFrameTree;
+import com.oracle.svm.core.config.ConfigurationValues;
+import com.oracle.svm.core.config.ObjectLayout;
+import com.oracle.svm.core.graal.code.SubstrateBackend;
+import com.oracle.svm.core.graal.code.SubstrateCallingConvention;
+import com.oracle.svm.core.graal.code.SubstrateCallingConventionKind;
+import com.oracle.svm.core.graal.code.SubstrateCallingConventionType;
+import com.oracle.svm.core.graal.meta.RuntimeConfiguration;
+import com.oracle.svm.core.graal.meta.SubstrateRegisterConfig;
+import com.oracle.svm.core.heap.Heap;
+import com.oracle.svm.core.heap.ObjectHeader;
+import com.oracle.svm.core.heap.ReferenceAccess;
+import com.oracle.svm.core.meta.SharedMethod;
+import com.oracle.svm.core.meta.SharedType;
+import jdk.graal.compiler.code.CompilationResult;
+import jdk.graal.compiler.core.common.CompilationIdentifier;
+import jdk.graal.compiler.core.target.Backend;
+import jdk.graal.compiler.debug.DebugContext;
+import jdk.graal.compiler.graph.NodeSourcePosition;
+import jdk.graal.compiler.java.StableMethodNameFormatter;
+import jdk.graal.compiler.util.Digest;
+import jdk.vm.ci.code.BytecodeFrame;
+import jdk.vm.ci.code.BytecodePosition;
+import jdk.vm.ci.code.CallingConvention;
+import jdk.vm.ci.code.RegisterConfig;
+import jdk.vm.ci.code.RegisterValue;
+import jdk.vm.ci.code.StackSlot;
+import jdk.vm.ci.meta.AllocatableValue;
+import jdk.vm.ci.meta.JavaConstant;
+import jdk.vm.ci.meta.JavaKind;
+import jdk.vm.ci.meta.JavaType;
+import jdk.vm.ci.meta.JavaValue;
+import jdk.vm.ci.meta.LineNumberTable;
+import jdk.vm.ci.meta.Local;
+import jdk.vm.ci.meta.LocalVariableTable;
+import jdk.vm.ci.meta.MetaAccessProvider;
+import jdk.vm.ci.meta.PrimitiveConstant;
+import jdk.vm.ci.meta.ResolvedJavaField;
+import jdk.vm.ci.meta.ResolvedJavaMethod;
+import jdk.vm.ci.meta.ResolvedJavaType;
+import jdk.vm.ci.meta.Signature;
+import jdk.vm.ci.meta.Value;
+import org.graalvm.collections.Pair;
+import org.graalvm.word.WordBase;
+
+import java.lang.reflect.Modifier;
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Stream;
+
+
+public abstract class SharedDebugInfoProvider implements DebugInfoProvider {
+
+    protected final RuntimeConfiguration runtimeConfiguration;
+    protected final MetaAccessProvider metaAccess;
+    protected final UniqueShortNameProvider nameProvider;
+    protected final boolean useHeapBase;
+    protected final int compressionShift;
+    protected final int referenceSize;
+    protected final int pointerSize;
+    protected final int objectAlignment;
+    protected final int reservedBitsMask;
+
+    protected final SharedType hubType;
+    protected final SharedType wordBaseType;
+    protected final SharedType voidType;
+
+    private final ConcurrentHashMap<Path, DirEntry> dirIndex = new ConcurrentHashMap<>();
+    private final ConcurrentHashMap<Path, FileEntry> fileIndex = new ConcurrentHashMap<>();
+    private final ConcurrentHashMap<String, LoaderEntry> loaderIndex = new ConcurrentHashMap<>();
+    private final ConcurrentHashMap<SharedType, TypeEntry> typeIndex = new ConcurrentHashMap<>();
+    private final ConcurrentHashMap<SharedMethod, MethodEntry> methodIndex = new ConcurrentHashMap<>();
+    private final ConcurrentHashMap<CompilationIdentifier, CompiledMethodEntry> compiledMethodIndex = new ConcurrentHashMap<>();
+    protected final StringTable stringTable = new StringTable();
+
+    /* Ensure we have a null string and  in the string section. */
+    protected final String uniqueNullStringEntry = stringTable.uniqueDebugString("");
+    protected final String cachePathEntry;
+
+    private HeaderTypeEntry headerTypeEntry;
+
+
+    /*
+     * A prefix used to label indirect types used to ensure gdb performs oop reference --> raw
+     * address translation
+     */
+    public static final String INDIRECT_PREFIX = "_z_.";
+    /*
+     * A prefix used for type signature generation to generate unique type signatures for type
+     * layout type units
+     */
+    public static final String LAYOUT_PREFIX = "_layout_.";
+
+
+    public static final String CLASS_CONSTANT_SUFFIX = ".class";
+
+    static final Path EMPTY_PATH = Paths.get("");
+    
+    public SharedDebugInfoProvider(RuntimeConfiguration runtimeConfiguration, MetaAccessProvider metaAccess, UniqueShortNameProvider nameProvider, Path cachePath) {
+        this.runtimeConfiguration = runtimeConfiguration;
+        this.metaAccess = metaAccess;
+        this.nameProvider = nameProvider;
+        this.cachePathEntry = stringTable.uniqueDebugString(cachePath.toString());
+        this.hubType = (SharedType) metaAccess.lookupJavaType(Class.class);
+        this.wordBaseType = (SharedType) metaAccess.lookupJavaType(WordBase.class);
+        this.voidType = (SharedType) metaAccess.lookupJavaType(Void.class);
+        this.pointerSize = ConfigurationValues.getTarget().wordSize;
+        ObjectHeader objectHeader = Heap.getHeap().getObjectHeader();
+        this.reservedBitsMask = objectHeader.getReservedBitsMask();
+        this.useHeapBase = ReferenceAccess.singleton().haveCompressedReferences() && ReferenceAccess.singleton().getCompressEncoding().hasBase();
+        this.compressionShift = ReferenceAccess.singleton().getCompressionShift();
+        this.referenceSize = getObjectLayout().getReferenceSize();
+        this.objectAlignment = getObjectLayout().getAlignment();
+    }
+
+    protected abstract Stream<SharedType> typeInfo();
+    protected abstract Stream<Pair<SharedMethod, CompilationResult>> codeInfo();
+    protected abstract Stream<Object> dataInfo();
+
+    @Override
+    public boolean useHeapBase() {
+        return useHeapBase;
+    }
+
+    @Override
+    public boolean isRuntimeCompilation() {
+        return false;
+    }
+
+    @Override
+    public int reservedBitsMask() {
+        return reservedBitsMask;
+    }
+
+    @Override
+    public int compressionShift() {
+        return compressionShift;
+    }
+
+    @Override
+    public int referenceSize() {
+        return referenceSize;
+    }
+
+    @Override
+    public int pointerSize() {
+        return pointerSize;
+    }
+
+    @Override
+    public int objectAlignment() {
+        return objectAlignment;
+    }
+
+    @Override
+    public int compiledCodeMax() {
+        return 0;
+    }
+
+    @Override
+    public String cachePath() {
+        return cachePathEntry;
+    }
+
+    @Override
+    public StringTable getStringTable() {
+        return stringTable;
+    }
+
+    @Override
+    public List<TypeEntry> typeEntries() {
+        // we will always create the headerTypeEntry, as it will be used as superclass for object
+        return Stream.concat(Stream.of(headerTypeEntry), typeIndex.values().stream()).toList();
+    }
+
+    @Override
+    public List<CompiledMethodEntry> compiledMethodEntries() {
+        return compiledMethodIndex.values().stream().toList();
+    }
+
+    /* TODO create debug info providers for debugging sections */
+
+    /* Functions for installing debug info into the index maps. */
+    @Override
+    public void installDebugInfo() {
+        // create and index an empty dir with index 0 for null paths.
+        lookupDirEntry(EMPTY_PATH);
+
+        // handle types, compilations and data
+        typeInfo().parallel().forEach(this::handleTypeInfo);
+        codeInfo().parallel().forEach(pair -> handleCodeInfo(pair.getLeft(), pair.getRight()));
+        dataInfo().parallel().forEach(this::handleDataInfo);
+
+        // create the header type
+        lookupHeaderTypeEntry();
+    }
+
+    private void handleDataInfo(Object data) {
+
+    }
+    private void handleTypeInfo(SharedType type) {
+        lookupTypeEntry(type);
+    }
+    private void handleCodeInfo(SharedMethod method, CompilationResult compilation) {
+        MethodEntry methodEntry = lookupMethodEntry(method);
+        lookupCompiledMethodEntry(methodEntry, method, compilation);
+    }
+
+    protected CompiledMethodEntry installCompilationInfo(MethodEntry methodEntry, SharedMethod method, CompilationResult compilation) {
+        methodEntry.setInRange();
+
+        int primaryLine = methodEntry.getLine();
+        int frameSize = compilation.getTotalFrameSize();
+        List<FrameSizeChangeEntry> frameSizeChanges = getFrameSizeChanges(compilation);
+        ClassEntry ownerType = methodEntry.getOwnerType();
+        PrimaryRange primaryRange = Range.createPrimary(methodEntry, 0, compilation.getTargetCodeSize(), primaryLine, getCodeOffset(method));
+        CompiledMethodEntry compiledMethodEntry = new CompiledMethodEntry(primaryRange, frameSizeChanges, frameSize, ownerType);
+        if (compiledMethodIndex.putIfAbsent(compilation.getCompilationId(), compiledMethodEntry) == null) {
+            // can we still provide locals if we have no file name?
+            if (methodEntry.getFileName().isEmpty()) {
+                return compiledMethodEntry;
+            }
+
+            boolean omitInline = false; // SubstrateOptions.OmitInlinedMethodDebugLineInfo.getValue();
+            int maxDepth = Integer.MAX_VALUE; // SubstrateOptions.DebugCodeInfoMaxDepth.getValue();
+            boolean useSourceMappings = false; // SubstrateOptions.DebugCodeInfoUseSourceMappings.getValue();
+//            if (omitInline) {
+//                if (!SubstrateOptions.DebugCodeInfoMaxDepth.hasBeenSet()) {
+//                    /* TopLevelVisitor will not go deeper than level 2 */
+//                    maxDepth = 2;
+//                }
+//                if (!SubstrateOptions.DebugCodeInfoUseSourceMappings.hasBeenSet()) {
+//                    /* Skip expensive CompilationResultFrameTree building with SourceMappings */
+//                    useSourceMappings = false;
+//                }
+//            }
+            final CompilationResultFrameTree.CallNode root = new CompilationResultFrameTree.Builder(DebugContext.forCurrentThread(), compilation.getTargetCodeSize(), maxDepth, useSourceMappings, true).build(compilation);
+            if (root == null) {
+                return compiledMethodEntry;
+            }
+            final List<Range> subRanges = new ArrayList<>();
+            final CompilationResultFrameTree.Visitor visitor = (omitInline ? new TopLevelVisitor(subRanges, frameSize, primaryRange) : new MultiLevelVisitor(subRanges, frameSize, primaryRange));
+            // arguments passed by visitor to apply are
+            // NativeImageDebugLocationInfo caller location info
+            // CallNode nodeToEmbed parent call node to convert to entry code leaf
+            // NativeImageDebugLocationInfo leaf into which current leaf may be merged
+            root.visitChildren(visitor, primaryRange, null, null);
+            // try to add a location record for offset zero
+            updateInitialLocation(primaryRange, subRanges, compilation, method);
+
+            ownerType.addCompiledMethod(compiledMethodEntry);
+        }
+
+        return compiledMethodEntry;
+    }
+
+    public List<FrameSizeChangeEntry> getFrameSizeChanges(CompilationResult compilation) {
+        List<FrameSizeChangeEntry> frameSizeChanges = new ArrayList<>();
+        for (CompilationResult.CodeMark mark : compilation.getMarks()) {
+            /* We only need to observe stack increment or decrement points. */
+            if (mark.id.equals(SubstrateBackend.SubstrateMarkId.PROLOGUE_DECD_RSP)) {
+                FrameSizeChangeEntry sizeChange = new FrameSizeChangeEntry(mark.pcOffset, FrameSizeChangeType.EXTEND);
+                frameSizeChanges.add(sizeChange);
+                // } else if (mark.id.equals("PROLOGUE_END")) {
+                // can ignore these
+                // } else if (mark.id.equals("EPILOGUE_START")) {
+                // can ignore these
+            } else if (mark.id.equals(SubstrateBackend.SubstrateMarkId.EPILOGUE_INCD_RSP)) {
+                FrameSizeChangeEntry sizeChange = new FrameSizeChangeEntry(mark.pcOffset, FrameSizeChangeType.CONTRACT);
+                frameSizeChanges.add(sizeChange);
+            } else if (mark.id.equals(SubstrateBackend.SubstrateMarkId.EPILOGUE_END) && mark.pcOffset < compilation.getTargetCodeSize()) {
+                /* There is code after this return point so notify a stack extend again. */
+                FrameSizeChangeEntry sizeChange = new FrameSizeChangeEntry(mark.pcOffset, FrameSizeChangeType.EXTEND);
+                frameSizeChanges.add(sizeChange);
+            }
+        }
+        return frameSizeChanges;
+    }
+
+    protected abstract long getCodeOffset(SharedMethod method);
+
+    protected MethodEntry installMethodEntry(SharedMethod method) {
+        FileEntry fileEntry = lookupFileEntry(method);
+
+        LineNumberTable lineNumberTable = method.getLineNumberTable();
+        int line = lineNumberTable == null ? 0 : lineNumberTable.getLineNumber(0);
+
+        String methodName = getMethodName(method); // stringTable.uniqueDebugString(getMethodName(method));
+        StructureTypeEntry ownerType = (StructureTypeEntry) lookupTypeEntry((SharedType) method.getDeclaringClass());
+        assert ownerType instanceof ClassEntry;
+        TypeEntry valueType = lookupTypeEntry((SharedType) method.getSignature().getReturnType(null));
+        int modifiers = getModifiers(method);
+
+        List<LocalEntry> paramInfos = getParamEntries(method, line);
+        int firstLocalSlot = paramInfos.isEmpty() ? 0 : paramInfos.getLast().slot() + paramInfos.getLast().kind().getSlotCount();
+        LocalEntry thisParam = Modifier.isStatic(modifiers) ? null : paramInfos.removeFirst();
+
+        String symbolName = getSymbolName(method); //stringTable.uniqueDebugString(getSymbolName(method));
+        int vTableOffset = getVTableOffset(method);
+
+        boolean isDeopt = isDeopt(method, methodName);
+        boolean isOverride = isOverride(method);
+        boolean isConstructor = isConstructor(method);
+
+        return methodIndex.computeIfAbsent(method, m -> new MethodEntry(fileEntry, line, methodName, ownerType,
+                valueType, modifiers, paramInfos, thisParam, symbolName, isDeopt, isOverride, isConstructor,
+                vTableOffset, firstLocalSlot));
+    }
+
+    private TypeEntry installTypeEntry(SharedType type) {
+        TypeEntry typeEntry = createTypeEntry(type);
+        if (typeIndex.putIfAbsent(type, typeEntry) == null) {
+            /* Add class constant name to string table. */
+            if (typeEntry.getClassOffset() != -1) {
+                // stringTable.uniqueDebugString(typeEntry.getTypeName() + CLASS_CONSTANT_SUFFIX);
+            }
+            // typeEntry was added to the type index, now we need to process the type
+            processTypeEntry(type, typeEntry);
+        }
+        return typeEntry;
+    }
+
+    protected abstract TypeEntry createTypeEntry(SharedType type);
+    protected abstract void processTypeEntry(SharedType type, TypeEntry typeEntry);
+
+    protected void installHeaderTypeEntry() {
+        ObjectLayout ol = getObjectLayout();
+
+        int hubOffset = ol.getHubOffset();
+
+        String typeName = "_objhdr"; // stringTable.uniqueDebugString("_objhdr");
+        long typeSignature = getTypeSignature(typeName);
+
+        headerTypeEntry = new HeaderTypeEntry(typeName, ol.getFirstFieldOffset(), typeSignature);
+
+        headerTypeEntry.addField(createSyntheticFieldEntry("hub", headerTypeEntry, hubType, hubOffset, referenceSize));
+        if (ol.isIdentityHashFieldInObjectHeader()) {
+            int idHashSize = ol.sizeInBytes(JavaKind.Int);
+            headerTypeEntry.addField(createSyntheticFieldEntry("idHash", headerTypeEntry, (SharedType) metaAccess.lookupJavaType(JavaKind.Int.toJavaClass()), ol.getObjectHeaderIdentityHashOffset(), idHashSize));
+        }
+    }
+    
+    protected FieldEntry createSyntheticFieldEntry(String name, StructureTypeEntry ownerType, SharedType type, int offset, int size) {
+        //stringTable.uniqueDebugString(name);
+        TypeEntry typeEntry = lookupTypeEntry(type);
+        return new FieldEntry(null, name, ownerType, typeEntry, size, offset, false, Modifier.PUBLIC);
+    }
+
+    protected FieldEntry createFieldEntry(FileEntry fileEntry, String name, StructureTypeEntry ownerType, SharedType type, int offset, int size, boolean isEmbedded, int modifier) {
+        //stringTable.uniqueDebugString(name);
+        TypeEntry typeEntry = lookupTypeEntry(type);
+        return new FieldEntry(fileEntry, name, ownerType, typeEntry, size, offset, isEmbedded, modifier);
+    }
+
+
+    public long getTypeSignature(String typeName) {
+        return Digest.digestAsUUID(typeName).getLeastSignificantBits();
+    }
+
+    public String getMethodName(SharedMethod method) {
+        String name = method.getName();
+        if (name.equals("<init>")) {
+            name = method.format("%h");
+            if (name.indexOf('$') >= 0) {
+                name = name.substring(name.lastIndexOf('$') + 1);
+            }
+        }
+        return name;
+    }
+
+    public List<LocalEntry> getParamEntries(SharedMethod method, int line) {
+        Signature signature = method.getSignature();
+        int parameterCount = signature.getParameterCount(false);
+        List<LocalEntry> paramInfos = new ArrayList<>(parameterCount);
+        LocalVariableTable table = method.getLocalVariableTable();
+        int slot = 0;
+        SharedType ownerType = (SharedType) method.getDeclaringClass();
+        if (!method.isStatic()) {
+            JavaKind kind = ownerType.getJavaKind();
+            JavaKind storageKind = isForeignWordType(ownerType, ownerType) ? JavaKind.Long : kind;
+            assert kind == JavaKind.Object : "must be an object";
+            paramInfos.add(new LocalEntry("this", lookupTypeEntry(ownerType), storageKind, slot, line));
+            slot += kind.getSlotCount();
+        }
+        for (int i = 0; i < parameterCount; i++) {
+            Local local = (table == null ? null : table.getLocal(slot, 0));
+            String name = local != null ? local.getName() : "__" + i; // stringTable.uniqueDebugString(local != null ? local.getName() : "__" + i);
+            SharedType paramType = (SharedType) signature.getParameterType(i, null);
+            JavaKind kind = paramType.getJavaKind();
+            JavaKind storageKind = isForeignWordType(paramType, ownerType) ? JavaKind.Long : kind;
+            paramInfos.add(new LocalEntry(name, lookupTypeEntry(paramType), storageKind, slot, line));
+            slot += kind.getSlotCount();
+        }
+        return paramInfos;
+    }
+
+    public int getModifiers(SharedMethod method) {
+        return method.getModifiers();
+    }
+
+    public String getSymbolName(SharedMethod method) {
+        return nameProvider.uniqueShortName(null, method.getDeclaringClass(), method.getName(), method.getSignature(), method.isConstructor());
+    }
+
+    public boolean isDeopt(SharedMethod method, String methodName) {
+        if (method instanceof SharedMethod sharedMethod) {
+            return sharedMethod.isDeoptTarget();
+        }
+        return methodName.endsWith(StableMethodNameFormatter.MULTI_METHOD_KEY_SEPARATOR);
+    }
+
+
+    public boolean isOverride(SharedMethod method) {
+        return false;
+    }
+
+
+    public boolean isConstructor(SharedMethod method) {
+        return method.isConstructor();
+    }
+
+    public boolean isVirtual(SharedMethod method) {
+        return false;
+    }
+
+
+    public int getVTableOffset(SharedMethod method) {
+        return isVirtual(method) ? method.getVTableIndex() : -1;
+    }
+
+    /* Lookup functions for indexed debug info entries. */
+
+    public HeaderTypeEntry lookupHeaderTypeEntry() {
+        if (headerTypeEntry == null) {
+            installHeaderTypeEntry();
+        }
+        return headerTypeEntry;
+    }
+
+    public MethodEntry lookupMethodEntry(SharedMethod method) {
+        if (method == null) {
+            return null;
+        }
+        MethodEntry methodEntry = methodIndex.get(method);
+        if (methodEntry == null) {
+            methodEntry = installMethodEntry(method);
+            methodEntry.getOwnerType().addMethod(methodEntry);
+        }
+        return methodEntry;
+    }
+
+
+    public CompiledMethodEntry lookupCompiledMethodEntry(MethodEntry methodEntry, SharedMethod method, CompilationResult compilation) {
+        if (method == null) {
+            return null;
+        }
+        CompiledMethodEntry compiledMethodEntry = compiledMethodIndex.get(compilation.getCompilationId());
+        if (compiledMethodEntry == null) {
+            compiledMethodEntry = installCompilationInfo(methodEntry, method, compilation);
+        }
+        return compiledMethodEntry;
+    }
+
+
+    public TypeEntry lookupTypeEntry(SharedType type) {
+        if (type == null) {
+            // this must be the header type entry, as it is the only one with no underlying
+            return lookupHeaderTypeEntry();
+        }
+        TypeEntry typeEntry = typeIndex.get(type);
+        if (typeEntry == null) {
+            typeEntry = installTypeEntry(type);
+        }
+        return typeEntry;
+    }
+
+
+    public LoaderEntry lookupLoaderEntry(SharedType type) {
+        if (type.isArray()) {
+            type = (SharedType) type.getElementalType();
+        }
+        return type.getHub().isLoaded() ? lookupLoaderEntry(nameProvider.uniqueShortLoaderName(type.getHub().getClassLoader())) : null;
+    }
+
+    public LoaderEntry lookupLoaderEntry(String loaderName) {
+        if (loaderName == null || loaderName.isEmpty()) {
+            return null;
+        }
+        return loaderIndex.computeIfAbsent(loaderName, LoaderEntry::new);
+    }
+
+    public FileEntry lookupFileEntry(ResolvedJavaType type) {
+        return lookupFileEntry(fullFilePathFromClassName(type));
+    }
+
+    public FileEntry lookupFileEntry(ResolvedJavaMethod method) {
+        return lookupFileEntry(method.getDeclaringClass());
+    }
+
+    public FileEntry lookupFileEntry(ResolvedJavaField field) {
+        return lookupFileEntry(field.getDeclaringClass());
+    }
+
+    public FileEntry lookupFileEntry(Path fullFilePath) {
+        if (fullFilePath == null) {
+            return null;
+        }
+
+        String fileName = fullFilePath.getFileName().toString(); //stringTable.uniqueDebugString(fullFilePath.getFileName().toString());
+        Path dirPath = fullFilePath.getParent();
+
+        DirEntry dirEntry = lookupDirEntry(dirPath);
+
+        /* Reuse any existing entry if available. */
+        FileEntry fileEntry = fileIndex.computeIfAbsent(fullFilePath, path -> new FileEntry(fileName, dirEntry));
+        assert dirPath == null || fileEntry.dirEntry() != null && fileEntry.dirEntry().path().equals(dirPath);
+        return fileEntry;
+    }
+
+    public DirEntry lookupDirEntry(Path dirPath) {
+        if (dirPath == null) {
+            dirPath = EMPTY_PATH;
+        }
+
+        // stringTable.uniqueDebugString(dirPath.toString());
+        return dirIndex.computeIfAbsent(dirPath, DirEntry::new);
+    }
+
+
+    /* Other helper functions. */
+    protected static ObjectLayout getObjectLayout() {
+        return ConfigurationValues.getObjectLayout();
+    }
+
+    protected static Path fullFilePathFromClassName(ResolvedJavaType type) {
+        String[] elements = type.toJavaName().split("\\.");
+        int count = elements.length;
+        String name = elements[count - 1];
+        while (name.startsWith("$")) {
+            name = name.substring(1);
+        }
+        if (name.contains("$")) {
+            name = name.substring(0, name.indexOf('$'));
+        }
+        if (name.isEmpty()) {
+            name = "_nofile_";
+        }
+        elements[count - 1] = name + ".java";
+        return FileSystems.getDefault().getPath("", elements);
+    }
+
+
+    /**
+     * Identify a Java type which is being used to model a foreign memory word or pointer type.
+     *
+     * @param type the type to be tested
+     * @param accessingType another type relative to which the first type may need to be resolved
+     * @return true if the type models a foreign memory word or pointer type
+     */
+    protected boolean isForeignWordType(JavaType type, SharedType accessingType) {
+        SharedType resolvedJavaType = (SharedType) type.resolve(accessingType);
+        return isForeignWordType(resolvedJavaType);
+    }
+
+    /**
+     * Identify a hosted type which is being used to model a foreign memory word or pointer type.
+     *
+     * @param type the type to be tested
+     * @return true if the type models a foreign memory word or pointer type
+     */
+    protected boolean isForeignWordType(SharedType type) {
+        return wordBaseType.isAssignableFrom(type);
+    }
+
+
+    private int findMarkOffset(SubstrateBackend.SubstrateMarkId markId, CompilationResult compilation) {
+        for (CompilationResult.CodeMark mark : compilation.getMarks()) {
+            if (mark.id.equals(markId)) {
+                return mark.pcOffset;
+            }
+        }
+        return -1;
+    }
+
+    private void updateInitialLocation(PrimaryRange primary, List<Range> locationInfos, CompilationResult compilation, SharedMethod method) {
+        int prologueEnd = findMarkOffset(SubstrateBackend.SubstrateMarkId.PROLOGUE_END, compilation);
+        if (prologueEnd < 0) {
+            // this is not a normal compiled method so give up
+            return;
+        }
+        int stackDecrement = findMarkOffset(SubstrateBackend.SubstrateMarkId.PROLOGUE_DECD_RSP, compilation);
+        if (stackDecrement < 0) {
+            // this is not a normal compiled method so give up
+            return;
+        }
+        // if there are any location info records then the first one will be for
+        // a nop which follows the stack decrement, stack range check and pushes
+        // of arguments into the stack frame.
+        //
+        // We can construct synthetic location info covering the first instruction
+        // based on the method arguments and the calling convention and that will
+        // normally be valid right up to the nop. In exceptional cases a call
+        // might pass arguments on the stack, in which case the stack decrement will
+        // invalidate the original stack locations. Providing location info for that
+        // case requires adding two locations, one for initial instruction that does
+        // the stack decrement and another for the range up to the nop. They will
+        // be essentially the same but the stack locations will be adjusted to account
+        // for the different value of the stack pointer.
+
+        if (locationInfos.isEmpty()) {
+            // this is not a normal compiled method so give up
+            return;
+        }
+        Range firstLocation = locationInfos.getFirst();
+        int firstLocationOffset = firstLocation.getLoOffset();
+
+        if (firstLocationOffset == 0) {
+            // this is not a normal compiled method so give up
+            return;
+        }
+        if (firstLocationOffset < prologueEnd) {
+            // this is not a normal compiled method so give up
+            return;
+        }
+        // create a synthetic location record including details of passed arguments
+        ParamLocationProducer locProducer = new ParamLocationProducer(method);
+        MethodEntry methodEntry = lookupMethodEntry(method);
+        Range locationInfo = Range.createSubrange(primary, methodEntry, 0, firstLocationOffset, methodEntry.getLine(), primary, true, true);
+
+        locationInfo.setLocalValueInfo(initSyntheticInfoList(locProducer, method));
+
+        // if the prologue extends beyond the stack extend and uses the stack then the info
+        // needs
+        // splitting at the extent point with the stack offsets adjusted in the new info
+        if (locProducer.usesStack() && firstLocationOffset > stackDecrement) {
+            Range splitLocationInfo = locationInfo.split(stackDecrement, compilation.getTotalFrameSize(), PRE_EXTEND_FRAME_SIZE);
+            locationInfos.addFirst(splitLocationInfo);
+        }
+        locationInfos.addFirst(locationInfo);
+    }
+
+    private List<LocalValueEntry> initSyntheticInfoList(ParamLocationProducer locProducer, SharedMethod method) {
+        Signature signature = method.getSignature();
+        int parameterCount = signature.getParameterCount(false);
+        ArrayList<LocalValueEntry> localInfos = new ArrayList<>();
+        LocalVariableTable table = method.getLocalVariableTable();
+        LineNumberTable lineNumberTable = method.getLineNumberTable();
+        int firstLine = (lineNumberTable != null ? lineNumberTable.getLineNumber(0) : -1);
+        int slot = 0;
+        SharedType ownerType = (SharedType) method.getDeclaringClass();
+        if (!method.isStatic()) {
+            String name = "this"; //stringTable.uniqueDebugString("this");
+            JavaKind kind = ownerType.getJavaKind();
+            JavaKind storageKind = isForeignWordType(ownerType, ownerType) ? JavaKind.Long : kind;
+            assert kind == JavaKind.Object : "must be an object";
+            JavaValue value = locProducer.thisLocation();
+            TypeEntry type = lookupTypeEntry(ownerType);
+            LocalEntry local = lookupMethodEntry(method).lookupLocalEntry(name, slot, type, storageKind, firstLine);
+            localInfos.add(createLocalValueEntry(name, value, PRE_EXTEND_FRAME_SIZE, storageKind, type, slot, firstLine, local));
+            slot += kind.getSlotCount();
+        }
+        for (int i = 0; i < parameterCount; i++) {
+            Local local = (table == null ? null : table.getLocal(slot, 0));
+            String name = local != null ? local.getName() : "__" + i; //stringTable.uniqueDebugString(local != null ? local.getName() : "__" + i);
+            SharedType paramType = (SharedType) signature.getParameterType(i, ownerType);
+            JavaKind kind = paramType.getJavaKind();
+            JavaKind storageKind = isForeignWordType(paramType, ownerType) ? JavaKind.Long : kind;
+            JavaValue value = locProducer.paramLocation(i);
+            TypeEntry type = lookupTypeEntry(paramType);
+            LocalEntry localEntry = lookupMethodEntry(method).lookupLocalEntry(name, slot, type, storageKind, firstLine);
+            localInfos.add(createLocalValueEntry(name, value, PRE_EXTEND_FRAME_SIZE, storageKind, type, slot, firstLine, localEntry));
+            slot += kind.getSlotCount();
+        }
+        return localInfos;
+    }
+
+
+    /**
+     * Size in bytes of the frame at call entry before any stack extend. Essentially this accounts
+     * for any automatically pushed return address whose presence depends upon the architecture.
+     */
+    static final int PRE_EXTEND_FRAME_SIZE = ConfigurationValues.getTarget().arch.getReturnAddressSize();
+
+    /**
+     * Retrieve details of the native calling convention for a top level compiled method, including
+     * details of which registers or stack slots are used to pass parameters.
+     *
+     * @param method The method whose calling convention is required.
+     * @return The calling convention for the method.
+     */
+    protected SubstrateCallingConvention getCallingConvention(SharedMethod method) {
+        SubstrateCallingConventionKind callingConventionKind = method.getCallingConventionKind();
+        ResolvedJavaType declaringClass = method.getDeclaringClass();
+        ResolvedJavaType receiverType = method.isStatic() ? null : declaringClass;
+        Signature signature = method.getSignature();
+        final SubstrateCallingConventionType type;
+        if (callingConventionKind.isCustom()) {
+            type = method.getCustomCallingConventionType();
+        } else {
+            type = callingConventionKind.toType(false);
+        }
+        Backend backend = runtimeConfiguration.lookupBackend(method);
+        RegisterConfig registerConfig = backend.getCodeCache().getRegisterConfig();
+        assert registerConfig instanceof SubstrateRegisterConfig;
+        return (SubstrateCallingConvention) registerConfig.getCallingConvention(type, signature.getReturnType(null), signature.toParameterTypes(receiverType), backend);
+    }
+
+    class ParamLocationProducer {
+        private final SharedMethod method;
+        private final CallingConvention callingConvention;
+        private boolean usesStack;
+
+        ParamLocationProducer(SharedMethod method) {
+            this.method = method;
+            this.callingConvention = getCallingConvention(method);
+            // assume no stack slots until we find out otherwise
+            this.usesStack = false;
+        }
+
+        JavaValue thisLocation() {
+            assert !method.isStatic();
+            return unpack(callingConvention.getArgument(0));
+        }
+
+        JavaValue paramLocation(int paramIdx) {
+            assert paramIdx < method.getSignature().getParameterCount(false);
+            int idx = paramIdx;
+            if (!method.isStatic()) {
+                idx++;
+            }
+            return unpack(callingConvention.getArgument(idx));
+        }
+
+        private JavaValue unpack(AllocatableValue value) {
+            if (value instanceof RegisterValue registerValue) {
+                return registerValue;
+            } else {
+                // call argument must be a stack slot if it is not a register
+                StackSlot stackSlot = (StackSlot) value;
+                this.usesStack = true;
+                // the calling convention provides offsets from the SP relative to the current
+                // frame size. At the point of call the frame may or may not include a return
+                // address depending on the architecture.
+                return stackSlot;
+            }
+        }
+
+        public boolean usesStack() {
+            return usesStack;
+        }
+    }
+
+
+    // indices for arguments passed to SingleLevelVisitor::apply
+    protected static final int CALLER_INFO = 0;
+    protected static final int PARENT_NODE_TO_EMBED = 1;
+    protected static final int LAST_LEAF_INFO = 2;
+
+    private abstract class SingleLevelVisitor implements CompilationResultFrameTree.Visitor {
+
+        protected final List<Range> locationInfos;
+        protected final int frameSize;
+
+        protected final PrimaryRange primary;
+
+        SingleLevelVisitor(List<Range> locationInfos, int frameSize, PrimaryRange primary) {
+            this.locationInfos = locationInfos;
+            this.frameSize = frameSize;
+            this.primary = primary;
+        }
+
+        @Override
+        public void apply(CompilationResultFrameTree.FrameNode node, Object... args) {
+            if (node instanceof CompilationResultFrameTree.CallNode && skipPos(node.frame)) {
+                node.visitChildren(this, args);
+            } else {
+                CallRange callerInfo = (CallRange) args[CALLER_INFO];
+                CompilationResultFrameTree.CallNode nodeToEmbed = (CompilationResultFrameTree.CallNode) args[PARENT_NODE_TO_EMBED];
+                handleNodeToEmbed(nodeToEmbed, node, callerInfo, args);
+                Range locationInfo = process(node, callerInfo);
+                if (node instanceof CompilationResultFrameTree.CallNode callNode) {
+                    assert locationInfo instanceof CallRange;
+                    locationInfos.add(locationInfo);
+                    // erase last leaf (if present) since there is an intervening call range
+                    args[LAST_LEAF_INFO] = null;
+                    // handle inlined methods in implementors
+                    handleCallNode(callNode, (CallRange) locationInfo);
+                } else {
+                    // last leaf node added at this level is 3rd element of arg vector
+                    Range lastLeaf = (Range) args[LAST_LEAF_INFO];
+                    if (lastLeaf == null || !lastLeaf.tryMerge(locationInfo)) {
+                        // update last leaf and add new leaf to local info list
+                        args[LAST_LEAF_INFO] = locationInfo;
+                        locationInfos.add(locationInfo);
+                    }
+                }
+            }
+        }
+
+        protected abstract void handleCallNode(CompilationResultFrameTree.CallNode callNode, CallRange locationInfo);
+        protected abstract void handleNodeToEmbed(CompilationResultFrameTree.CallNode nodeToEmbed, CompilationResultFrameTree.FrameNode node, CallRange callerInfo, Object... args);
+
+        public Range process(CompilationResultFrameTree.FrameNode node, CallRange callerInfo) {
+            BytecodePosition pos;
+            boolean isLeaf = true;
+            if (node instanceof CompilationResultFrameTree.CallNode callNode) {
+                // this node represents an inline call range so
+                // add a location info to cover the range of the call
+                pos = callNode.frame.getCaller();
+                while (skipPos(pos)) {
+                    pos = pos.getCaller();
+                }
+                isLeaf = false;
+            } else if (isBadLeaf(node, callerInfo)) {
+                pos = node.frame.getCaller();
+                assert pos != null : "bad leaf must have a caller";
+                assert pos.getCaller() == null : "bad leaf caller must be root method";
+            } else {
+                pos = node.frame;
+            }
+
+            SharedMethod method = (SharedMethod) pos.getMethod();
+            MethodEntry methodEntry = lookupMethodEntry(method);
+
+            LineNumberTable lineNumberTable = method.getLineNumberTable();
+            int line = lineNumberTable == null ? -1 : lineNumberTable.getLineNumber(pos.getBCI());
+
+            Range locationInfo = Range.createSubrange(primary, methodEntry, node.getStartPos(), node.getEndPos() + 1, line, callerInfo, isLeaf);
+
+            locationInfo.setLocalValueInfo(initLocalInfoList(pos, methodEntry, frameSize));
+
+            return locationInfo;
+        }
+    }
+
+    protected List<LocalValueEntry> initLocalInfoList(BytecodePosition pos, MethodEntry method, int frameSize) {
+        List<LocalValueEntry> localInfos = new ArrayList<>();
+
+        if (pos instanceof BytecodeFrame frame && frame.numLocals > 0) {
+            // deal with any inconsistencies in the layout of the frame locals
+            LineNumberTable lineNumberTable = frame.getMethod().getLineNumberTable();
+
+            LocalVariableTable lvt = pos.getMethod().getLocalVariableTable();
+            Local[] localsBySlot = null;
+            if (lvt != null) {
+                Local[] locals = lvt.getLocalsAt(pos.getBCI());
+                if (locals != null && locals.length > 0) {
+                    localsBySlot = Arrays.copyOf(locals, locals.length);
+                    Arrays.sort(localsBySlot, Comparator.comparingInt(Local::getSlot));
+                }
+            }
+
+            if (localsBySlot != null) {
+                int count = Integer.min(localsBySlot.length, frame.numLocals);
+                for (int i = 0; i < count; i++) {
+                    Local l = localsBySlot[i];
+                    if (l != null) {
+                        // we have a local with a known name, type and slot
+                        String name = l.getName(); //stringTable.uniqueDebugString(l.getName());
+                        SharedType ownerType = (SharedType) pos.getMethod().getDeclaringClass();
+                        SharedType type = (SharedType) l.getType().resolve(ownerType);
+                        JavaKind kind = type.getJavaKind();
+                        int slot = l.getSlot();
+                        JavaValue value = (slot < frame.numLocals ? frame.getLocalValue(slot) : Value.ILLEGAL);
+                        JavaKind storageKind = (slot < frame.numLocals ? frame.getLocalValueKind(slot) : JavaKind.Illegal);
+                        int bciStart = l.getStartBCI();
+                        int firstLine = (lineNumberTable != null ? lineNumberTable.getLineNumber(bciStart) : -1);
+                        // only add the local if the kinds match
+                        if ((storageKind == kind) ||
+                                isIntegralKindPromotion(storageKind, kind) ||
+                                (isForeignWordType(type, ownerType) && kind == JavaKind.Object && storageKind == JavaKind.Long)) {
+                            TypeEntry typeEntry = lookupTypeEntry(type);
+                            LocalEntry localEntry = method.lookupLocalEntry(name, slot, typeEntry, storageKind, firstLine);
+                            localInfos.add(createLocalValueEntry(name, value, frameSize, storageKind, typeEntry, slot, firstLine, localEntry));
+                        }
+                    }
+                }
+            }
+        }
+
+        return localInfos;
+    }
+
+    private LocalValueEntry createLocalValueEntry(String name, JavaValue value, int frameSize, JavaKind kind, TypeEntry type, int slot, int line, LocalEntry local) {
+        LocalValueKind localKind;
+        int regIndex = -1;
+        int stackSlot = -1;
+        long heapOffset = -1;
+        JavaConstant constant = null;
+
+        switch (value) {
+            case RegisterValue registerValue -> {
+                localKind = LocalValueKind.REGISTER;
+                regIndex = registerValue.getRegister().number;
+            }
+            case StackSlot stackValue -> {
+                localKind = LocalValueKind.STACK;
+                stackSlot = frameSize == 0 ? stackValue.getRawOffset() : stackValue.getOffset(frameSize);
+            }
+            case JavaConstant constantValue -> {
+                if (constantValue instanceof PrimitiveConstant || constantValue.isNull()) {
+                    localKind = LocalValueKind.CONSTANT;
+                    constant = constantValue;
+                } else {
+                    heapOffset = objectOffset(constantValue);
+                    if (heapOffset >= 0) {
+                        localKind = LocalValueKind.CONSTANT;
+                        constant = constantValue;
+                    } else {
+                        localKind = LocalValueKind.UNDEFINED;
+                    }
+                }
+            }
+            case null, default -> localKind = LocalValueKind.UNDEFINED;
+        }
+
+        return new LocalValueEntry(line, name, type, regIndex, stackSlot, heapOffset, constant, localKind, local);
+    }
+
+
+    public abstract long objectOffset(JavaConstant constant);
+
+    private static boolean isIntegralKindPromotion(JavaKind promoted, JavaKind original) {
+        return (promoted == JavaKind.Int &&
+                (original == JavaKind.Boolean || original == JavaKind.Byte || original == JavaKind.Short || original == JavaKind.Char));
+    }
+
+    private class TopLevelVisitor extends SingleLevelVisitor {
+        TopLevelVisitor(List<Range> locationInfos, int frameSize, PrimaryRange primary) {
+            super(locationInfos, frameSize, primary);
+        }
+
+        @Override
+        protected void handleCallNode(CompilationResultFrameTree.CallNode callNode, CallRange locationInfo) {
+            // nothing to do here
+        }
+
+        @Override
+        protected void handleNodeToEmbed(CompilationResultFrameTree.CallNode nodeToEmbed, CompilationResultFrameTree.FrameNode node, CallRange callerInfo, Object... args) {
+            // nothing to do here
+        }
+    }
+
+    public class MultiLevelVisitor extends SingleLevelVisitor {
+        MultiLevelVisitor(List<Range> locationInfos, int frameSize, PrimaryRange primary) {
+            super(locationInfos, frameSize, primary);
+        }
+
+        @Override
+        protected void handleCallNode(CompilationResultFrameTree.CallNode callNode, CallRange locationInfo) {
+            if (hasChildren(callNode)) {
+                // a call node may include an initial leaf range for the call that must
+                // be
+                // embedded under the newly created location info so pass it as an
+                // argument
+                callNode.visitChildren(this, locationInfo, callNode, null);
+            } else {
+                // we need to embed a leaf node for the whole call range
+                locationInfos.add(createEmbeddedParentLocationInfo(primary, callNode, null, locationInfo, frameSize));
+            }
+        }
+
+        @Override
+        protected void handleNodeToEmbed(CompilationResultFrameTree.CallNode nodeToEmbed, CompilationResultFrameTree.FrameNode node, CallRange callerInfo, Object... args) {
+            if (nodeToEmbed != null) {
+                // we only need to insert a range for the caller if it fills a gap
+                // at the start of the caller range before the first child
+                if (nodeToEmbed.getStartPos() < node.getStartPos()) {
+                    // embed a leaf range for the method start that was included in the
+                    // parent CallNode
+                    // its end range is determined by the start of the first node at this
+                    // level
+                    Range embeddedLocationInfo = createEmbeddedParentLocationInfo(primary, nodeToEmbed, node, callerInfo, frameSize);
+                    locationInfos.add(embeddedLocationInfo);
+                    // since this is a leaf node we can merge later leafs into it
+                    args[LAST_LEAF_INFO] = embeddedLocationInfo;
+                }
+                // reset args so we only embed the parent node before the first node at
+                // this level
+                args[PARENT_NODE_TO_EMBED] = null;
+            }
+        }
+    }
+
+    /**
+     * Report whether a call node has any children.
+     *
+     * @param callNode the node to check
+     * @return true if it has any children otherwise false.
+     */
+    private static boolean hasChildren(CompilationResultFrameTree.CallNode callNode) {
+        Object[] result = new Object[]{false};
+        callNode.visitChildren((node, args) -> args[0] = true, result);
+        return (boolean) result[0];
+    }
+
+    /**
+     * Create a location info record for the initial range associated with a parent call node
+     * whose position and start are defined by that call node and whose end is determined by the
+     * first child of the call node.
+     *
+     * @param parentToEmbed a parent call node which has already been processed to create the
+     *            caller location info
+     * @param firstChild the first child of the call node
+     * @param callerLocation the location info created to represent the range for the call
+     * @return a location info to be embedded as the first child range of the caller location.
+     */
+    private Range createEmbeddedParentLocationInfo(PrimaryRange primary, CompilationResultFrameTree.CallNode parentToEmbed, CompilationResultFrameTree.FrameNode firstChild, CallRange callerLocation, int frameSize) {
+        BytecodePosition pos = parentToEmbed.frame;
+        int startPos = parentToEmbed.getStartPos();
+        int endPos = (firstChild != null ? firstChild.getStartPos() : parentToEmbed.getEndPos() + 1);
+
+        SharedMethod method = (SharedMethod) pos.getMethod();
+        MethodEntry methodEntry = lookupMethodEntry(method);
+
+        LineNumberTable lineNumberTable = method.getLineNumberTable();
+        int line = lineNumberTable == null ? -1 : lineNumberTable.getLineNumber(pos.getBCI());
+
+        Range newRange = Range.createSubrange(primary, methodEntry, startPos, endPos, line, callerLocation, true);
+
+        newRange.setLocalValueInfo(initLocalInfoList(pos, methodEntry, frameSize));
+
+        return newRange;
+    }
+
+    private static boolean isBadLeaf(CompilationResultFrameTree.FrameNode node, CallRange callerLocation) {
+        // Sometimes we see a leaf node marked as belonging to an inlined method
+        // that sits directly under the root method rather than under a call node.
+        // It needs replacing with a location info for the root method that covers
+        // the relevant code range.
+        if (callerLocation.isPrimary()) {
+            BytecodePosition pos = node.frame;
+            BytecodePosition callerPos = pos.getCaller();
+            if (callerPos != null && !callerPos.getMethod().equals(pos.getMethod())) {
+                return callerPos.getCaller() == null;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Test whether a bytecode position represents a bogus frame added by the compiler when a
+     * substitution or snippet call is injected.
+     *
+     * @param pos the position to be tested
+     * @return true if the frame is bogus otherwise false
+     */
+    private static boolean skipPos(BytecodePosition pos) {
+        return pos.getBCI() == -1 && pos instanceof NodeSourcePosition sourcePos && sourcePos.isSubstitution();
+    }
+}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
index 63744a90bb41..94c5c169ccfc 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
@@ -1,14 +1,20 @@
 package com.oracle.svm.core.debug;
 
+import com.oracle.objectfile.BasicNobitsSectionImpl;
+import com.oracle.objectfile.ObjectFile;
+import com.oracle.objectfile.SectionName;
 import com.oracle.svm.core.Uninterruptible;
 import com.oracle.svm.core.c.NonmovableArray;
 import com.oracle.svm.core.c.NonmovableArrays;
 import com.oracle.svm.core.code.InstalledCodeObserver;
 import com.oracle.svm.core.graal.meta.RuntimeConfiguration;
 import com.oracle.svm.core.meta.SharedMethod;
+import com.oracle.svm.core.nmt.NmtCategory;
+import com.oracle.svm.core.os.VirtualMemoryProvider;
 import com.oracle.svm.core.thread.VMOperation;
 import com.oracle.svm.core.util.VMError;
 import jdk.graal.compiler.code.CompilationResult;
+import jdk.graal.compiler.core.common.NumUtil;
 import jdk.graal.compiler.debug.DebugContext;
 import jdk.vm.ci.meta.MetaAccessProvider;
 import org.graalvm.nativeimage.ImageSingletons;
@@ -19,11 +25,21 @@
 import org.graalvm.nativeimage.c.type.CCharPointer;
 import org.graalvm.nativeimage.impl.UnmanagedMemorySupport;
 import org.graalvm.word.Pointer;
-import org.graalvm.word.WordFactory;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
 
 public class SubstrateDebugInfoInstaller implements InstalledCodeObserver {
 
     private final SubstrateDebugInfoProvider substrateDebugInfoProvider;
+    private final DebugContext debugContext;
+    private final ObjectFile objectFile;
+    private final ArrayList<ObjectFile.Element> sortedObjectFileElements;
+    private final int debugInfoSize;
 
     static final class Factory implements InstalledCodeObserver.Factory {
 
@@ -46,7 +62,31 @@ public InstalledCodeObserver create(DebugContext debugContext, SharedMethod meth
     }
 
     private SubstrateDebugInfoInstaller(DebugContext debugContext, SharedMethod method, CompilationResult compilation, MetaAccessProvider metaAccess, RuntimeConfiguration runtimeConfiguration, Pointer code, int codeSize) {
-        substrateDebugInfoProvider = new SubstrateDebugInfoProvider(debugContext, method, compilation, runtimeConfiguration, code.rawValue(), codeSize);
+        this.debugContext = debugContext;
+        substrateDebugInfoProvider = new SubstrateDebugInfoProvider(method, compilation, runtimeConfiguration, metaAccess, code.rawValue());
+
+        int pageSize = NumUtil.safeToInt(ImageSingletons.lookup(VirtualMemoryProvider.class).getGranularity().rawValue());
+        objectFile = ObjectFile.createRuntimeDebugInfo(pageSize);
+        objectFile.newNobitsSection(SectionName.TEXT.getFormatDependentName(objectFile.getFormat()), new BasicNobitsSectionImpl(codeSize));
+        objectFile.installDebugInfo(substrateDebugInfoProvider);
+        sortedObjectFileElements = new ArrayList<>();
+        debugInfoSize = objectFile.bake(sortedObjectFileElements);
+
+        if (debugContext.isLogEnabled()) {
+            dumpObjectFile();
+        }
+    }
+
+    private void dumpObjectFile() {
+        StringBuilder sb = new StringBuilder(substrateDebugInfoProvider.getCompilationName()).append(".debug");
+        try (FileChannel dumpFile = FileChannel.open(Paths.get(sb.toString()),
+                StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING,
+                StandardOpenOption.CREATE)) {
+            ByteBuffer buffer = dumpFile.map(FileChannel.MapMode.READ_WRITE, 0, debugInfoSize);
+            objectFile.writeBuffer(sortedObjectFileElements, buffer);
+        } catch (IOException e) {
+            debugContext.log("Failed to dump %s", sb);
+        }
     }
 
     @RawStructure
@@ -139,26 +179,30 @@ public void releaseOnTearDown(InstalledCodeObserverHandle installedCodeObserverH
             // UnmanagedMemory.free(handle); -> change because of Uninterruptible annotation
             ImageSingletons.lookup(UnmanagedMemorySupport.class).free(handle);
         }
-
-        static String toString(Handle handle) {
-            StringBuilder sb = new StringBuilder("DebugInfoHandle(handle = 0x");
-            sb.append(Long.toHexString(handle.getRawHandle().rawValue()));
-            sb.append(", address = 0x");
-            sb.append(Long.toHexString(NonmovableArrays.addressOf(handle.getDebugInfoData(), 0).rawValue()));
-            sb.append(", size = ");
-            sb.append(NonmovableArrays.lengthOf(handle.getDebugInfoData()));
-            sb.append(", handleState = ");
-            sb.append(handle.getState());
-            sb.append(")");
-            return sb.toString();
-        }
     }
 
     @Override
     public InstalledCodeObserverHandle install() {
-        NonmovableArray<Byte> debugInfoData = substrateDebugInfoProvider.writeDebugInfoData();
+        NonmovableArray<Byte> debugInfoData = writeDebugInfoData();
         Handle handle = Accessor.createHandle(debugInfoData);
-        System.out.println(Accessor.toString(handle));
+        System.out.println(toString(handle));
         return handle;
     }
+
+    private NonmovableArray<Byte> writeDebugInfoData() {
+        NonmovableArray<Byte> array = NonmovableArrays.createByteArray(debugInfoSize, NmtCategory.Code);
+        objectFile.writeBuffer(sortedObjectFileElements, NonmovableArrays.asByteBuffer(array));
+        return array;
+    }
+
+    private static String toString(Handle handle) {
+        return "DebugInfoHandle(handle = 0x" + Long.toHexString(handle.getRawHandle().rawValue()) +
+                ", address = 0x" +
+                Long.toHexString(NonmovableArrays.addressOf(handle.getDebugInfoData(), 0).rawValue()) +
+                ", size = " +
+                NonmovableArrays.lengthOf(handle.getDebugInfoData()) +
+                ", handleState = " +
+                handle.getState() +
+                ")";
+    }
 }
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
index 723c01d3a435..e375e3fbdf0a 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
@@ -1,1858 +1,179 @@
+/*
+ * Copyright (c) 2024, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
 package com.oracle.svm.core.debug;
 
-import com.oracle.objectfile.BasicNobitsSectionImpl;
-import com.oracle.objectfile.ObjectFile;
-import com.oracle.objectfile.SectionName;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider;
-import com.oracle.objectfile.runtime.RuntimeDebugInfoProvider;
+import com.oracle.objectfile.debugentry.ArrayTypeEntry;
+import com.oracle.objectfile.debugentry.ClassEntry;
+import com.oracle.objectfile.debugentry.FileEntry;
+import com.oracle.objectfile.debugentry.LoaderEntry;
+import com.oracle.objectfile.debugentry.PrimitiveTypeEntry;
+import com.oracle.objectfile.debugentry.TypeEntry;
 import com.oracle.svm.core.SubstrateOptions;
-import com.oracle.svm.core.c.NonmovableArray;
-import com.oracle.svm.core.c.NonmovableArrays;
-import com.oracle.svm.core.code.CompilationResultFrameTree;
-import com.oracle.svm.core.config.ConfigurationValues;
-import com.oracle.svm.core.graal.code.SubstrateBackend;
-import com.oracle.svm.core.graal.code.SubstrateCallingConvention;
-import com.oracle.svm.core.graal.code.SubstrateCallingConventionKind;
-import com.oracle.svm.core.graal.code.SubstrateCallingConventionType;
+import com.oracle.svm.core.SubstrateUtil;
 import com.oracle.svm.core.graal.meta.RuntimeConfiguration;
-import com.oracle.svm.core.graal.meta.SubstrateRegisterConfig;
-import com.oracle.svm.core.heap.Heap;
-import com.oracle.svm.core.heap.ObjectHeader;
 import com.oracle.svm.core.meta.SharedMethod;
-import com.oracle.svm.core.nmt.NmtCategory;
-import com.oracle.svm.core.os.VirtualMemoryProvider;
+import com.oracle.svm.core.meta.SharedType;
 import jdk.graal.compiler.code.CompilationResult;
-import jdk.graal.compiler.core.common.CompressEncoding;
-import jdk.graal.compiler.core.common.NumUtil;
-import jdk.graal.compiler.core.target.Backend;
-import jdk.graal.compiler.debug.DebugContext;
-import jdk.graal.compiler.graph.NodeSourcePosition;
-import jdk.graal.compiler.java.StableMethodNameFormatter;
-import jdk.graal.compiler.util.Digest;
-import jdk.vm.ci.code.BytecodeFrame;
-import jdk.vm.ci.code.BytecodePosition;
-import jdk.vm.ci.code.CallingConvention;
-import jdk.vm.ci.code.RegisterConfig;
-import jdk.vm.ci.code.RegisterValue;
-import jdk.vm.ci.code.StackSlot;
-import jdk.vm.ci.meta.AllocatableValue;
 import jdk.vm.ci.meta.JavaConstant;
 import jdk.vm.ci.meta.JavaKind;
-import jdk.vm.ci.meta.JavaValue;
-import jdk.vm.ci.meta.LineNumberTable;
-import jdk.vm.ci.meta.Local;
-import jdk.vm.ci.meta.LocalVariableTable;
-import jdk.vm.ci.meta.PrimitiveConstant;
-import jdk.vm.ci.meta.ResolvedJavaMethod;
+import jdk.vm.ci.meta.MetaAccessProvider;
 import jdk.vm.ci.meta.ResolvedJavaType;
-import jdk.vm.ci.meta.Signature;
-import jdk.vm.ci.meta.Value;
+import org.graalvm.collections.Pair;
 import org.graalvm.nativeimage.ImageSingletons;
 
-import java.io.IOException;
-import java.lang.reflect.Modifier;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
 import java.nio.file.FileSystems;
 import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.nio.file.StandardOpenOption;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Objects;
-import java.util.function.Consumer;
 import java.util.stream.Stream;
 
-import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFrameSizeChange.Type.CONTRACT;
-import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFrameSizeChange.Type.EXTEND;
 
+public class SubstrateDebugInfoProvider extends SharedDebugInfoProvider {
 
-public class SubstrateDebugInfoProvider implements RuntimeDebugInfoProvider {
-
-    private final DebugContext debugContext;
-    private final ResolvedJavaMethod method;
+    private final SharedMethod method;
     private final CompilationResult compilation;
     private final long codeAddress;
-    private final int codeSize;
-
-    private final RuntimeConfiguration runtimeConfiguration;
-    private final int pointerSize;
-    private final int tagsMask;
-    private final boolean useHeapBase;
-    private final int compressShift;
-    private final int referenceSize;
-    private final int referenceAlignment;
 
-    private final ObjectFile objectFile;
-    private final List<ObjectFile.Element> sortedObjectFileElements;
-    private final int debugInfoSize;
 
-
-    public SubstrateDebugInfoProvider(DebugContext debugContext, ResolvedJavaMethod method, CompilationResult compilation, RuntimeConfiguration runtimeConfiguration, long codeAddress, int codeSize) {
-        this.debugContext = debugContext;
+    public SubstrateDebugInfoProvider(SharedMethod method, CompilationResult compilation, RuntimeConfiguration runtimeConfiguration, MetaAccessProvider metaAccess, long codeAddress) {
+        super(runtimeConfiguration, metaAccess, ImageSingletons.lookup(SubstrateBFDNameProvider.class), SubstrateOptions.getRuntimeSourceDestDir());
         this.method = method;
         this.compilation = compilation;
         this.codeAddress = codeAddress;
-        this.codeSize = codeSize;
-
-        this.runtimeConfiguration = runtimeConfiguration;
-        this.pointerSize = ConfigurationValues.getTarget().wordSize;
-        ObjectHeader objectHeader = Heap.getHeap().getObjectHeader();
-        this.tagsMask = objectHeader.getReservedBitsMask();
-        CompressEncoding compressEncoding = ImageSingletons.lookup(CompressEncoding.class);
-        this.useHeapBase = compressEncoding.hasBase();
-        this.compressShift = (compressEncoding.hasShift() ? compressEncoding.getShift() : 0);
-        this.referenceSize = ConfigurationValues.getObjectLayout().getReferenceSize();
-        this.referenceAlignment = ConfigurationValues.getObjectLayout().getAlignment();
-
-        int pageSize = NumUtil.safeToInt(ImageSingletons.lookup(VirtualMemoryProvider.class).getGranularity().rawValue());
-        objectFile = ObjectFile.createRuntimeDebugInfo(pageSize);
-        objectFile.newNobitsSection(SectionName.TEXT.getFormatDependentName(objectFile.getFormat()), new BasicNobitsSectionImpl(codeSize));
-
-        objectFile.installRuntimeDebugInfo(this);
-
-        sortedObjectFileElements = new ArrayList<>();
-        debugInfoSize = objectFile.bake(sortedObjectFileElements);
-        dumpObjectFile();
-    }
-
-    public NonmovableArray<Byte> writeDebugInfoData() {
-        NonmovableArray<Byte> array = NonmovableArrays.createByteArray(debugInfoSize, NmtCategory.Code);
-        objectFile.writeBuffer(sortedObjectFileElements, NonmovableArrays.asByteBuffer(array));
-        return array;
     }
 
-    @Override
-    public String getCompilationUnitName() {
-        String name = (compilation != null) ? compilation.getName() : null;
-        if (name == null && method != null) {
-            name = method.format("%H.%n(%p)");
+    public String getCompilationName() {
+        String name = null;
+        if (compilation != null) {
+            name = compilation.getName();
         }
-        if (name == null || name.isEmpty()) {
-            name = "UnnamedCU";
+        if ((name == null || name.isEmpty()) && method != null) {
+            name = method.format("%h.%n");
         }
-        name += " at 0x" + Long.toHexString(codeAddress);
-        return name;
-    }
-
-    private void dumpObjectFile() {
-        StringBuilder sb = new StringBuilder();
-        sb.append(method.getName()).append('@').append(Long.toHexString(codeAddress)).append(".debug");
-        try (FileChannel dumpFile = FileChannel.open(Paths.get(sb.toString()),
-                StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING,
-                StandardOpenOption.CREATE)) {
-            ByteBuffer buffer = dumpFile.map(FileChannel.MapMode.READ_WRITE, 0, debugInfoSize);
-            objectFile.writeBuffer(sortedObjectFileElements, buffer);
-        } catch (IOException e) {
-            debugContext.log("Failed to dump %s", sb);
+        if (name == null || name.isEmpty()) {
+            name = "UnnamedCompilation";
         }
+        return name + "@0x" + Long.toHexString(codeAddress);
     }
 
     @Override
-    public boolean useHeapBase() {
-        return useHeapBase;
-    }
-
-    @Override
-    public int oopCompressShift() {
-        return compressShift;
-    }
-
-    @Override
-    public int oopTagsMask() {
-        return tagsMask;
-    }
-
-    @Override
-    public int oopReferenceSize() {
-        return referenceSize;
-    }
-
-    @Override
-    public int pointerSize() {
-        return pointerSize;
+    public boolean isRuntimeCompilation() {
+        return true;
     }
 
     @Override
-    public int oopAlignment() {
-        return referenceAlignment;
+    protected Stream<SharedType> typeInfo() {
+        // create type infos on demand for compilation
+        return Stream.empty();
     }
 
     @Override
-    public int compiledCodeMax() {
-        return 0;
+    protected Stream<Pair<SharedMethod, CompilationResult>> codeInfo() {
+        return Stream.of(Pair.create(method, compilation));
     }
 
     @Override
-    public DebugInfoProvider.DebugCodeInfo codeInfoProvider() {
-        return new SubstrateDebugCodeInfo(method, compilation);
+    protected Stream<Object> dataInfo() {
+        // no data info needed for runtime compilations
+        return Stream.empty();
     }
 
     @Override
-    public DebugInfoProvider.DebugTypeInfo createDebugTypeInfo(ResolvedJavaType javaType) {
-
-        try (DebugContext.Scope s = debugContext.scope("DebugTypeInfo", javaType.toJavaName())) {
-            if (javaType.isInstanceClass()) {
-                return new SubstrateDebugInstanceTypeInfo(javaType);
-            } else if (javaType.isInterface()) {
-                return new SubstrateDebugInterfaceTypeInfo(javaType);
-            } else if (javaType.isArray()) {
-                return new SubstrateDebugArrayTypeInfo(javaType);
-            } else if (javaType.isPrimitive()) {
-                return new SubstrateDebugPrimitiveTypeInfo(javaType);
-            } else {
-                System.out.println(javaType.getName());
-                return new SubstrateDebugForeignTypeInfo(javaType);
-            }
-        } catch (Throwable e) {
-            throw debugContext.handle(e);
-        }
+    protected long getCodeOffset(SharedMethod method) {
+        return codeAddress;
     }
 
     @Override
-    public Path getCachePath() {
-        return SubstrateOptions.getRuntimeSourceDestDir();
-    }
-
-    @Override
-    public void recordActivity() {
-
-    }
-
-    private class SubstrateDebugFileInfo implements DebugInfoProvider.DebugFileInfo {
-        private final Path fullFilePath;
-        private final String fileName;
-
-        SubstrateDebugFileInfo(ResolvedJavaMethod method) {
-            this(method.getDeclaringClass());
-        }
-
-        SubstrateDebugFileInfo(ResolvedJavaType type) {
-            fullFilePath = fullFilePathFromClassName(type);
-            fileName = type.getSourceFileName();
-        }
-
-        private Path fullFilePathFromClassName(ResolvedJavaType type) {
-            String[] elements = type.toJavaName().split("\\.");
-            int count = elements.length;
-            String name = elements[count - 1];
-            while (name.startsWith("$")) {
-                name = name.substring(1);
-            }
-            if (name.contains("$")) {
-                name = name.substring(0, name.indexOf('$'));
-            }
-            if (name.isEmpty()) {
-                name = "_nofile_";
-            }
-            elements[count - 1] = name + ".java";
-            return FileSystems.getDefault().getPath("", elements);
-        }
-
-        @Override
-        public String fileName() {
-            return fileName;
-        }
-
-        @Override
-        public Path filePath() {
-            if (fullFilePath != null) {
-                return fullFilePath.getParent();
-            }
-            return null;
-        }
-    }
-
-
-    // actually unused currently
-    private abstract class SubstrateDebugTypeInfo extends SubstrateDebugFileInfo implements DebugInfoProvider.DebugTypeInfo {
-        protected final ResolvedJavaType type;
-
-        SubstrateDebugTypeInfo(ResolvedJavaType type) {
-            super(type);
-            this.type = type;
-        }
-
-        @Override
-        public long typeSignature(String prefix) {
-            return Digest.digestAsUUID(prefix + typeName()).getLeastSignificantBits();
-        }
-
-        @Override
-        public ResolvedJavaType idType() {
-            return type;
-        }
-
-        @Override
-        public void debugContext(Consumer<DebugContext> action) {
-            try (DebugContext.Scope s = debugContext.scope("DebugTypeInfo", method)) {
-                action.accept(debugContext);
-            } catch (Throwable e) {
-                throw debugContext.handle(e);
-            }
-        }
-
-        @Override
-        public String typeName() {
-            return type.toJavaName();
-        }
-
-        @Override
-        public long classOffset() {
-            // we would need access to the heap/types on the heap
-            return -1;
-        }
-
-        @Override
-        public int size() {
+    public FileEntry lookupFileEntry(ResolvedJavaType type) {
+        if (type instanceof SharedType sharedType) {
+            String[] packageElements = SubstrateUtil.split(sharedType.getHub().getPackageName(), ".");
+            String fileName = sharedType.getSourceFileName();
+            if (fileName != null && !fileName.isEmpty()) {
+                Path filePath = FileSystems.getDefault().getPath("", packageElements).resolve(fileName);
+                return lookupFileEntry(filePath);
+            }
+        }
+        return super.lookupFileEntry(type);
+    }
+
+    private int getTypeSize(SharedType type) {
+        if (type.isPrimitive()) {
+            JavaKind javaKind = type.getStorageKind();
+            return (javaKind == JavaKind.Void ? 0 : javaKind.getByteCount());
+        } else if (type.isArray()) {
+            SharedType componentType = (SharedType) type.getComponentType();
+            return getObjectLayout().getArrayBaseOffset(componentType.getStorageKind());
+        } else if (type.isInterface() || type.isInstanceClass()) {
+            return getObjectLayout().getFirstFieldOffset();
+        } else {
             return 0;
         }
     }
-
-    private class SubstrateDebugEnumTypeInfo extends SubstrateDebugInstanceTypeInfo implements DebugInfoProvider.DebugEnumTypeInfo {
-
-        SubstrateDebugEnumTypeInfo(ResolvedJavaType enumType) {
-            super(enumType);
-        }
-
-        @Override
-        public DebugTypeKind typeKind() {
-            return DebugTypeKind.ENUM;
-        }
-    }
-
-    private class SubstrateDebugInstanceTypeInfo extends SubstrateDebugTypeInfo implements DebugInfoProvider.DebugInstanceTypeInfo {
-        SubstrateDebugInstanceTypeInfo(ResolvedJavaType javaType) {
-            super(javaType);
-        }
-
-        @Override
-        public long typeSignature(String prefix) {
-            return super.typeSignature(prefix + loaderName());
-        }
-
-        @Override
-        public DebugTypeKind typeKind() {
-            return DebugTypeKind.INSTANCE;
-        }
-
-        @Override
-        public String loaderName() {
-            // return UniqueShortNameProvider.singleton().uniqueShortLoaderName(type.getClass().getClassLoader());
-            return ImageSingletons.lookup(SubstrateBFDNameProvider.class).uniqueShortLoaderName(type.getClass().getClassLoader());
-        }
-
-        @Override
-        public Stream<DebugInfoProvider.DebugFieldInfo> fieldInfoProvider() {
-            return Stream.empty();
-        }
-
-        @Override
-        public Stream<DebugInfoProvider.DebugMethodInfo> methodInfoProvider() {
-            return Stream.empty();
-        }
-
-        @Override
-        public ResolvedJavaType superClass() {
-            return type.getSuperclass();
-        }
-
-        @Override
-        public Stream<ResolvedJavaType> interfaces() {
-            // map through getOriginal so we can use the result as an id type
-            return Arrays.stream(type.getInterfaces());
-        }
-    }
-
-    private class SubstrateDebugInterfaceTypeInfo extends SubstrateDebugInstanceTypeInfo implements DebugInfoProvider.DebugInterfaceTypeInfo {
-
-        SubstrateDebugInterfaceTypeInfo(ResolvedJavaType interfaceType) {
-            super(interfaceType);
-        }
-
-        @Override
-        public DebugTypeKind typeKind() {
-            return DebugTypeKind.INTERFACE;
-        }
-    }
-
-    private class SubstrateDebugForeignTypeInfo extends SubstrateDebugInstanceTypeInfo implements DebugInfoProvider.DebugForeignTypeInfo {
-
-        SubstrateDebugForeignTypeInfo(ResolvedJavaType foreignType) {
-            super(foreignType);
-        }
-
-        @Override
-        public DebugTypeKind typeKind() {
-            return DebugTypeKind.FOREIGN;
-        }
-
-        @Override
-        public String typedefName() {
-            return "";
-        }
-
-        @Override
-        public boolean isWord() {
-            return false;
-        }
-
-        @Override
-        public boolean isStruct() {
-            return false;
-        }
-
-        @Override
-        public boolean isPointer() {
-            return false;
-        }
-
-        @Override
-        public boolean isIntegral() {
-            return false;
-        }
-
-        @Override
-        public boolean isFloat() {
-            return false;
-        }
-
-        @Override
-        public boolean isSigned() {
-            return false;
-        }
-
-        @Override
-        public ResolvedJavaType parent() {
-            return null;
-        }
-
-        @Override
-        public ResolvedJavaType pointerTo() {
-            return null;
-        }
-    }
-
-    private class SubstrateDebugArrayTypeInfo extends SubstrateDebugTypeInfo implements DebugInfoProvider.DebugArrayTypeInfo {
-
-        SubstrateDebugArrayTypeInfo(ResolvedJavaType arrayClass) {
-            super(arrayClass);
-        }
-
-        @Override
-        public long typeSignature(String prefix) {
-            ResolvedJavaType elementType = type.getComponentType();
-            while (elementType.isArray()) {
-                elementType = elementType.getComponentType();
-            }
-            String loaderId = "";
-            if (elementType.isInstanceClass() || elementType.isInterface()) {
-                // loaderId = UniqueShortNameProvider.singleton().uniqueShortLoaderName(elementType.getClass().getClassLoader());
-                loaderId = ImageSingletons.lookup(SubstrateBFDNameProvider.class).uniqueShortLoaderName(elementType.getClass().getClassLoader());
-            }
-            return super.typeSignature(prefix + loaderId);
-        }
-
-        @Override
-        public DebugTypeKind typeKind() {
-            return DebugTypeKind.ARRAY;
-        }
-
-        @Override
-        public int baseSize() {
-            return 0;
-        }
-
-        @Override
-        public int lengthOffset() {
-            return 0;
-        }
-
-        @Override
-        public ResolvedJavaType elementType() {
-            return type.getComponentType();
-        }
-
-        @Override
-        public Stream<DebugInfoProvider.DebugFieldInfo> fieldInfoProvider() {
-            return Stream.empty();
-        }
-    }
-
-    private class SubstrateDebugPrimitiveTypeInfo extends SubstrateDebugTypeInfo implements DebugInfoProvider.DebugPrimitiveTypeInfo {
-
-        SubstrateDebugPrimitiveTypeInfo(ResolvedJavaType type) {
-            super(type);
-            assert type.isPrimitive();
-        }
-
-        @Override
-        public DebugTypeKind typeKind() {
-            return DebugTypeKind.PRIMITIVE;
-        }
-
-        @Override
-        public int bitCount() {
-            JavaKind javaKind = type.getJavaKind();
-            return (javaKind == JavaKind.Void ? 0 : javaKind.getBitCount());
-        }
-
-        @Override
-        public char typeChar() {
-            return type.getJavaKind().getTypeChar();
-        }
-
-        @Override
-        public int flags() {
-            char typeChar = typeChar();
-            return switch (typeChar) {
-                case 'B', 'S', 'I', 'J' -> FLAG_NUMERIC | FLAG_INTEGRAL | FLAG_SIGNED;
-                case 'C' -> FLAG_NUMERIC | FLAG_INTEGRAL;
-                case 'F', 'D' -> FLAG_NUMERIC;
-                default -> {
-                    assert typeChar == 'V' || typeChar == 'Z';
-                    yield 0;
-                }
-            };
-        }
-    }
-
-    private class SubstrateDebugMethodInfo extends SubstrateDebugFileInfo implements DebugInfoProvider.DebugMethodInfo {
-        protected final ResolvedJavaMethod method;
-        protected int line;
-        protected final List<DebugInfoProvider.DebugLocalInfo> paramInfo;
-        protected final DebugInfoProvider.DebugLocalInfo thisParamInfo;
-
-        SubstrateDebugMethodInfo(ResolvedJavaMethod method) {
-            super(method);
-            this.method = method;
-            LineNumberTable lineNumberTable = method.getLineNumberTable();
-            this.line = (lineNumberTable != null ? lineNumberTable.getLineNumber(0) : 0);
-            this.paramInfo = createParamInfo(method, line);
-
-            // We use the target modifiers to decide where to install any first param
-            // even though we may have added it according to whether method is static.
-            // That's because in a few special cases method is static but the original
-            // DebugFrameLocals
-            // from which it is derived is an instance method. This appears to happen
-            // when a C function pointer masquerades as a method. Whatever parameters
-            // we pass through need to match the definition of the original.
-            if (Modifier.isStatic(modifiers())) {
-                this.thisParamInfo = null;
+    
+    @Override
+    protected TypeEntry createTypeEntry(SharedType type) {
+        String typeName = type.toJavaName(); //stringTable.uniqueDebugString(idType.toJavaName());
+        LoaderEntry loaderEntry = lookupLoaderEntry(type);
+        int size = getTypeSize(type);
+        long classOffset = -1;
+        String loaderName = loaderEntry == null ? uniqueNullStringEntry : loaderEntry.loaderId();
+        long typeSignature = getTypeSignature(typeName + loaderName);
+        long compressedTypeSignature = useHeapBase ? getTypeSignature(INDIRECT_PREFIX + typeName + loaderName) : typeSignature;
+
+        if (type.isPrimitive()) {
+            JavaKind kind = type.getStorageKind();
+            return new PrimitiveTypeEntry(typeName, kind == JavaKind.Void ? 0 : kind.getByteCount(), classOffset, typeSignature, kind);
+        } else {
+            // otherwise we have a structured type
+            long layoutTypeSignature = getTypeSignature(LAYOUT_PREFIX + typeName + loaderName);
+            long compressedLayoutTypeSignature = useHeapBase ? getTypeSignature(INDIRECT_PREFIX + LAYOUT_PREFIX + typeName + loaderName) : layoutTypeSignature;
+            if (type.isArray()) {
+                TypeEntry elementTypeEntry = lookupTypeEntry((SharedType) type.getComponentType());
+                return new ArrayTypeEntry(typeName, size, classOffset, typeSignature, compressedTypeSignature,
+                        layoutTypeSignature, compressedLayoutTypeSignature, elementTypeEntry, loaderEntry);
             } else {
-                this.thisParamInfo = paramInfo.removeFirst();
-            }
-        }
-
-        private List<DebugInfoProvider.DebugLocalInfo> createParamInfo(ResolvedJavaMethod method, int line) {
-            Signature signature = method.getSignature();
-            int parameterCount = signature.getParameterCount(false);
-            List<DebugInfoProvider.DebugLocalInfo> paramInfos = new ArrayList<>(parameterCount);
-            LocalVariableTable table = method.getLocalVariableTable();
-            int slot = 0;
-            ResolvedJavaType ownerType = method.getDeclaringClass();
-            if (!method.isStatic()) {
-                JavaKind kind = ownerType.getJavaKind();
-                assert kind == JavaKind.Object : "must be an object";
-                paramInfos.add(new SubstrateDebugLocalInfo("this", kind, ownerType, slot, line));
-                slot += kind.getSlotCount();
-            }
-            for (int i = 0; i < parameterCount; i++) {
-                Local local = (table == null ? null : table.getLocal(slot, 0));
-                String name = (local != null ? local.getName() : "__" + i);
-                ResolvedJavaType paramType = (ResolvedJavaType) signature.getParameterType(i, null);
-                JavaKind kind = paramType.getJavaKind();
-                paramInfos.add(new SubstrateDebugLocalInfo(name, kind, paramType, slot, line));
-                slot += kind.getSlotCount();
-            }
-            return paramInfos;
-        }
-
-        @Override
-        public String name() {
-            String name = method.getName();
-            if (name.equals("<init>")) {
-                name = method.format("%h");
-                if (name.indexOf('$') >= 0) {
-                    name = name.substring(name.lastIndexOf('$') + 1);
-                }
-            }
-            return name;
-        }
-
-        @Override
-        public ResolvedJavaType valueType() {
-            return (ResolvedJavaType) method.getSignature().getReturnType(null);
-        }
-
-        @Override
-        public int modifiers() {
-            return method.getModifiers();
-        }
-
-        @Override
-        public int line() {
-            return line;
-        }
-
-        @Override
-        public DebugInfoProvider.DebugLocalInfo[] getParamInfo() {
-            return paramInfo.toArray(new DebugInfoProvider.DebugLocalInfo[]{});
-        }
-
-        @Override
-        public DebugInfoProvider.DebugLocalInfo getThisParamInfo() {
-            return thisParamInfo;
-        }
-
-        @Override
-        public String symbolNameForMethod() {
-            // SubstrateOptions.ImageSymbolsPrefix.getValue() + getUniqueShortName(sm);
-            return ImageSingletons.lookup(SubstrateBFDNameProvider.class).uniqueShortName(null, method.getDeclaringClass(), method.getName(), method.getSignature(), method.isConstructor());
-        }
-
-        @Override
-        public boolean isDeoptTarget() {
-            return name().endsWith(StableMethodNameFormatter.MULTI_METHOD_KEY_SEPARATOR);
-        }
-
-        @Override
-        public boolean isConstructor() {
-            return method.isConstructor();
-        }
-
-        @Override
-        public boolean isVirtual() {
-            return false;
-        }
-
-        @Override
-        public int vtableOffset() {
-            // not virtual
-            return -1;
-        }
-
-        @Override
-        public boolean isOverride() {
-            return false;
-        }
-
-        @Override
-        public ResolvedJavaType ownerType() {
-            return method.getDeclaringClass();
-        }
-
-        @Override
-        public ResolvedJavaMethod idMethod() {
-            return method;
-        }
-    }
-
-    private class SubstrateDebugCodeInfo extends SubstrateDebugMethodInfo implements DebugInfoProvider.DebugCodeInfo {
-        private final CompilationResult compilation;
-
-        SubstrateDebugCodeInfo(ResolvedJavaMethod method, CompilationResult compilation) {
-            super(method);
-            this.compilation = compilation;
-        }
-
-        @SuppressWarnings("try")
-        @Override
-        public void debugContext(Consumer<DebugContext> action) {
-            try (DebugContext.Scope s = debugContext.scope("DebugCodeInfo", method)) {
-                action.accept(debugContext);
-            } catch (Throwable e) {
-                throw debugContext.handle(e);
-            }
-        }
-
-        @Override
-        public long addressLo() {
-            return codeAddress;
-        }
-
-        @Override
-        public long addressHi() {
-            return codeAddress + compilation.getTargetCodeSize();
-        }
-
-        @Override
-        public Stream<DebugInfoProvider.DebugLocationInfo> locationInfoProvider() {
-            int maxDepth = Integer.MAX_VALUE; //SubstrateOptions.DebugCodeInfoMaxDepth.getValue();
-            boolean useSourceMappings = false; //SubstrateOptions.DebugCodeInfoUseSourceMappings.getValue();
-            final CompilationResultFrameTree.CallNode root = new CompilationResultFrameTree.Builder(debugContext, compilation.getTargetCodeSize(), maxDepth, useSourceMappings, true).build(compilation);
-            if (root == null) {
-                return Stream.empty();
-            }
-            final List<DebugInfoProvider.DebugLocationInfo> locationInfos = new ArrayList<>();
-            int frameSize = getFrameSize();
-            final CompilationResultFrameTree.Visitor visitor = new MultiLevelVisitor(locationInfos, frameSize);
-            // arguments passed by visitor to apply are
-            // NativeImageDebugLocationInfo caller location info
-            // CallNode nodeToEmbed parent call node to convert to entry code leaf
-            // NativeImageDebugLocationInfo leaf into which current leaf may be merged
-            root.visitChildren(visitor, (Object) null, (Object) null, (Object) null);
-            // try to add a location record for offset zero
-            updateInitialLocation(locationInfos);
-            return locationInfos.stream();
-        }
-
-        private int findMarkOffset(SubstrateBackend.SubstrateMarkId markId) {
-            for (CompilationResult.CodeMark mark : compilation.getMarks()) {
-                if (mark.id.equals(markId)) {
-                    return mark.pcOffset;
-                }
-            }
-            return -1;
-        }
-
-        private void updateInitialLocation(List<DebugInfoProvider.DebugLocationInfo> locationInfos) {
-            int prologueEnd = findMarkOffset(SubstrateBackend.SubstrateMarkId.PROLOGUE_END);
-            if (prologueEnd < 0) {
-                // this is not a normal compiled method so give up
-                return;
-            }
-            int stackDecrement = findMarkOffset(SubstrateBackend.SubstrateMarkId.PROLOGUE_DECD_RSP);
-            if (stackDecrement < 0) {
-                // this is not a normal compiled method so give up
-                return;
-            }
-            // if there are any location info records then the first one will be for
-            // a nop which follows the stack decrement, stack range check and pushes
-            // of arguments into the stack frame.
-            //
-            // We can construct synthetic location info covering the first instruction
-            // based on the method arguments and the calling convention and that will
-            // normally be valid right up to the nop. In exceptional cases a call
-            // might pass arguments on the stack, in which case the stack decrement will
-            // invalidate the original stack locations. Providing location info for that
-            // case requires adding two locations, one for initial instruction that does
-            // the stack decrement and another for the range up to the nop. They will
-            // be essentially the same but the stack locations will be adjusted to account
-            // for the different value of the stack pointer.
-
-            if (locationInfos.isEmpty()) {
-                // this is not a normal compiled method so give up
-                return;
-            }
-            SubstrateDebugLocationInfo firstLocation = (SubstrateDebugLocationInfo) locationInfos.get(0);
-            long firstLocationOffset = firstLocation.addressLo();
-
-            if (firstLocationOffset == 0) {
-                // this is not a normal compiled method so give up
-                return;
-            }
-            if (firstLocationOffset < prologueEnd) {
-                // this is not a normal compiled method so give up
-                return;
-            }
-            // create a synthetic location record including details of passed arguments
-            ParamLocationProducer locProducer = new ParamLocationProducer(method);
-            debugContext.log(DebugContext.DETAILED_LEVEL, "Add synthetic Location Info : %s (0, %d)", method.getName(), firstLocationOffset - 1);
-            SubstrateDebugLocationInfo locationInfo = new SubstrateDebugLocationInfo(method, firstLocationOffset, locProducer);
-            // if the prologue extends beyond the stack extend and uses the stack then the info
-            // needs
-            // splitting at the extend point with the stack offsets adjusted in the new info
-            if (locProducer.usesStack() && firstLocationOffset > stackDecrement) {
-                SubstrateDebugLocationInfo splitLocationInfo = locationInfo.split(stackDecrement, getFrameSize());
-                debugContext.log(DebugContext.DETAILED_LEVEL, "Split synthetic Location Info : %s (%d, %d) (%d, %d)", locationInfo.name(), 0,
-                        locationInfo.addressLo() - 1, locationInfo.addressLo(), locationInfo.addressHi() - 1);
-                locationInfos.add(0, splitLocationInfo);
-            }
-            locationInfos.add(0, locationInfo);
-        }
-
-        // indices for arguments passed to SingleLevelVisitor::apply
-        protected static final int CALLER_INFO = 0;
-        protected static final int PARENT_NODE_TO_EMBED = 1;
-        protected static final int LAST_LEAF_INFO = 2;
-
-        private abstract class SingleLevelVisitor implements CompilationResultFrameTree.Visitor {
-
-            protected final List<DebugInfoProvider.DebugLocationInfo> locationInfos;
-            protected final int frameSize;
-
-            SingleLevelVisitor(List<DebugInfoProvider.DebugLocationInfo> locationInfos, int frameSize) {
-                this.locationInfos = locationInfos;
-                this.frameSize = frameSize;
-            }
-
-            public SubstrateDebugLocationInfo process(CompilationResultFrameTree.FrameNode node, SubstrateDebugLocationInfo callerInfo) {
-                SubstrateDebugLocationInfo locationInfo;
-                if (node instanceof CompilationResultFrameTree.CallNode) {
-                    // this node represents an inline call range so
-                    // add a locationinfo to cover the range of the call
-                    locationInfo = createCallLocationInfo((CompilationResultFrameTree.CallNode) node, callerInfo, frameSize);
-                } else if (isBadLeaf(node, callerInfo)) {
-                    locationInfo = createBadLeafLocationInfo(node, callerInfo, frameSize);
+                // otherwise this is a class entry
+                ClassEntry superClass = type.getSuperclass() == null ? null : (ClassEntry) lookupTypeEntry((SharedType) type.getSuperclass());
+                FileEntry fileEntry = lookupFileEntry(type);
+                if (isForeignWordType(type)) {
+                    // we just need the correct type signatures here
+                    return new ClassEntry(typeName, size, classOffset, typeSignature, typeSignature, layoutTypeSignature,
+                            layoutTypeSignature, superClass, fileEntry, loaderEntry);
                 } else {
-                    // this is leaf method code so add details of its range
-                    locationInfo = createLeafLocationInfo(node, callerInfo, frameSize);
+                    return new ClassEntry(typeName, size, classOffset, typeSignature, compressedTypeSignature,
+                            layoutTypeSignature, compressedLayoutTypeSignature, superClass, fileEntry, loaderEntry);
                 }
-                return locationInfo;
-            }
-        }
-
-        private class TopLevelVisitor extends SingleLevelVisitor {
-            TopLevelVisitor(List<DebugInfoProvider.DebugLocationInfo> locationInfos, int frameSize) {
-                super(locationInfos, frameSize);
             }
-
-            @Override
-            public void apply(CompilationResultFrameTree.FrameNode node, Object... args) {
-                if (skipNode(node)) {
-                    // this is a bogus wrapper so skip it and transform the wrapped node instead
-                    node.visitChildren(this, args);
-                } else {
-                    SubstrateDebugLocationInfo locationInfo = process(node, null);
-                    if (node instanceof CompilationResultFrameTree.CallNode) {
-                        locationInfos.add(locationInfo);
-                        // erase last leaf (if present) since there is an intervening call range
-                        invalidateMerge(args);
-                    } else {
-                        locationInfo = tryMerge(locationInfo, args);
-                        if (locationInfo != null) {
-                            locationInfos.add(locationInfo);
-                        }
-                    }
-                }
-            }
-        }
-
-        public class MultiLevelVisitor extends SingleLevelVisitor {
-            MultiLevelVisitor(List<DebugInfoProvider.DebugLocationInfo> locationInfos, int frameSize) {
-                super(locationInfos, frameSize);
-            }
-
-            @Override
-            public void apply(CompilationResultFrameTree.FrameNode node, Object... args) {
-                if (skipNode(node)) {
-                    // this is a bogus wrapper so skip it and transform the wrapped node instead
-                    node.visitChildren(this, args);
-                } else {
-                    SubstrateDebugLocationInfo callerInfo = (SubstrateDebugLocationInfo) args[CALLER_INFO];
-                    CompilationResultFrameTree.CallNode nodeToEmbed = (CompilationResultFrameTree.CallNode) args[PARENT_NODE_TO_EMBED];
-                    if (nodeToEmbed != null) {
-                        if (embedWithChildren(nodeToEmbed, node)) {
-                            // embed a leaf range for the method start that was included in the
-                            // parent CallNode
-                            // its end range is determined by the start of the first node at this
-                            // level
-                            SubstrateDebugLocationInfo embeddedLocationInfo = createEmbeddedParentLocationInfo(nodeToEmbed, node, callerInfo, frameSize);
-                            locationInfos.add(embeddedLocationInfo);
-                            // since this is a leaf node we can merge later leafs into it
-                            initMerge(embeddedLocationInfo, args);
-                        }
-                        // reset args so we only embed the parent node before the first node at
-                        // this level
-                        args[PARENT_NODE_TO_EMBED] = nodeToEmbed = null;
-                    }
-                    SubstrateDebugLocationInfo locationInfo = process(node, callerInfo);
-                    if (node instanceof CompilationResultFrameTree.CallNode) {
-                        CompilationResultFrameTree.CallNode callNode = (CompilationResultFrameTree.CallNode) node;
-                        locationInfos.add(locationInfo);
-                        // erase last leaf (if present) since there is an intervening call range
-                        invalidateMerge(args);
-                        if (hasChildren(callNode)) {
-                            // a call node may include an initial leaf range for the call that must
-                            // be
-                            // embedded under the newly created location info so pass it as an
-                            // argument
-                            callNode.visitChildren(this, locationInfo, callNode, (Object) null);
-                        } else {
-                            // we need to embed a leaf node for the whole call range
-                            locationInfo = createEmbeddedParentLocationInfo(callNode, null, locationInfo, frameSize);
-                            locationInfos.add(locationInfo);
-                        }
-                    } else {
-                        locationInfo = tryMerge(locationInfo, args);
-                        if (locationInfo != null) {
-                            locationInfos.add(locationInfo);
-                        }
-                    }
-                }
-            }
-        }
-
-        /**
-         * Report whether a call node has any children.
-         *
-         * @param callNode the node to check
-         * @return true if it has any children otherwise false.
-         */
-        private boolean hasChildren(CompilationResultFrameTree.CallNode callNode) {
-            Object[] result = new Object[]{false};
-            callNode.visitChildren(new CompilationResultFrameTree.Visitor() {
-                @Override
-                public void apply(CompilationResultFrameTree.FrameNode node, Object... args) {
-                    args[0] = true;
-                }
-            }, result);
-            return (boolean) result[0];
-        }
-
-        /**
-         * Create a location info record for a leaf subrange.
-         *
-         * @param node is a simple FrameNode
-         * @return the newly created location info record
-         */
-        private SubstrateDebugLocationInfo createLeafLocationInfo(CompilationResultFrameTree.FrameNode node, SubstrateDebugLocationInfo callerInfo, int framesize) {
-            assert !(node instanceof CompilationResultFrameTree.CallNode);
-            SubstrateDebugLocationInfo locationInfo = new SubstrateDebugLocationInfo(node, callerInfo, framesize);
-            debugContext.log(DebugContext.DETAILED_LEVEL, "Create leaf Location Info : %s depth %d (%d, %d)", locationInfo.name(), locationInfo.depth(), locationInfo.addressLo(),
-                    locationInfo.addressHi() - 1);
-            return locationInfo;
-        }
-
-        /**
-         * Create a location info record for a subrange that encloses an inline call.
-         *
-         * @param callNode is the top level inlined call frame
-         * @return the newly created location info record
-         */
-        private SubstrateDebugLocationInfo createCallLocationInfo(CompilationResultFrameTree.CallNode callNode, SubstrateDebugLocationInfo callerInfo, int framesize) {
-            BytecodePosition callerPos = realCaller(callNode);
-            SubstrateDebugLocationInfo locationInfo = new SubstrateDebugLocationInfo(callerPos, callNode.getStartPos(), callNode.getEndPos() + 1, callerInfo, framesize);
-            debugContext.log(DebugContext.DETAILED_LEVEL, "Create call Location Info : %s depth %d (%d, %d)", locationInfo.name(), locationInfo.depth(), locationInfo.addressLo(),
-                    locationInfo.addressHi() - 1);
-            return locationInfo;
-        }
-
-        /**
-         * Create a location info record for the initial range associated with a parent call node
-         * whose position and start are defined by that call node and whose end is determined by the
-         * first child of the call node.
-         *
-         * @param parentToEmbed a parent call node which has already been processed to create the
-         *            caller location info
-         * @param firstChild the first child of the call node
-         * @param callerLocation the location info created to represent the range for the call
-         * @return a location info to be embedded as the first child range of the caller location.
-         */
-        private SubstrateDebugLocationInfo createEmbeddedParentLocationInfo(CompilationResultFrameTree.CallNode parentToEmbed, CompilationResultFrameTree.FrameNode firstChild, SubstrateDebugLocationInfo callerLocation, int framesize) {
-            BytecodePosition pos = parentToEmbed.frame;
-            int startPos = parentToEmbed.getStartPos();
-            int endPos = (firstChild != null ? firstChild.getStartPos() : parentToEmbed.getEndPos() + 1);
-            SubstrateDebugLocationInfo locationInfo = new SubstrateDebugLocationInfo(pos, startPos, endPos, callerLocation, framesize);
-            debugContext.log(DebugContext.DETAILED_LEVEL, "Embed leaf Location Info : %s depth %d (%d, %d)", locationInfo.name(), locationInfo.depth(), locationInfo.addressLo(),
-                    locationInfo.addressHi() - 1);
-            return locationInfo;
-        }
-
-        private SubstrateDebugLocationInfo createBadLeafLocationInfo(CompilationResultFrameTree.FrameNode node, SubstrateDebugLocationInfo callerLocation, int framesize) {
-            assert !(node instanceof CompilationResultFrameTree.CallNode) : "bad leaf location cannot be a call node!";
-            assert callerLocation == null : "should only see bad leaf at top level!";
-            BytecodePosition pos = node.frame;
-            BytecodePosition callerPos = pos.getCaller();
-            assert callerPos != null : "bad leaf must have a caller";
-            assert callerPos.getCaller() == null : "bad leaf caller must be root method";
-            int startPos = node.getStartPos();
-            int endPos = node.getEndPos() + 1;
-            SubstrateDebugLocationInfo locationInfo = new SubstrateDebugLocationInfo(callerPos, startPos, endPos, null, framesize);
-            debugContext.log(DebugContext.DETAILED_LEVEL, "Embed leaf Location Info : %s depth %d (%d, %d)", locationInfo.name(), locationInfo.depth(), locationInfo.addressLo(),
-                    locationInfo.addressHi() - 1);
-            return locationInfo;
-        }
-
-        private boolean isBadLeaf(CompilationResultFrameTree.FrameNode node, SubstrateDebugLocationInfo callerLocation) {
-            // Sometimes we see a leaf node marked as belonging to an inlined method
-            // that sits directly under the root method rather than under a call node.
-            // It needs replacing with a location info for the root method that covers
-            // the relevant code range.
-            if (callerLocation == null) {
-                BytecodePosition pos = node.frame;
-                BytecodePosition callerPos = pos.getCaller();
-                if (callerPos != null && !callerPos.getMethod().equals(pos.getMethod())) {
-                    if (callerPos.getCaller() == null) {
-                        return true;
-                    }
-                }
-            }
-            return false;
-        }
-
-        /**
-         * Test whether a bytecode position represents a bogus frame added by the compiler when a
-         * substitution or snippet call is injected.
-         *
-         * @param pos the position to be tested
-         * @return true if the frame is bogus otherwise false
-         */
-        private boolean skipPos(BytecodePosition pos) {
-            return (pos.getBCI() == -1 && pos instanceof NodeSourcePosition && ((NodeSourcePosition) pos).isSubstitution());
-        }
-
-        /**
-         * Skip caller nodes with bogus positions, as determined by
-         * {@link #skipPos(BytecodePosition)}, returning first caller node position that is not
-         * bogus.
-         *
-         * @param node the node whose callers are to be traversed
-         * @return the first non-bogus position in the caller chain.
-         */
-        private BytecodePosition realCaller(CompilationResultFrameTree.CallNode node) {
-            BytecodePosition pos = node.frame.getCaller();
-            while (skipPos(pos)) {
-                pos = pos.getCaller();
-            }
-            return pos;
-        }
-
-        /**
-         * Test whether the position associated with a child node should result in an entry in the
-         * inline tree. The test is for a call node with a bogus position as determined by
-         * {@link #skipPos(BytecodePosition)}.
-         *
-         * @param node A node associated with a child frame in the compilation result frame tree.
-         * @return True an entry should be included or false if it should be omitted.
-         */
-        private boolean skipNode(CompilationResultFrameTree.FrameNode node) {
-            return node instanceof CompilationResultFrameTree.CallNode && skipPos(node.frame);
-        }
-
-        /**
-         * Test whether the position associated with a call node frame should be embedded along with
-         * the locations generated for the node's children. This is needed because call frames may
-         * include a valid source position that precedes the first child position.
-         *
-         * @param parent The call node whose children are currently being visited
-         * @param firstChild The first child of that call node
-         * @return true if the node should be embedded otherwise false
-         */
-        private boolean embedWithChildren(CompilationResultFrameTree.CallNode parent, CompilationResultFrameTree.FrameNode firstChild) {
-            // we only need to insert a range for the caller if it fills a gap
-            // at the start of the caller range before the first child
-            if (parent.getStartPos() < firstChild.getStartPos()) {
-                return true;
-            }
-            return false;
-        }
-
-        /**
-         * Try merging a new location info for a leaf range into the location info for the last leaf
-         * range added at this level.
-         *
-         * @param newLeaf the new leaf location info
-         * @param args the visitor argument vector used to pass parameters from one child visit to
-         *            the next possibly including the last leaf
-         * @return the new location info if it could not be merged or null to indicate that it was
-         *         merged
-         */
-        private SubstrateDebugLocationInfo tryMerge(SubstrateDebugLocationInfo newLeaf, Object[] args) {
-            // last leaf node added at this level is 3rd element of arg vector
-            SubstrateDebugLocationInfo lastLeaf = (SubstrateDebugLocationInfo) args[LAST_LEAF_INFO];
-
-            if (lastLeaf != null) {
-                // try merging new leaf into last one
-                lastLeaf = lastLeaf.merge(newLeaf);
-                if (lastLeaf != null) {
-                    // null return indicates new leaf has been merged into last leaf
-                    return null;
-                }
-            }
-            // update last leaf and return new leaf for addition to local info list
-            args[LAST_LEAF_INFO] = newLeaf;
-            return newLeaf;
-        }
-
-        /**
-         * Set the last leaf node at the current level to the supplied leaf node.
-         *
-         * @param lastLeaf the last leaf node created at this level
-         * @param args the visitor argument vector used to pass parameters from one child visit to
-         *            the next
-         */
-        private void initMerge(SubstrateDebugLocationInfo lastLeaf, Object[] args) {
-            args[LAST_LEAF_INFO] = lastLeaf;
-        }
-
-        /**
-         * Clear the last leaf node at the current level from the visitor arguments by setting the
-         * arg vector entry to null.
-         *
-         * @param args the visitor argument vector used to pass parameters from one child visit to
-         *            the next
-         */
-        private void invalidateMerge(Object[] args) {
-            args[LAST_LEAF_INFO] = null;
-        }
-
-        @Override
-        public int getFrameSize() {
-            return compilation.getTotalFrameSize();
-        }
-
-        @Override
-        public List<DebugInfoProvider.DebugFrameSizeChange> getFrameSizeChanges() {
-            List<DebugInfoProvider.DebugFrameSizeChange> frameSizeChanges = new LinkedList<>();
-            for (CompilationResult.CodeMark mark : compilation.getMarks()) {
-                /* We only need to observe stack increment or decrement points. */
-                if (mark.id.equals(SubstrateBackend.SubstrateMarkId.PROLOGUE_DECD_RSP)) {
-                    SubstrateDebugFrameSizeChange sizeChange = new SubstrateDebugFrameSizeChange(mark.pcOffset, EXTEND);
-                    frameSizeChanges.add(sizeChange);
-                    // } else if (mark.id.equals("PROLOGUE_END")) {
-                    // can ignore these
-                    // } else if (mark.id.equals("EPILOGUE_START")) {
-                    // can ignore these
-                } else if (mark.id.equals(SubstrateBackend.SubstrateMarkId.EPILOGUE_INCD_RSP)) {
-                    SubstrateDebugFrameSizeChange sizeChange = new SubstrateDebugFrameSizeChange(mark.pcOffset, CONTRACT);
-                    frameSizeChanges.add(sizeChange);
-                } else if (mark.id.equals(SubstrateBackend.SubstrateMarkId.EPILOGUE_END) && mark.pcOffset < compilation.getTargetCodeSize()) {
-                    /* There is code after this return point so notify a stack extend again. */
-                    SubstrateDebugFrameSizeChange sizeChange = new SubstrateDebugFrameSizeChange(mark.pcOffset, EXTEND);
-                    frameSizeChanges.add(sizeChange);
-                }
-            }
-            return frameSizeChanges;
-        }
-    }
-
-    private class SubstrateDebugLocationInfo extends SubstrateDebugMethodInfo implements DebugInfoProvider.DebugLocationInfo {
-        private final int bci;
-        private long lo;
-        private long hi;
-        private DebugInfoProvider.DebugLocationInfo callersLocationInfo;
-        private List<DebugInfoProvider.DebugLocalValueInfo> localInfoList;
-        private boolean isLeaf;
-
-        SubstrateDebugLocationInfo(CompilationResultFrameTree.FrameNode frameNode, SubstrateDebugLocationInfo callersLocationInfo, int framesize) {
-            this(frameNode.frame, frameNode.getStartPos(), frameNode.getEndPos() + 1, callersLocationInfo, framesize);
-        }
-
-        SubstrateDebugLocationInfo(BytecodePosition bcpos, long lo, long hi, SubstrateDebugLocationInfo callersLocationInfo, int framesize) {
-            super(bcpos.getMethod());
-            this.bci = bcpos.getBCI();
-            this.lo = lo;
-            this.hi = hi;
-            this.callersLocationInfo = callersLocationInfo;
-            this.localInfoList = initLocalInfoList(bcpos, framesize);
-            // assume this is a leaf until we find out otherwise
-            this.isLeaf = true;
-            // tag the caller as a non-leaf
-            if (callersLocationInfo != null) {
-                callersLocationInfo.isLeaf = false;
-            }
-        }
-
-        // special constructor for synthetic location info added at start of method
-        SubstrateDebugLocationInfo(ResolvedJavaMethod method, long hi, ParamLocationProducer locProducer) {
-            super(method);
-            // bci is always 0 and lo is always 0.
-            this.bci = 0;
-            this.lo = 0;
-            this.hi = hi;
-            // this is always going to be a top-level leaf range.
-            this.callersLocationInfo = null;
-            // location info is synthesized off the method signature
-            this.localInfoList = initSyntheticInfoList(locProducer);
-            // assume this is a leaf until we find out otherwise
-            this.isLeaf = true;
-        }
-
-        // special constructor for synthetic location info which splits off the initial segment
-        // of the first range to accommodate a stack access prior to the stack extend
-        SubstrateDebugLocationInfo(SubstrateDebugLocationInfo toSplit, int stackDecrement, int frameSize) {
-            super(toSplit.method);
-            this.lo = stackDecrement;
-            this.hi = toSplit.hi;
-            toSplit.hi = this.lo;
-            this.bci = toSplit.bci;
-            this.callersLocationInfo = toSplit.callersLocationInfo;
-            this.isLeaf = toSplit.isLeaf;
-            toSplit.isLeaf = true;
-            this.localInfoList = new ArrayList<>(toSplit.localInfoList.size());
-            for (DebugInfoProvider.DebugLocalValueInfo localInfo : toSplit.localInfoList) {
-                if (localInfo.localKind() == DebugInfoProvider.DebugLocalValueInfo.LocalKind.STACKSLOT) {
-                    // need to redefine the value for this param using a stack slot value
-                    // that allows for the stack being extended by framesize. however we
-                    // also need to remove any adjustment that was made to allow for the
-                    // difference between the caller SP and the pre-extend callee SP
-                    // because of a stacked return address.
-                    int adjustment = frameSize - PRE_EXTEND_FRAME_SIZE;
-                    SubstrateDebugLocalValue value = SubstrateDebugStackValue.create(localInfo, adjustment);
-                    SubstrateDebugLocalValueInfo nativeLocalInfo = (SubstrateDebugLocalValueInfo) localInfo;
-                    SubstrateDebugLocalValueInfo newLocalinfo = new SubstrateDebugLocalValueInfo(nativeLocalInfo.name,
-                            value,
-                            nativeLocalInfo.kind,
-                            nativeLocalInfo.type,
-                            nativeLocalInfo.slot,
-                            nativeLocalInfo.line);
-                    localInfoList.add(newLocalinfo);
-                } else {
-                    localInfoList.add(localInfo);
-                }
-            }
-        }
-
-        private List<DebugInfoProvider.DebugLocalValueInfo> initLocalInfoList(BytecodePosition bcpos, int framesize) {
-            if (!(bcpos instanceof BytecodeFrame)) {
-                return null;
-            }
-
-            BytecodeFrame frame = (BytecodeFrame) bcpos;
-            if (frame.numLocals == 0) {
-                return null;
-            }
-            // deal with any inconsistencies in the layout of the frame locals
-            // NativeImageDebugFrameInfo debugFrameInfo = new NativeImageDebugFrameInfo(frame);
-
-            LineNumberTable lineNumberTable = frame.getMethod().getLineNumberTable();
-            Local[] localsBySlot = getLocalsBySlot();
-            if (localsBySlot == null) {
-                return Collections.emptyList();
-            }
-            int count = Integer.min(localsBySlot.length, frame.numLocals);
-            ArrayList<DebugInfoProvider.DebugLocalValueInfo> localInfos = new ArrayList<>(count);
-            for (int i = 0; i < count; i++) {
-                Local l = localsBySlot[i];
-                if (l != null) {
-                    // we have a local with a known name, type and slot
-                    String name = l.getName();
-                    ResolvedJavaType ownerType = method.getDeclaringClass();
-                    ResolvedJavaType type = l.getType().resolve(ownerType);
-                    JavaKind kind = type.getJavaKind();
-                    int slot = l.getSlot();
-                    debugContext.log(DebugContext.DETAILED_LEVEL, "locals[%d] %s type %s slot %d", i, name, type.getName(), slot);
-                    JavaValue value = (slot < frame.numLocals ? frame.getLocalValue(slot) : Value.ILLEGAL);
-                    JavaKind storageKind = (slot < frame.numLocals ? frame.getLocalValueKind(slot) : JavaKind.Illegal);
-                    debugContext.log(DebugContext.DETAILED_LEVEL, "  =>  %s kind %s", value, storageKind);
-                    int bciStart = l.getStartBCI();
-                    int firstLine = (lineNumberTable != null ? lineNumberTable.getLineNumber(bciStart) : -1);
-                    // only add the local if the kinds match
-                    if ((storageKind == kind) ||
-                            isIntegralKindPromotion(storageKind, kind) ||
-                            (kind == JavaKind.Object && storageKind == JavaKind.Long)) {
-                        localInfos.add(new SubstrateDebugLocalValueInfo(name, value, framesize, storageKind, type, slot, firstLine));
-                    } else if (storageKind != JavaKind.Illegal) {
-                        debugContext.log(DebugContext.DETAILED_LEVEL, "  value kind incompatible with var kind %s!", type.getJavaKind());
-                    }
-                }
-            }
-            return localInfos;
-        }
-
-        private List<DebugInfoProvider.DebugLocalValueInfo> initSyntheticInfoList(ParamLocationProducer locProducer) {
-            Signature signature = method.getSignature();
-            int parameterCount = signature.getParameterCount(false);
-            ArrayList<DebugInfoProvider.DebugLocalValueInfo> localInfos = new ArrayList<>();
-            LocalVariableTable table = method.getLocalVariableTable();
-            LineNumberTable lineNumberTable = method.getLineNumberTable();
-            int firstLine = (lineNumberTable != null ? lineNumberTable.getLineNumber(0) : -1);
-            int slot = 0;
-            int localIdx = 0;
-            ResolvedJavaType ownerType = method.getDeclaringClass();
-            if (!method.isStatic()) {
-                String name = "this";
-                JavaKind kind = ownerType.getJavaKind();
-                assert kind == JavaKind.Object : "must be an object";
-                SubstrateDebugLocalValue value = locProducer.thisLocation();
-                debugContext.log(DebugContext.DETAILED_LEVEL, "locals[%d] %s type %s slot %d", localIdx, name, ownerType.getName(), slot);
-                debugContext.log(DebugContext.DETAILED_LEVEL, "  =>  %s kind %s", value, kind);
-                localInfos.add(new SubstrateDebugLocalValueInfo(name, value, kind, ownerType, slot, firstLine));
-                slot += kind.getSlotCount();
-                localIdx++;
-            }
-            for (int i = 0; i < parameterCount; i++) {
-                Local local = (table == null ? null : table.getLocal(slot, 0));
-                String name = (local != null ? local.getName() : "__" + i);
-                ResolvedJavaType paramType = (ResolvedJavaType) signature.getParameterType(i, ownerType);
-                JavaKind kind = paramType.getJavaKind();
-                SubstrateDebugLocalValue value = locProducer.paramLocation(i);
-                debugContext.log(DebugContext.DETAILED_LEVEL, "locals[%d] %s type %s slot %d", localIdx, name, ownerType.getName(), slot);
-                debugContext.log(DebugContext.DETAILED_LEVEL, "  =>  %s kind %s", value, kind);
-                localInfos.add(new SubstrateDebugLocalValueInfo(name, value, kind, paramType, slot, firstLine));
-                slot += kind.getSlotCount();
-                localIdx++;
-            }
-            return localInfos;
-        }
-
-        private static boolean isIntegralKindPromotion(JavaKind promoted, JavaKind original) {
-            return (promoted == JavaKind.Int &&
-                    (original == JavaKind.Boolean || original == JavaKind.Byte || original == JavaKind.Short || original == JavaKind.Char));
-        }
-
-        private Local[] getLocalsBySlot() {
-            LocalVariableTable lvt = method.getLocalVariableTable();
-            Local[] nonEmptySortedLocals = null;
-            if (lvt != null) {
-                Local[] locals = lvt.getLocalsAt(bci);
-                if (locals != null && locals.length > 0) {
-                    nonEmptySortedLocals = Arrays.copyOf(locals, locals.length);
-                    Arrays.sort(nonEmptySortedLocals, (Local l1, Local l2) -> l1.getSlot() - l2.getSlot());
-                }
-            }
-            return nonEmptySortedLocals;
-        }
-
-        @Override
-        public long addressLo() {
-            return lo;
-        }
-
-        @Override
-        public long addressHi() {
-            return hi;
-        }
-
-        @Override
-        public int line() {
-            LineNumberTable lineNumberTable = method.getLineNumberTable();
-            if (lineNumberTable != null && bci >= 0) {
-                return lineNumberTable.getLineNumber(bci);
-            }
-            return -1;
-        }
-
-        @Override
-        public DebugInfoProvider.DebugLocationInfo getCaller() {
-            return callersLocationInfo;
-        }
-
-        @Override
-        public DebugInfoProvider.DebugLocalValueInfo[] getLocalValueInfo() {
-            if (localInfoList != null) {
-                return localInfoList.toArray(new DebugInfoProvider.DebugLocalValueInfo[]{});
-            } else {
-                return new DebugInfoProvider.DebugLocalValueInfo[]{};
-            }
-        }
-
-        @Override
-        public boolean isLeaf() {
-            return isLeaf;
-        }
-
-        public int depth() {
-            int depth = 1;
-            DebugInfoProvider.DebugLocationInfo caller = getCaller();
-            while (caller != null) {
-                depth++;
-                caller = caller.getCaller();
-            }
-            return depth;
-        }
-
-        private int localsSize() {
-            if (localInfoList != null) {
-                return localInfoList.size();
-            } else {
-                return 0;
-            }
-        }
-
-        /**
-         * Merge the supplied leaf location info into this leaf location info if they have
-         * contiguous ranges, the same method and line number and the same live local variables with
-         * the same values.
-         *
-         * @param that a leaf location info to be merged into this one
-         * @return this leaf location info if the merge was performed otherwise null
-         */
-        SubstrateDebugLocationInfo merge(SubstrateDebugLocationInfo that) {
-            assert callersLocationInfo == that.callersLocationInfo;
-            assert isLeaf == that.isLeaf;
-            assert depth() == that.depth() : "should only compare sibling ranges";
-            assert this.hi <= that.lo : "later nodes should not overlap earlier ones";
-            if (this.hi != that.lo) {
-                return null;
-            }
-            if (!method.equals(that.method)) {
-                return null;
-            }
-            if (line() != that.line()) {
-                return null;
-            }
-            int size = localsSize();
-            if (size != that.localsSize()) {
-                return null;
-            }
-            for (int i = 0; i < size; i++) {
-                SubstrateDebugLocalValueInfo thisLocal = (SubstrateDebugLocalValueInfo) localInfoList.get(i);
-                SubstrateDebugLocalValueInfo thatLocal = (SubstrateDebugLocalValueInfo) that.localInfoList.get(i);
-                if (!thisLocal.equals(thatLocal)) {
-                    return null;
-                }
-            }
-            debugContext.log(DebugContext.DETAILED_LEVEL, "Merge  leaf Location Info : %s depth %d (%d, %d) into (%d, %d)", that.name(), that.depth(), that.lo, that.hi - 1, this.lo, this.hi - 1);
-            // merging just requires updating lo and hi range as everything else is equal
-            this.hi = that.hi;
-
-            return this;
-        }
-
-        public SubstrateDebugLocationInfo split(int stackDecrement, int frameSize) {
-            // this should be for an initial range extending beyond the stack decrement
-            assert lo == 0 && lo < stackDecrement && stackDecrement < hi : "invalid split request";
-            return new SubstrateDebugLocationInfo(this, stackDecrement, frameSize);
-        }
-    }
-
-    /**
-     * Size in bytes of the frame at call entry before any stack extend. Essentially this accounts
-     * for any automatically pushed return address whose presence depends upon the architecture.
-     */
-    static final int PRE_EXTEND_FRAME_SIZE = ConfigurationValues.getTarget().arch.getReturnAddressSize();
-
-    class ParamLocationProducer {
-        private final ResolvedJavaMethod method;
-        private final CallingConvention callingConvention;
-        private boolean usesStack;
-
-        SubstrateDebugLocalValue thisLocation() {
-
-            assert !method.isStatic();
-            return unpack(callingConvention.getArgument(0));
-        }
-
-        ParamLocationProducer(ResolvedJavaMethod method) {
-            assert method instanceof SharedMethod;
-            this.method = method;
-            this.callingConvention = getCallingConvention((SharedMethod) method);
-            // assume no stack slots until we find out otherwise
-            this.usesStack = false;
-        }
-
-        SubstrateDebugLocalValue paramLocation(int paramIdx) {
-            assert paramIdx < method.getSignature().getParameterCount(false);
-            int idx = paramIdx;
-            if (!method.isStatic()) {
-                idx++;
-            }
-            return unpack(callingConvention.getArgument(idx));
-        }
-
-        private SubstrateDebugLocalValue unpack(AllocatableValue value) {
-            if (value instanceof RegisterValue) {
-                RegisterValue registerValue = (RegisterValue) value;
-                return SubstrateDebugRegisterValue.create(registerValue);
-            } else {
-                // call argument must be a stack slot if it is not a register
-                StackSlot stackSlot = (StackSlot) value;
-                this.usesStack = true;
-                // the calling convention provides offsets from the SP relative to the current
-                // frame size. At the point of call the frame may or may not include a return
-                // address depending on the architecture.
-                return SubstrateDebugStackValue.create(stackSlot, PRE_EXTEND_FRAME_SIZE);
-            }
-        }
-
-
-        /**
-         * Retrieve details of the native calling convention for a top level compiled method, including
-         * details of which registers or stack slots are used to pass parameters.
-         *
-         * @param method The method whose calling convention is required.
-         * @return The calling convention for the method.
-         */
-        private SubstrateCallingConvention getCallingConvention(SharedMethod method) {
-            SubstrateCallingConventionKind callingConventionKind = method.getCallingConventionKind();
-            ResolvedJavaType declaringClass = method.getDeclaringClass();
-            ResolvedJavaType receiverType = method.isStatic() ? null : declaringClass;
-            var signature = method.getSignature();
-            final SubstrateCallingConventionType type;
-            if (callingConventionKind.isCustom()) {
-                type = method.getCustomCallingConventionType();
-            } else {
-                type = callingConventionKind.toType(false);
-            }
-            Backend backend = runtimeConfiguration.lookupBackend(method);
-            RegisterConfig registerConfig = backend.getCodeCache().getRegisterConfig();
-            assert registerConfig instanceof SubstrateRegisterConfig;
-            return (SubstrateCallingConvention) registerConfig.getCallingConvention(type, signature.getReturnType(null), signature.toParameterTypes(receiverType), backend);
-        }
-
-        public boolean usesStack() {
-            return usesStack;
-        }
-    }
-
-    private class SubstrateDebugLocalInfo implements DebugInfoProvider.DebugLocalInfo {
-        protected final String name;
-        protected ResolvedJavaType type;
-        protected final JavaKind kind;
-        protected int slot;
-        protected int line;
-
-        SubstrateDebugLocalInfo(String name, JavaKind kind, ResolvedJavaType resolvedType, int slot, int line) {
-            this.name = name;
-            this.kind = kind;
-            this.slot = slot;
-            this.line = line;
-            // if we don't have a type default it for the JavaKind
-            // it may still end up null when kind is Undefined.
-            this.type = resolvedType;
-        }
-
-        @Override
-        public ResolvedJavaType valueType() {
-            return type;
-        }
-
-        @Override
-        public String name() {
-            return name;
-        }
-
-        @Override
-        public String typeName() {
-            ResolvedJavaType valueType = valueType();
-            return (valueType == null ? "" : valueType().toJavaName());
-        }
-
-        @Override
-        public int slot() {
-            return slot;
-        }
-
-        @Override
-        public int slotCount() {
-            return kind.getSlotCount();
-        }
-
-        @Override
-        public JavaKind javaKind() {
-            return kind;
-        }
-
-        @Override
-        public int line() {
-            return line;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (!(o instanceof SubstrateDebugLocalInfo)) {
-                return false;
-            }
-            SubstrateDebugLocalInfo that = (SubstrateDebugLocalInfo) o;
-            // locals need to have the same name
-            if (!name.equals(that.name)) {
-                return false;
-            }
-            // locals need to have the same type
-            if (!type.equals(that.type)) {
-                return false;
-            }
-            // values need to be for the same line
-            if (line != that.line) {
-                return false;
-            }
-            // kinds must match
-            if (kind != that.kind) {
-                return false;
-            }
-            // slots must match
-            return slot == that.slot;
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(name) * 31 + line;
-        }
-
-        @Override
-        public String toString() {
-            return typeName() + " " + name;
-        }
-    }
-
-    private class SubstrateDebugLocalValueInfo extends SubstrateDebugLocalInfo implements DebugInfoProvider.DebugLocalValueInfo {
-        private final SubstrateDebugLocalValue value;
-        private DebugInfoProvider.DebugLocalValueInfo.LocalKind localKind;
-
-        SubstrateDebugLocalValueInfo(String name, JavaValue value, int framesize, JavaKind kind, ResolvedJavaType resolvedType, int slot, int line) {
-            super(name, kind, resolvedType, slot, line);
-            if (value instanceof RegisterValue) {
-                this.localKind = DebugInfoProvider.DebugLocalValueInfo.LocalKind.REGISTER;
-                this.value = SubstrateDebugRegisterValue.create((RegisterValue) value);
-            } else if (value instanceof StackSlot) {
-                this.localKind = DebugInfoProvider.DebugLocalValueInfo.LocalKind.STACKSLOT;
-                this.value = SubstrateDebugStackValue.create((StackSlot) value, framesize);
-            } else if (value instanceof JavaConstant constant && (constant instanceof PrimitiveConstant || constant.isNull())) {
-                this.localKind = DebugInfoProvider.DebugLocalValueInfo.LocalKind.CONSTANT;
-                this.value = SubstrateDebugConstantValue.create(constant);
-            } else {
-                this.localKind = DebugInfoProvider.DebugLocalValueInfo.LocalKind.UNDEFINED;
-                this.value = null;
-            }
-        }
-
-        SubstrateDebugLocalValueInfo(String name, SubstrateDebugLocalValue value, JavaKind kind, ResolvedJavaType type, int slot, int line) {
-            super(name, kind, type, slot, line);
-            if (value == null) {
-                this.localKind = DebugInfoProvider.DebugLocalValueInfo.LocalKind.UNDEFINED;
-            } else if (value instanceof SubstrateDebugRegisterValue) {
-                this.localKind = DebugInfoProvider.DebugLocalValueInfo.LocalKind.REGISTER;
-            } else if (value instanceof SubstrateDebugStackValue) {
-                this.localKind = DebugInfoProvider.DebugLocalValueInfo.LocalKind.STACKSLOT;
-            } else if (value instanceof SubstrateDebugConstantValue) {
-                this.localKind = DebugInfoProvider.DebugLocalValueInfo.LocalKind.CONSTANT;
-            }
-            this.value = value;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (!(o instanceof SubstrateDebugLocalValueInfo)) {
-                return false;
-            }
-            SubstrateDebugLocalValueInfo that = (SubstrateDebugLocalValueInfo) o;
-            // values need to have the same name
-            if (!name().equals(that.name())) {
-                return false;
-            }
-            // values need to be for the same line
-            if (line != that.line) {
-                return false;
-            }
-            // location kinds must match
-            if (localKind != that.localKind) {
-                return false;
-            }
-            // locations must match
-            switch (localKind) {
-                case REGISTER:
-                case STACKSLOT:
-                case CONSTANT:
-                    return value.equals(that.value);
-                default:
-                    return true;
-            }
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(name(), value) * 31 + line;
-        }
-
-        @Override
-        public String toString() {
-            switch (localKind) {
-                case REGISTER:
-                    return "reg[" + regIndex() + "]";
-                case STACKSLOT:
-                    return "stack[" + stackSlot() + "]";
-                case CONSTANT:
-                    return "constant[" + (constantValue() != null ? constantValue().toValueString() : "null") + "]";
-                default:
-                    return "-";
-            }
-        }
-
-        @Override
-        public DebugInfoProvider.DebugLocalValueInfo.LocalKind localKind() {
-            return localKind;
-        }
-
-        @Override
-        public int regIndex() {
-            return ((SubstrateDebugRegisterValue) value).getNumber();
-        }
-
-        @Override
-        public int stackSlot() {
-            return ((SubstrateDebugStackValue) value).getOffset();
-        }
-
-        @Override
-        public long heapOffset() {
-            return ((SubstrateDebugConstantValue) value).getHeapOffset();
-        }
-
-        @Override
-        public JavaConstant constantValue() {
-            return ((SubstrateDebugConstantValue) value).getConstant();
-        }
-    }
-
-    public abstract static class SubstrateDebugLocalValue {
-    }
-
-    public static final class SubstrateDebugRegisterValue extends SubstrateDebugLocalValue {
-        private static HashMap<Integer, SubstrateDebugRegisterValue> registerValues = new HashMap<>();
-        private int number;
-        private String name;
-
-        private SubstrateDebugRegisterValue(int number, String name) {
-            this.number = number;
-            this.name = "reg:" + name;
-        }
-
-        static SubstrateDebugRegisterValue create(RegisterValue value) {
-            int number = value.getRegister().number;
-            String name = value.getRegister().name;
-            return memoizedCreate(number, name);
-        }
-
-        static SubstrateDebugRegisterValue memoizedCreate(int number, String name) {
-            SubstrateDebugRegisterValue reg = registerValues.get(number);
-            if (reg == null) {
-                reg = new SubstrateDebugRegisterValue(number, name);
-                registerValues.put(number, reg);
-            }
-            return reg;
-        }
-
-        public int getNumber() {
-            return number;
-        }
-
-        @Override
-        public String toString() {
-            return name;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (!(o instanceof SubstrateDebugRegisterValue)) {
-                return false;
-            }
-            SubstrateDebugRegisterValue that = (SubstrateDebugRegisterValue) o;
-            return number == that.number;
-        }
-
-        @Override
-        public int hashCode() {
-            return number * 31;
-        }
-    }
-
-    public static final class SubstrateDebugStackValue extends SubstrateDebugLocalValue {
-        private static HashMap<Integer, SubstrateDebugStackValue> stackValues = new HashMap<>();
-        private int offset;
-        private String name;
-
-        private SubstrateDebugStackValue(int offset) {
-            this.offset = offset;
-            this.name = "stack:" + offset;
-        }
-
-        static SubstrateDebugStackValue create(StackSlot value, int framesize) {
-            // Work around a problem on AArch64 where StackSlot asserts if it is
-            // passed a zero frame size, even though this is what is expected
-            // for stack slot offsets provided at the point of entry (because,
-            // unlike x86, lr has not been pushed).
-            int offset = (framesize == 0 ? value.getRawOffset() : value.getOffset(framesize));
-            return memoizedCreate(offset);
-        }
-
-        static SubstrateDebugStackValue create(DebugInfoProvider.DebugLocalValueInfo previous, int adjustment) {
-            assert previous.localKind() == DebugInfoProvider.DebugLocalValueInfo.LocalKind.STACKSLOT;
-            return memoizedCreate(previous.stackSlot() + adjustment);
-        }
-
-        private static SubstrateDebugStackValue memoizedCreate(int offset) {
-            SubstrateDebugStackValue value = stackValues.get(offset);
-            if (value == null) {
-                value = new SubstrateDebugStackValue(offset);
-                stackValues.put(offset, value);
-            }
-            return value;
-        }
-
-        public int getOffset() {
-            return offset;
-        }
-
-        @Override
-        public String toString() {
-            return name;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (!(o instanceof SubstrateDebugStackValue)) {
-                return false;
-            }
-            SubstrateDebugStackValue that = (SubstrateDebugStackValue) o;
-            return offset == that.offset;
-        }
-
-        @Override
-        public int hashCode() {
-            return offset * 31;
         }
     }
 
-    public static final class SubstrateDebugConstantValue extends SubstrateDebugLocalValue {
-        private static HashMap<JavaConstant, SubstrateDebugConstantValue> constantValues = new HashMap<>();
-        private JavaConstant value;
-        private long heapoffset;
-
-        private SubstrateDebugConstantValue(JavaConstant value, long heapoffset) {
-            this.value = value;
-            this.heapoffset = heapoffset;
-        }
-
-        static SubstrateDebugConstantValue create(JavaConstant value) {
-            return create(value, -1);
-        }
-
-        static SubstrateDebugConstantValue create(JavaConstant value, long heapoffset) {
-            SubstrateDebugConstantValue c = constantValues.get(value);
-            if (c == null) {
-                c = new SubstrateDebugConstantValue(value, heapoffset);
-                constantValues.put(value, c);
-            }
-            assert c.heapoffset == heapoffset;
-            return c;
-        }
-
-        public JavaConstant getConstant() {
-            return value;
-        }
-
-        public long getHeapOffset() {
-            return heapoffset;
-        }
-
-        @Override
-        public String toString() {
-            return "constant:" + value.toString();
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (!(o instanceof SubstrateDebugConstantValue)) {
-                return false;
-            }
-            SubstrateDebugConstantValue that = (SubstrateDebugConstantValue) o;
-            return heapoffset == that.heapoffset && value.equals(that.value);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(value) * 31 + (int) heapoffset;
-        }
+    @Override
+    protected void processTypeEntry(SharedType type, TypeEntry typeEntry) {
+        // nothing to do here
     }
 
-    /**
-     * Implementation of the DebugFrameSizeChange API interface that allows stack frame size change
-     * info to be passed to an ObjectFile when generation of debug info is enabled.
-     */
-    private class SubstrateDebugFrameSizeChange implements DebugInfoProvider.DebugFrameSizeChange {
-        private int offset;
-        private Type type;
-
-        SubstrateDebugFrameSizeChange(int offset, Type type) {
-            this.offset = offset;
-            this.type = type;
-        }
-
-        @Override
-        public int getOffset() {
-            return offset;
-        }
-
-        @Override
-        public Type getType() {
-            return type;
-        }
+    @Override
+    public long objectOffset(JavaConstant constant) {
+        return 0;
     }
 }
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 33f848f2bee3..3c6e4a94367f 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
@@ -25,59 +25,48 @@
  */
 package com.oracle.svm.hosted.image;
 
-import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFrameSizeChange.Type.CONTRACT;
-import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFrameSizeChange.Type.EXTEND;
-
 import java.lang.reflect.Modifier;
 import java.nio.file.Path;
-import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Objects;
+import java.util.Comparator;
 import java.util.Set;
-import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
-import org.graalvm.collections.EconomicMap;
-import org.graalvm.nativeimage.ImageSingletons;
-import org.graalvm.nativeimage.c.struct.CPointerTo;
-import org.graalvm.nativeimage.c.struct.RawPointerTo;
-
-import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider;
 import com.oracle.graal.pointsto.infrastructure.WrappedJavaMethod;
 import com.oracle.graal.pointsto.infrastructure.WrappedJavaType;
-import com.oracle.graal.pointsto.meta.AnalysisMethod;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider;
+import com.oracle.objectfile.debugentry.ArrayTypeEntry;
+import com.oracle.objectfile.debugentry.ClassEntry;
+import com.oracle.objectfile.debugentry.CompiledMethodEntry;
+import com.oracle.objectfile.debugentry.EnumClassEntry;
+import com.oracle.objectfile.debugentry.FieldEntry;
+import com.oracle.objectfile.debugentry.FileEntry;
+import com.oracle.objectfile.debugentry.ForeignTypeEntry;
+import com.oracle.objectfile.debugentry.InterfaceClassEntry;
+import com.oracle.objectfile.debugentry.LoaderEntry;
+import com.oracle.objectfile.debugentry.MethodEntry;
+import com.oracle.objectfile.debugentry.PrimitiveTypeEntry;
+import com.oracle.objectfile.debugentry.StructureTypeEntry;
+import com.oracle.objectfile.debugentry.TypeEntry;
+import com.oracle.svm.common.meta.MultiMethod;
+import com.oracle.svm.core.StaticFieldsSupport;
 import com.oracle.svm.core.SubstrateOptions;
 import com.oracle.svm.core.UniqueShortNameProvider;
-import com.oracle.svm.core.code.CompilationResultFrameTree.Builder;
-import com.oracle.svm.core.code.CompilationResultFrameTree.CallNode;
-import com.oracle.svm.core.code.CompilationResultFrameTree.FrameNode;
-import com.oracle.svm.core.code.CompilationResultFrameTree.Visitor;
-import com.oracle.svm.core.config.ConfigurationValues;
-import com.oracle.svm.core.config.ObjectLayout;
-import com.oracle.svm.core.graal.code.SubstrateBackend.SubstrateMarkId;
+import com.oracle.svm.core.debug.SharedDebugInfoProvider;
 import com.oracle.svm.core.graal.meta.RuntimeConfiguration;
-import com.oracle.svm.core.image.ImageHeapPartition;
-import com.oracle.svm.hosted.DeadlockWatchdog;
+import com.oracle.svm.core.meta.SharedMethod;
+import com.oracle.svm.core.meta.SharedType;
+import com.oracle.svm.core.util.VMError;
 import com.oracle.svm.hosted.c.NativeLibraries;
 import com.oracle.svm.hosted.c.info.AccessorInfo;
 import com.oracle.svm.hosted.c.info.ElementInfo;
 import com.oracle.svm.hosted.c.info.PointerToInfo;
-import com.oracle.svm.hosted.c.info.PropertyInfo;
-import com.oracle.svm.hosted.c.info.RawStructureInfo;
 import com.oracle.svm.hosted.c.info.SizableInfo;
-import com.oracle.svm.hosted.c.info.SizableInfo.ElementKind;
 import com.oracle.svm.hosted.c.info.StructFieldInfo;
 import com.oracle.svm.hosted.c.info.StructInfo;
-import com.oracle.svm.hosted.image.NativeImageHeap.ObjectInfo;
 import com.oracle.svm.hosted.image.sources.SourceManager;
 import com.oracle.svm.hosted.meta.HostedArrayClass;
-import com.oracle.svm.hosted.meta.HostedClass;
 import com.oracle.svm.hosted.meta.HostedField;
 import com.oracle.svm.hosted.meta.HostedInstanceClass;
 import com.oracle.svm.hosted.meta.HostedInterface;
@@ -85,49 +74,55 @@
 import com.oracle.svm.hosted.meta.HostedMethod;
 import com.oracle.svm.hosted.meta.HostedPrimitiveType;
 import com.oracle.svm.hosted.meta.HostedType;
-import com.oracle.svm.hosted.substitute.SubstitutionField;
+import com.oracle.svm.hosted.substitute.InjectedFieldsType;
 import com.oracle.svm.hosted.substitute.SubstitutionMethod;
-import com.oracle.svm.util.ClassUtil;
-
+import com.oracle.svm.hosted.substitute.SubstitutionType;
 import jdk.graal.compiler.code.CompilationResult;
 import jdk.graal.compiler.debug.DebugContext;
-import jdk.graal.compiler.graph.NodeSourcePosition;
-import jdk.graal.compiler.java.StableMethodNameFormatter;
-import jdk.graal.compiler.util.Digest;
-import jdk.vm.ci.aarch64.AArch64;
-import jdk.vm.ci.amd64.AMD64;
-import jdk.vm.ci.code.BytecodeFrame;
-import jdk.vm.ci.code.BytecodePosition;
-import jdk.vm.ci.code.CallingConvention;
-import jdk.vm.ci.code.Register;
-import jdk.vm.ci.code.RegisterValue;
-import jdk.vm.ci.code.StackSlot;
-import jdk.vm.ci.meta.AllocatableValue;
 import jdk.vm.ci.meta.JavaConstant;
 import jdk.vm.ci.meta.JavaKind;
-import jdk.vm.ci.meta.JavaValue;
-import jdk.vm.ci.meta.LineNumberTable;
-import jdk.vm.ci.meta.Local;
-import jdk.vm.ci.meta.LocalVariableTable;
-import jdk.vm.ci.meta.PrimitiveConstant;
 import jdk.vm.ci.meta.ResolvedJavaField;
 import jdk.vm.ci.meta.ResolvedJavaMethod;
 import jdk.vm.ci.meta.ResolvedJavaType;
-import jdk.vm.ci.meta.Signature;
-import jdk.vm.ci.meta.Value;
+import org.graalvm.collections.Pair;
+import org.graalvm.nativeimage.ImageSingletons;
+import org.graalvm.nativeimage.c.struct.CPointerTo;
+import org.graalvm.nativeimage.c.struct.RawPointerTo;
+
+import static com.oracle.svm.hosted.c.info.AccessorInfo.AccessorKind.ADDRESS;
+import static com.oracle.svm.hosted.c.info.AccessorInfo.AccessorKind.GETTER;
+import static com.oracle.svm.hosted.c.info.AccessorInfo.AccessorKind.SETTER;
 
 /**
  * Implementation of the DebugInfoProvider API interface that allows type, code and heap data info
  * to be passed to an ObjectFile when generation of debug info is enabled.
  */
-class NativeImageDebugInfoProvider extends NativeImageDebugInfoProviderBase implements DebugInfoProvider {
+class NativeImageDebugInfoProvider extends SharedDebugInfoProvider {
+    protected final NativeImageHeap heap;
+    protected final NativeImageCodeCache codeCache;
+    protected final NativeLibraries nativeLibs;
+
+
+    protected final int primitiveStartOffset;
+    protected final int referenceStartOffset;
+
     private final DebugContext debugContext;
     private final Set<HostedMethod> allOverrides;
 
     NativeImageDebugInfoProvider(DebugContext debugContext, NativeImageCodeCache codeCache, NativeImageHeap heap, NativeLibraries nativeLibs, HostedMetaAccess metaAccess,
                     RuntimeConfiguration runtimeConfiguration) {
-        super(codeCache, heap, nativeLibs, metaAccess, runtimeConfiguration);
+        super(runtimeConfiguration, metaAccess, UniqueShortNameProvider.singleton(), SubstrateOptions.getDebugInfoSourceCacheRoot());
+        this.heap = heap;
+        this.codeCache = codeCache;
+        this.nativeLibs = nativeLibs;
         this.debugContext = debugContext;
+
+        /* Offsets need to be adjusted relative to the heap base plus partition-specific offset. */
+        NativeImageHeap.ObjectInfo primitiveFields = heap.getObjectInfo(StaticFieldsSupport.getStaticPrimitiveFields());
+        NativeImageHeap.ObjectInfo objectFields = heap.getObjectInfo(StaticFieldsSupport.getStaticObjectFields());
+        primitiveStartOffset = (int) primitiveFields.getOffset();
+        referenceStartOffset = (int) objectFields.getOffset();
+
         /* Calculate the set of all HostedMethods that are overrides. */
         allOverrides = heap.hUniverse.getMethods().stream()
                         .filter(HostedMethod::hasVTableIndex)
@@ -136,2533 +131,506 @@ class NativeImageDebugInfoProvider extends NativeImageDebugInfoProviderBase impl
                         .collect(Collectors.toSet());
     }
 
-    @Override
-    public Stream<DebugTypeInfo> typeInfoProvider() {
-        Stream<DebugTypeInfo> headerTypeInfo = computeHeaderTypeInfo();
-        Stream<DebugTypeInfo> heapTypeInfo = heap.hUniverse.getTypes().stream().map(this::createDebugTypeInfo);
-        return Stream.concat(headerTypeInfo, heapTypeInfo);
-    }
-
-    @Override
-    public Stream<DebugCodeInfo> codeInfoProvider() {
-        return codeCache.getOrderedCompilations().stream().map(pair -> new NativeImageDebugCodeInfo(pair.getLeft(), pair.getRight()));
-    }
-
-    @Override
-    public Stream<DebugDataInfo> dataInfoProvider() {
-        return heap.getObjects().stream().filter(this::acceptObjectInfo).map(this::createDebugDataInfo);
-    }
-
-    private abstract class NativeImageDebugFileInfo implements DebugFileInfo {
-        private final Path fullFilePath;
-
-        @SuppressWarnings("try")
-        NativeImageDebugFileInfo(HostedType hostedType) {
-            ResolvedJavaType javaType = getDeclaringClass(hostedType, false);
-            Class<?> clazz = hostedType.getJavaClass();
-            SourceManager sourceManager = ImageSingletons.lookup(SourceManager.class);
-            try (DebugContext.Scope s = debugContext.scope("DebugFileInfo", hostedType)) {
-                Path filePath = sourceManager.findAndCacheSource(javaType, clazz, debugContext);
-                if (filePath == null && (hostedType instanceof HostedInstanceClass || hostedType instanceof HostedInterface)) {
-                    // conjure up an appropriate, unique file name to keep tools happy
-                    // even though we cannot find a corresponding source
-                    filePath = fullFilePathFromClassName(hostedType);
-                }
-                fullFilePath = filePath;
-            } catch (Throwable e) {
-                throw debugContext.handle(e);
-            }
-        }
-
-        @SuppressWarnings("try")
-        NativeImageDebugFileInfo(ResolvedJavaMethod method) {
-            /*
-             * Note that this constructor allows for any ResolvedJavaMethod, not just a
-             * HostedMethod, because it needs to provide common behaviour for DebugMethodInfo,
-             * DebugCodeInfo and DebugLocationInfo records. The former two are derived from a
-             * HostedMethod while the latter may be derived from an arbitrary ResolvedJavaMethod.
-             */
-            ResolvedJavaType javaType;
-            if (method instanceof HostedMethod) {
-                javaType = getDeclaringClass((HostedMethod) method, false);
-            } else {
-                javaType = method.getDeclaringClass();
-            }
-            Class<?> clazz = OriginalClassProvider.getJavaClass(javaType);
-            SourceManager sourceManager = ImageSingletons.lookup(SourceManager.class);
-            try (DebugContext.Scope s = debugContext.scope("DebugFileInfo", javaType)) {
-                fullFilePath = sourceManager.findAndCacheSource(javaType, clazz, debugContext);
-            } catch (Throwable e) {
-                throw debugContext.handle(e);
-            }
-        }
-
-        @SuppressWarnings("try")
-        NativeImageDebugFileInfo(HostedField hostedField) {
-            ResolvedJavaType javaType = getDeclaringClass(hostedField, false);
-            HostedType hostedType = hostedField.getDeclaringClass();
-            Class<?> clazz = hostedType.getJavaClass();
-            SourceManager sourceManager = ImageSingletons.lookup(SourceManager.class);
-            try (DebugContext.Scope s = debugContext.scope("DebugFileInfo", hostedType)) {
-                fullFilePath = sourceManager.findAndCacheSource(javaType, clazz, debugContext);
-            } catch (Throwable e) {
-                throw debugContext.handle(e);
-            }
+    private static ResolvedJavaType getOriginal(ResolvedJavaType type) {
+        /*
+         * unwrap then traverse through substitutions to the original. We don't want to
+         * get the original type of LambdaSubstitutionType to keep the stable name
+         */
+        while (type instanceof WrappedJavaType wrappedJavaType) {
+            type = wrappedJavaType.getWrapped();
         }
 
-        @Override
-        public String fileName() {
-            if (fullFilePath != null) {
-                Path filename = fullFilePath.getFileName();
-                if (filename != null) {
-                    return filename.toString();
-                }
-            }
-            return "";
+        if (type instanceof SubstitutionType substitutionType) {
+            type = substitutionType.getOriginal();
+        } else if (type instanceof InjectedFieldsType injectedFieldsType) {
+            type = injectedFieldsType.getOriginal();
         }
 
-        @Override
-        public Path filePath() {
-            if (fullFilePath != null) {
-                return fullFilePath.getParent();
-            }
-            return null;
-        }
+        return type;
     }
 
-    private abstract class NativeImageDebugTypeInfo extends NativeImageDebugFileInfo implements DebugTypeInfo {
-        protected final HostedType hostedType;
-
-        @SuppressWarnings("try")
-        protected NativeImageDebugTypeInfo(HostedType hostedType) {
-            super(hostedType);
-            this.hostedType = hostedType;
-        }
-
-        @SuppressWarnings("try")
-        @Override
-        public void debugContext(Consumer<DebugContext> action) {
-            try (DebugContext.Scope s = debugContext.scope("DebugTypeInfo", typeName())) {
-                action.accept(debugContext);
-            } catch (Throwable e) {
-                throw debugContext.handle(e);
-            }
-        }
-
-        @Override
-        public long typeSignature(String prefix) {
-            return Digest.digestAsUUID(prefix + typeName()).getLeastSignificantBits();
+    private static ResolvedJavaMethod getAnnotatedOrOriginal(ResolvedJavaMethod method) {
+        while (method instanceof WrappedJavaMethod wrappedJavaMethod) {
+            method = wrappedJavaMethod.getWrapped();
         }
+        // This method is only used when identifying the modifiers or the declaring class
+        // of a HostedMethod. Normally the method unwraps to the underlying JVMCI method
+        // which is the one that provides bytecode to the compiler as well as, line numbers
+        // and local info. If we unwrap to a SubstitutionMethod then we use the annotated
+        // method, not the JVMCI method that the annotation refers to since that will be the
+        // one providing the bytecode etc used by the compiler. If we unwrap to any other,
+        // custom substitution method we simply use it rather than dereferencing to the
+        // original. The difference is that the annotated method's bytecode will be used to
+        // replace the original and the debugger needs to use it to identify the file and access
+        // permissions. A custom substitution may exist alongside the original, as is the case
+        // with some uses for reflection. So, we don't want to conflate the custom substituted
+        // method and the original. In this latter case the method code will be synthesized without
+        // reference to the bytecode of the original. Hence, there is no associated file and the
+        // permissions need to be determined from the custom substitution method itself.
 
-        public String toJavaName(@SuppressWarnings("hiding") HostedType hostedType) {
-            return getDeclaringClass(hostedType, true).toJavaName();
+        if (method instanceof SubstitutionMethod substitutionMethod) {
+            method = substitutionMethod.getAnnotated();
         }
 
-        @Override
-        public ResolvedJavaType idType() {
-            // always use the original type for establishing identity
-            return getOriginal(hostedType);
-        }
+        return method;
+    }
 
-        @Override
-        public String typeName() {
-            return toJavaName(hostedType);
+    private static int elementSize(ElementInfo elementInfo) {
+        if (!(elementInfo instanceof SizableInfo) || elementInfo instanceof StructInfo structInfo && structInfo.isIncomplete()) {
+            return 0;
         }
+        return ((SizableInfo) elementInfo).getSizeInBytes();
+    }
 
-        @Override
-        public long classOffset() {
-            /*
-             * Only query the heap for reachable types. These are guaranteed to have been seen by
-             * the analysis and to exist in the shadow heap.
-             */
-            if (hostedType.getWrapped().isReachable()) {
-                ObjectInfo objectInfo = heap.getObjectInfo(hostedType.getHub());
-                if (objectInfo != null) {
-                    return objectInfo.getOffset();
-                }
+    private static String typedefName(ElementInfo elementInfo) {
+        String name = null;
+        if (elementInfo != null) {
+            if (elementInfo instanceof PointerToInfo) {
+                name = ((PointerToInfo) elementInfo).getTypedefName();
+            } else if (elementInfo instanceof StructInfo) {
+                name = ((StructInfo) elementInfo).getTypedefName();
             }
-            return -1;
-        }
-
-        @Override
-        public int size() {
-            if (hostedType instanceof HostedInstanceClass) {
-                /* We know the actual instance size in bytes. */
-                return ((HostedInstanceClass) hostedType).getInstanceSize();
-            } else if (hostedType instanceof HostedArrayClass) {
-                /* Use the size of header common to all arrays of this type. */
-                return getObjectLayout().getArrayBaseOffset(hostedType.getComponentType().getStorageKind());
-            } else if (hostedType instanceof HostedInterface) {
-                /* Use the size of the header common to all implementors. */
-                return getObjectLayout().getFirstFieldOffset();
-            } else {
-                /* Use the number of bytes needed needed to store the value. */
-                assert hostedType instanceof HostedPrimitiveType;
-                JavaKind javaKind = hostedType.getStorageKind();
-                return (javaKind == JavaKind.Void ? 0 : javaKind.getByteCount());
+            if (name == null) {
+                name = elementInfo.getName();
             }
         }
+        return name;
     }
 
-    private class NativeImageHeaderTypeInfo implements DebugHeaderTypeInfo {
-        String typeName;
-        int size;
-        List<DebugFieldInfo> fieldInfos;
-
-        NativeImageHeaderTypeInfo(String typeName, int size) {
-            this.typeName = typeName;
-            this.size = size;
-            this.fieldInfos = new LinkedList<>();
-        }
+    private boolean isForeignPointerType(HostedType type) {
+        // unwrap because native libs operates on the analysis type universe
+        return nativeLibs.isPointerBase(type.getWrapped());
+    }
 
-        void addField(String name, ResolvedJavaType valueType, int offset, @SuppressWarnings("hiding") int size) {
-            NativeImageDebugHeaderFieldInfo fieldinfo = new NativeImageDebugHeaderFieldInfo(name, valueType, offset, size);
-            fieldInfos.add(fieldinfo);
-        }
 
-        @Override
-        public ResolvedJavaType idType() {
-            // The header type is unique in that it does not have an associated ResolvedJavaType
-            return null;
-        }
+    /*
+     * Foreign pointer types have associated element info which describes the target type. The
+     * following helpers support querying of and access to this element info.
+     */
 
-        @SuppressWarnings("try")
-        @Override
-        public void debugContext(Consumer<DebugContext> action) {
-            try (DebugContext.Scope s = debugContext.scope("DebugTypeInfo", typeName())) {
-                action.accept(debugContext);
-            } catch (Throwable e) {
-                throw debugContext.handle(e);
+    protected static boolean isTypedField(ElementInfo elementInfo) {
+        if (elementInfo instanceof StructFieldInfo) {
+            for (ElementInfo child : elementInfo.getChildren()) {
+                if (child instanceof AccessorInfo) {
+                    switch (((AccessorInfo) child).getAccessorKind()) {
+                        case GETTER:
+                        case SETTER:
+                        case ADDRESS:
+                            return true;
+                    }
+                }
             }
         }
-
-        @Override
-        public String typeName() {
-            return typeName;
-        }
-
-        @Override
-        public long typeSignature(String prefix) {
-            return Digest.digestAsUUID(typeName).getLeastSignificantBits();
-        }
-
-        @Override
-        public DebugTypeKind typeKind() {
-            return DebugTypeKind.HEADER;
-        }
-
-        @Override
-        public String fileName() {
-            return "";
-        }
-
-        @Override
-        public Path filePath() {
-            return null;
-        }
-
-        @Override
-        public long classOffset() {
-            return -1;
-        }
-
-        @Override
-        public int size() {
-            return size;
-        }
-
-        @Override
-        public Stream<DebugFieldInfo> fieldInfoProvider() {
-            return fieldInfos.stream();
-        }
+        return false;
     }
 
-    private class NativeImageDebugHeaderFieldInfo implements DebugFieldInfo {
-        private final String name;
-        private final ResolvedJavaType valueType;
-        private final int offset;
-        private final int size;
-        private final int modifiers;
-
-        NativeImageDebugHeaderFieldInfo(String name, ResolvedJavaType valueType, int offset, int size) {
-            this.name = name;
-            this.valueType = valueType;
-            this.offset = offset;
-            this.size = size;
-            this.modifiers = Modifier.PUBLIC;
-        }
-
-        @Override
-        public String name() {
-            return name;
-        }
-
-        @Override
-        public ResolvedJavaType valueType() {
-            if (valueType instanceof HostedType) {
-                return getOriginal((HostedType) valueType);
+    protected HostedType getFieldType(StructFieldInfo field) {
+        // we should always have some sort of accessor, preferably a GETTER or a SETTER
+        // but possibly an ADDRESS accessor
+        for (ElementInfo elt : field.getChildren()) {
+            if (elt instanceof AccessorInfo accessorInfo) {
+                if (accessorInfo.getAccessorKind() == GETTER) {
+                    return heap.hUniverse.lookup(accessorInfo.getReturnType());
+                }
             }
-            return valueType;
         }
-
-        @Override
-        public int offset() {
-            return offset;
-        }
-
-        @Override
-        public int size() {
-            return size;
+        for (ElementInfo elt : field.getChildren()) {
+            if (elt instanceof AccessorInfo accessorInfo) {
+                if (accessorInfo.getAccessorKind() == SETTER) {
+                    return heap.hUniverse.lookup(accessorInfo.getParameterType(0));
+                }
+            }
         }
-
-        @Override
-        public boolean isEmbedded() {
-            return false;
+        for (ElementInfo elt : field.getChildren()) {
+            if (elt instanceof AccessorInfo accessorInfo) {
+                if (accessorInfo.getAccessorKind() == ADDRESS) {
+                    return heap.hUniverse.lookup(accessorInfo.getReturnType());
+                }
+            }
         }
+        assert false : "Field %s must have a GETTER, SETTER, ADDRESS or OFFSET accessor".formatted(field);
+        // treat it as a word?
+        // n.b. we want a hosted type not an analysis type
+        return heap.hUniverse.lookup(wordBaseType);
+    }
 
-        @Override
-        public int modifiers() {
-            return modifiers;
+    protected static boolean fieldTypeIsEmbedded(StructFieldInfo field) {
+        // we should always have some sort of accessor, preferably a GETTER or a SETTER
+        // but possibly an ADDRESS
+        for (ElementInfo elt : field.getChildren()) {
+            if (elt instanceof AccessorInfo accessorInfo) {
+                if (accessorInfo.getAccessorKind() == GETTER) {
+                    return false;
+                }
+            }
         }
-
-        @Override
-        public String fileName() {
-            return "";
+        for (ElementInfo elt : field.getChildren()) {
+            if (elt instanceof AccessorInfo accessorInfo) {
+                if (accessorInfo.getAccessorKind() == SETTER) {
+                    return false;
+                }
+            }
         }
-
-        @Override
-        public Path filePath() {
-            return null;
+        for (ElementInfo elt : field.getChildren()) {
+            if (elt instanceof AccessorInfo accessorInfo) {
+                if (accessorInfo.getAccessorKind() == ADDRESS) {
+                    return true;
+                }
+            }
         }
+        throw VMError.shouldNotReachHere("Field %s must have a GETTER, SETTER, ADDRESS or OFFSET accessor".formatted(field));
     }
 
-    private Stream<DebugTypeInfo> computeHeaderTypeInfo() {
-        ObjectLayout ol = getObjectLayout();
-
-        List<DebugTypeInfo> infos = new LinkedList<>();
-        int hubOffset = ol.getHubOffset();
-
-        NativeImageHeaderTypeInfo objHeader = new NativeImageHeaderTypeInfo("_objhdr", ol.getFirstFieldOffset());
-        objHeader.addField("hub", hubType, hubOffset, referenceSize);
-        if (ol.isIdentityHashFieldInObjectHeader()) {
-            int idHashSize = ol.sizeInBytes(JavaKind.Int);
-            objHeader.addField("idHash", javaKindToHostedType.get(JavaKind.Int), ol.getObjectHeaderIdentityHashOffset(), idHashSize);
-        }
-        infos.add(objHeader);
-
-        return infos.stream();
+    @Override
+    protected Stream<SharedType> typeInfo() {
+        // null represents the header type
+        return heap.hUniverse.getTypes().stream().map(type -> type);
     }
 
-    private class NativeImageDebugEnumTypeInfo extends NativeImageDebugInstanceTypeInfo implements DebugEnumTypeInfo {
-
-        NativeImageDebugEnumTypeInfo(HostedInstanceClass enumClass) {
-            super(enumClass);
-        }
-
-        @Override
-        public DebugTypeKind typeKind() {
-            return DebugTypeKind.ENUM;
-        }
+    @Override
+    protected Stream<Pair<SharedMethod, CompilationResult>> codeInfo() {
+        return codeCache.getOrderedCompilations().stream().map(pair -> Pair.create(pair.getLeft(), pair.getRight()));
     }
 
-    private class NativeImageDebugInstanceTypeInfo extends NativeImageDebugTypeInfo implements DebugInstanceTypeInfo {
-        NativeImageDebugInstanceTypeInfo(HostedType hostedType) {
-            super(hostedType);
-        }
+    @Override
+    protected Stream<Object> dataInfo() {
+        return heap.getObjects().stream().filter(obj -> obj.getPartition().getStartOffset() > 0).map(obj -> obj);
+    }
 
-        @Override
-        public long typeSignature(String prefix) {
-            return super.typeSignature(prefix + loaderName());
-        }
+    @Override
+    protected long getCodeOffset(SharedMethod method) {
+        assert method instanceof HostedMethod;
+        return ((HostedMethod) method).getCodeAddressOffset();
+    }
 
-        @Override
-        public DebugTypeKind typeKind() {
-            return DebugTypeKind.INSTANCE;
+    @Override
+    public MethodEntry lookupMethodEntry(SharedMethod method) {
+        if (method instanceof HostedMethod hostedMethod && !hostedMethod.isOriginalMethod()) {
+            method = hostedMethod.getMultiMethod(MultiMethod.ORIGINAL_METHOD);
         }
+        return super.lookupMethodEntry(method);
+    }
 
-        @Override
-        public String loaderName() {
-            return UniqueShortNameProvider.singleton().uniqueShortLoaderName(hostedType.getJavaClass().getClassLoader());
+    @Override
+    public CompiledMethodEntry lookupCompiledMethodEntry(MethodEntry methodEntry, SharedMethod method, CompilationResult compilation) {
+        if (method instanceof HostedMethod hostedMethod && !hostedMethod.isOriginalMethod()) {
+            // method = hostedMethod.getMultiMethod(MultiMethod.ORIGINAL_METHOD);
         }
+        return super.lookupCompiledMethodEntry(methodEntry, method, compilation);
+    }
 
-        @Override
-        public Stream<DebugFieldInfo> fieldInfoProvider() {
-            Stream<DebugFieldInfo> instanceFieldsStream = Arrays.stream(hostedType.getInstanceFields(false)).map(this::createDebugFieldInfo);
-            if (hostedType instanceof HostedInstanceClass && hostedType.getStaticFields().length > 0) {
-                Stream<DebugFieldInfo> staticFieldsStream = Arrays.stream(hostedType.getStaticFields()).map(this::createDebugStaticFieldInfo);
-                return Stream.concat(instanceFieldsStream, staticFieldsStream);
+    @Override
+    protected void processTypeEntry(SharedType type, TypeEntry typeEntry) {
+        assert type instanceof HostedType;
+        HostedType hostedType = (HostedType) type;
+
+        if (typeEntry instanceof StructureTypeEntry structureTypeEntry) {
+            if (typeEntry instanceof ArrayTypeEntry arrayTypeEntry) {
+                processArrayFields(hostedType, arrayTypeEntry);
+            } else if (typeEntry instanceof ForeignTypeEntry foreignTypeEntry) {
+                processForeignTypeFields(hostedType, foreignTypeEntry);
             } else {
-                return instanceFieldsStream;
+                processFieldEntries(hostedType, structureTypeEntry);
             }
-        }
-
-        @Override
-        public Stream<DebugMethodInfo> methodInfoProvider() {
-            return Arrays.stream(hostedType.getAllDeclaredMethods()).map(this::createDebugMethodInfo);
-        }
 
-        @Override
-        public ResolvedJavaType superClass() {
-            HostedClass superClass = hostedType.getSuperclass();
-            /*
-             * Unwrap the hosted type's super class to the original to provide the correct identity
-             * type.
-             */
-            if (superClass != null) {
-                return getOriginal(superClass);
+            if (typeEntry instanceof ClassEntry classEntry) {
+                processInterfaces(hostedType, classEntry);
+                processMethods(hostedType, classEntry);
             }
-            return null;
-        }
-
-        @Override
-        public Stream<ResolvedJavaType> interfaces() {
-            // map through getOriginal so we can use the result as an id type
-            return Arrays.stream(hostedType.getInterfaces()).map(interfaceType -> getOriginal(interfaceType));
-        }
-
-        private NativeImageDebugFieldInfo createDebugFieldInfo(HostedField field) {
-            return new NativeImageDebugFieldInfo(field);
-        }
-
-        private NativeImageDebugFieldInfo createDebugStaticFieldInfo(ResolvedJavaField field) {
-            return new NativeImageDebugFieldInfo((HostedField) field);
         }
+    }
 
-        private NativeImageDebugMethodInfo createDebugMethodInfo(HostedMethod method) {
-            return new NativeImageDebugMethodInfo(method);
+    private void processMethods(HostedType type, ClassEntry classEntry) {
+        for (HostedMethod method : type.getAllDeclaredMethods()) {
+            MethodEntry methodEntry = lookupMethodEntry(method);
+            classEntry.addMethod(methodEntry);
         }
+    }
 
-        private class NativeImageDebugFieldInfo extends NativeImageDebugFileInfo implements DebugFieldInfo {
-            private final HostedField field;
-
-            NativeImageDebugFieldInfo(HostedField field) {
-                super(field);
-                this.field = field;
-            }
-
-            @Override
-            public String name() {
-                return field.getName();
-            }
-
-            @Override
-            public ResolvedJavaType valueType() {
-                return getOriginal(field.getType());
-            }
-
-            @Override
-            public int offset() {
-                int offset = field.getLocation();
-                /*
-                 * For static fields we need to add in the appropriate partition base but only if we
-                 * have a real offset
-                 */
-                if (isStatic() && offset >= 0) {
-                    if (isPrimitive()) {
-                        offset += primitiveStartOffset;
-                    } else {
-                        offset += referenceStartOffset;
-                    }
+    @Override
+    public String getMethodName(SharedMethod method) {
+        String name;
+        if (method instanceof HostedMethod hostedMethod) {
+            name = hostedMethod.getName();
+            if (name.equals("<init>")) {
+                name = hostedMethod.getDeclaringClass().toJavaName();
+                if (name.indexOf('.') >= 0) {
+                    name = name.substring(name.lastIndexOf('.') + 1);
                 }
-                return offset;
-            }
-
-            @Override
-            public int size() {
-                return getObjectLayout().sizeInBytes(field.getType().getStorageKind());
-            }
-
-            @Override
-            public boolean isEmbedded() {
-                return false;
-            }
-
-            @Override
-            public int modifiers() {
-                ResolvedJavaField targetField = field.wrapped.wrapped;
-                if (targetField instanceof SubstitutionField) {
-                    targetField = ((SubstitutionField) targetField).getOriginal();
+                if (name.indexOf('$') >= 0) {
+                    name = name.substring(name.lastIndexOf('$') + 1);
                 }
-                return targetField.getModifiers();
-            }
-
-            private boolean isStatic() {
-                return Modifier.isStatic(modifiers());
-            }
-
-            private boolean isPrimitive() {
-                return field.getType().getStorageKind().isPrimitive();
-            }
-        }
-
-        private class NativeImageDebugMethodInfo extends NativeImageDebugHostedMethodInfo implements DebugMethodInfo {
-            NativeImageDebugMethodInfo(HostedMethod hostedMethod) {
-                super(hostedMethod);
             }
+        } else {
+            name = super.getMethodName(method);
         }
+        return name;
     }
 
-    private class NativeImageDebugInterfaceTypeInfo extends NativeImageDebugInstanceTypeInfo implements DebugInterfaceTypeInfo {
-
-        NativeImageDebugInterfaceTypeInfo(HostedInterface interfaceClass) {
-            super(interfaceClass);
-        }
-
-        @Override
-        public DebugTypeKind typeKind() {
-            return DebugTypeKind.INTERFACE;
-        }
+    @Override
+    public boolean isOverride(SharedMethod method) {
+        return method instanceof HostedMethod && allOverrides.contains(method);
     }
 
-    private class NativeImageDebugForeignTypeInfo extends NativeImageDebugInstanceTypeInfo implements DebugForeignTypeInfo {
-
-        ElementInfo elementInfo;
-
-        NativeImageDebugForeignTypeInfo(HostedType hostedType) {
-            this(hostedType, nativeLibs.findElementInfo(hostedType));
-        }
+    @Override
+    public boolean isVirtual(SharedMethod method) {
+        return method instanceof HostedMethod hostedMethod && hostedMethod.hasVTableIndex();
+    }
 
-        NativeImageDebugForeignTypeInfo(HostedType hostedType, ElementInfo elementInfo) {
-            super(hostedType);
-            assert isForeignWordType(hostedType);
-            this.elementInfo = elementInfo;
-            assert verifyElementInfo() : "unexpected element info kind";
-        }
+    @Override
+    public String getSymbolName(SharedMethod method) {
+        return NativeImage.localSymbolNameForMethod(method);
+    }
 
-        private boolean verifyElementInfo() {
-            // word types and some pointer types do not have element info
-            if (elementInfo == null || !(elementInfo instanceof SizableInfo)) {
-                return true;
-            }
-            switch (elementKind((SizableInfo) elementInfo)) {
-                // we may see these as the target kinds for foreign pointer types
-                case INTEGER:
-                case POINTER:
-                case FLOAT:
-                case UNKNOWN:
-                    return true;
-                // we may not see these as the target kinds for foreign pointer types
-                case STRING:
-                case BYTEARRAY:
-                case OBJECT:
-                default:
-                    return false;
+    private void processInterfaces(HostedType type, ClassEntry classEntry) {
+        for (HostedType interfaceType : type.getInterfaces()) {
+            TypeEntry entry = lookupTypeEntry(interfaceType);
+            if (entry instanceof InterfaceClassEntry interfaceClassEntry) {
+                interfaceClassEntry.addImplementor(classEntry);
+            } else {
+                // don't model the interface relationship when the Java interface actually identifies a
+                // foreign type
+                assert entry instanceof ForeignTypeEntry && classEntry instanceof ForeignTypeEntry;
             }
         }
+    }
 
-        @Override
-        public DebugTypeKind typeKind() {
-            return DebugTypeKind.FOREIGN;
-        }
-
-        @Override
-        public Stream<DebugFieldInfo> fieldInfoProvider() {
-            // TODO - generate fields for Struct and RawStruct types derived from element info
-            return orderedFieldsStream(elementInfo).map(this::createDebugForeignFieldInfo);
-        }
-
-        @Override
-        public int size() {
-            return elementSize(elementInfo);
-        }
-
-        DebugFieldInfo createDebugForeignFieldInfo(StructFieldInfo structFieldInfo) {
-            return new NativeImageDebugForeignFieldInfo(hostedType, structFieldInfo);
-        }
+    private void processArrayFields(HostedType type, ArrayTypeEntry arrayTypeEntry) {
+        JavaKind arrayKind = type.getBaseType().getJavaKind();
+        int headerSize = getObjectLayout().getArrayBaseOffset(arrayKind);
+        int arrayLengthOffset = getObjectLayout().getArrayLengthOffset();
+        int arrayLengthSize = getObjectLayout().sizeInBytes(JavaKind.Int);
+        assert arrayLengthOffset + arrayLengthSize <= headerSize;
+        arrayTypeEntry.addField(createSyntheticFieldEntry("len", arrayTypeEntry, (HostedType) metaAccess.lookupJavaType(JavaKind.Int.toJavaClass()), arrayLengthOffset, arrayLengthSize));
+    }
 
-        @Override
-        public String typedefName() {
-            String name = null;
-            if (elementInfo != null) {
-                if (elementInfo instanceof PointerToInfo) {
-                    name = ((PointerToInfo) elementInfo).getTypedefName();
-                } else if (elementInfo instanceof StructInfo) {
-                    name = ((StructInfo) elementInfo).getTypedefName();
-                }
-                if (name == null) {
-                    name = elementName(elementInfo);
-                }
-            }
-            return name;
+    private void processForeignTypeFields(HostedType type, ForeignTypeEntry foreignTypeEntry) {
+        ElementInfo elementInfo = nativeLibs.findElementInfo(type);
+        if (elementInfo instanceof StructInfo) {
+            elementInfo.getChildren().stream().filter(NativeImageDebugInfoProvider::isTypedField)
+                    .map(elt -> ((StructFieldInfo) elt))
+                    .sorted(Comparator.comparingInt(field -> field.getOffsetInfo().getProperty()))
+                    .forEach(field -> {
+                        HostedType fieldType = getFieldType(field);
+                        FieldEntry fieldEntry = createFieldEntry(foreignTypeEntry.getFileEntry(), field.getName(), foreignTypeEntry, fieldType, field.getOffsetInfo().getProperty(), field.getSizeInBytes(), fieldTypeIsEmbedded(field), 0);
+                        foreignTypeEntry.addField(fieldEntry);
+                    });
         }
+    }
 
-        @Override
-        public boolean isWord() {
-            return !isForeignPointerType(hostedType);
+    private void processFieldEntries(HostedType type, StructureTypeEntry structureTypeEntry) {
+        for (HostedField field : type.getInstanceFields(false)) {
+            structureTypeEntry.addField(createFieldEntry(field, structureTypeEntry));
         }
 
-        @Override
-        public boolean isStruct() {
-            return elementInfo instanceof StructInfo;
+        for (ResolvedJavaField field : type.getStaticFields()) {
+            assert field instanceof HostedField;
+            structureTypeEntry.addField(createFieldEntry((HostedField) field, structureTypeEntry));
         }
+    }
 
-        @Override
-        public boolean isPointer() {
-            if (elementInfo != null && elementInfo instanceof SizableInfo) {
-                return elementKind((SizableInfo) elementInfo) == ElementKind.POINTER;
+    private FieldEntry createFieldEntry(HostedField field, StructureTypeEntry ownerType) {
+        FileEntry fileEntry = lookupFileEntry(field);
+        String fieldName = field.getName();
+        HostedType valueType = field.getType();
+        JavaKind storageKind = field.getType().getStorageKind();
+        int size = getObjectLayout().sizeInBytes(storageKind);
+        int modifiers = field.getModifiers();
+        int offset = field.getLocation();
+        /*
+         * For static fields we need to add in the appropriate partition base but only if we
+         * have a real offset
+         */
+        if (Modifier.isStatic(modifiers) && offset >= 0) {
+            if (storageKind.isPrimitive()) {
+                offset += primitiveStartOffset;
             } else {
-                return false;
+                offset += referenceStartOffset;
             }
         }
 
-        @Override
-        public boolean isIntegral() {
-            if (elementInfo != null && elementInfo instanceof SizableInfo) {
-                return elementKind((SizableInfo) elementInfo) == ElementKind.INTEGER;
-            } else {
-                return false;
-            }
-        }
+        return createFieldEntry(fileEntry, fieldName, ownerType, valueType, offset, size, false, modifiers);
+    }
 
-        @Override
-        public boolean isFloat() {
-            if (elementInfo != null) {
-                return elementKind((SizableInfo) elementInfo) == ElementKind.FLOAT;
+    @Override
+    protected TypeEntry createTypeEntry(SharedType type) {
+        assert type instanceof HostedType;
+        HostedType hostedType = (HostedType) type;
+
+        String typeName = hostedType.toJavaName(); //stringTable.uniqueDebugString(idType.toJavaName());
+        int size = getTypeSize(hostedType);
+        long classOffset = getClassOffset(hostedType);
+        LoaderEntry loaderEntry = lookupLoaderEntry(hostedType);
+        String loaderName = loaderEntry == null ? uniqueNullStringEntry : loaderEntry.loaderId();
+        long typeSignature = getTypeSignature(typeName + loaderName);
+        long compressedTypeSignature = useHeapBase ? getTypeSignature(INDIRECT_PREFIX + typeName + loaderName) : typeSignature;
+
+        if (hostedType.isPrimitive()) {
+            JavaKind kind = hostedType.getStorageKind();
+            return new PrimitiveTypeEntry(typeName, size, classOffset, typeSignature, kind);
+        } else {
+            // otherwise we have a structured type
+            long layoutTypeSignature = getTypeSignature(LAYOUT_PREFIX + typeName + loaderName);
+            long compressedLayoutTypeSignature = useHeapBase ? getTypeSignature(INDIRECT_PREFIX + LAYOUT_PREFIX + typeName + loaderName) : layoutTypeSignature;
+            if (hostedType.isArray()) {
+                TypeEntry elementTypeEntry = lookupTypeEntry(hostedType.getComponentType());
+                // int baseSize = getObjectLayout().getArrayBaseOffset(hostedType.getComponentType().getStorageKind());
+                // int lengthOffset = getObjectLayout().getArrayLengthOffset();
+                return new ArrayTypeEntry(typeName, size, classOffset, typeSignature, compressedTypeSignature,
+                        layoutTypeSignature, compressedLayoutTypeSignature, elementTypeEntry, loaderEntry);
             } else {
-                return false;
-            }
-        }
+                // otherwise this is a class entry
+                ClassEntry superClass = hostedType.getSuperclass() == null ? null : (ClassEntry) lookupTypeEntry(hostedType.getSuperclass());
+                FileEntry fileEntry = lookupFileEntry(hostedType);
+                if (isForeignWordType(hostedType)) {
+                    ElementInfo elementInfo = nativeLibs.findElementInfo(hostedType);
+                    SizableInfo.ElementKind elementKind = elementInfo instanceof SizableInfo ? ((SizableInfo) elementInfo).getKind() : null;
+                    size = elementSize(elementInfo);
+                    String typedefName = typedefName(elementInfo); //stringTable.uniqueDebugString(typedefName(elementInfo));
+                    boolean isWord = !isForeignPointerType(hostedType);
+                    boolean isStruct = elementInfo instanceof StructInfo;
+                    boolean isPointer = elementKind == SizableInfo.ElementKind.POINTER;
+                    boolean isInteger = elementKind == SizableInfo.ElementKind.INTEGER;
+                    boolean isFloat = elementKind == SizableInfo.ElementKind.FLOAT;
+                    boolean isSigned = nativeLibs.isSigned(hostedType) || (isInteger && !((SizableInfo) elementInfo).isUnsigned());
+
+                    ForeignTypeEntry parentEntry = null;
+                    if (isStruct) {
+                        // look for the first interface that also has an associated StructInfo
+                        for (HostedInterface hostedInterface : hostedType.getInterfaces()) {
+                            ElementInfo otherInfo = nativeLibs.findElementInfo(hostedInterface);
+                            if (otherInfo instanceof StructInfo) {
+                                parentEntry = (ForeignTypeEntry) lookupTypeEntry(hostedInterface);
+                            }
+                        }
+                    }
 
-        @Override
-        public boolean isSigned() {
-            // pretty much everything is unsigned by default
-            // special cases are SignedWord which, obviously, points to a signed word and
-            // anything pointing to an integral type that is not tagged as unsigned
-            return (nativeLibs.isSigned(hostedType.getWrapped()) || (isIntegral() && !((SizableInfo) elementInfo).isUnsigned()));
-        }
+                    TypeEntry pointerToEntry = null;
+                    if (isPointer) {
+                        // any target type for the pointer will be defined by a CPointerTo or RawPointerTo
+                        // annotation
+                        CPointerTo cPointerTo = type.getAnnotation(CPointerTo.class);
+                        if (cPointerTo != null) {
+                            HostedType pointerTo = heap.hMetaAccess.lookupJavaType(cPointerTo.value());
+                            pointerToEntry = lookupTypeEntry(pointerTo);
+                        }
+                        RawPointerTo rawPointerTo = type.getAnnotation(RawPointerTo.class);
+                        if (rawPointerTo != null) {
+                            HostedType pointerTo = heap.hMetaAccess.lookupJavaType(rawPointerTo.value());
+                            pointerToEntry = lookupTypeEntry(pointerTo);
+                        }
+                    }
 
-        @Override
-        public ResolvedJavaType parent() {
-            if (isStruct()) {
-                // look for the first interface that also has an associated StructInfo
-                for (HostedInterface hostedInterface : hostedType.getInterfaces()) {
-                    ElementInfo otherInfo = nativeLibs.findElementInfo(hostedInterface);
-                    if (otherInfo instanceof StructInfo) {
-                        return getOriginal(hostedInterface);
+                    if (!isStruct && !isWord && !isInteger && !isFloat) {
+                        // set pointer to type as alias for the layout type
+                        if (pointerToEntry == null) {
+                            pointerToEntry = lookupTypeEntry(voidType);
+                        }
+                        if (pointerToEntry != null) {
+                            layoutTypeSignature = pointerToEntry.getTypeSignature();
+                        }
                     }
-                }
-            }
-            return null;
-        }
 
-        @Override
-        public ResolvedJavaType pointerTo() {
-            if (isPointer()) {
-                // any target type for the pointer will be defined by a CPointerTo or RawPointerTo
-                // annotation
-                CPointerTo cPointerTo = hostedType.getAnnotation(CPointerTo.class);
-                if (cPointerTo != null) {
-                    HostedType pointerTo = heap.hMetaAccess.lookupJavaType(cPointerTo.value());
-                    return getOriginal(pointerTo);
-                }
-                RawPointerTo rawPointerTo = hostedType.getAnnotation(RawPointerTo.class);
-                if (rawPointerTo != null) {
-                    HostedType pointerTo = heap.hMetaAccess.lookupJavaType(rawPointerTo.value());
-                    return getOriginal(pointerTo);
+                    return new ForeignTypeEntry(typeName, size, classOffset, typeSignature, layoutTypeSignature,
+                            superClass, fileEntry, loaderEntry, typedefName, parentEntry, pointerToEntry, isWord,
+                            isStruct, isPointer, isInteger, isSigned, isFloat);
+                } else if (hostedType.isEnum()) {
+                    return new EnumClassEntry(typeName, size, classOffset, typeSignature, compressedTypeSignature,
+                            layoutTypeSignature, compressedLayoutTypeSignature, superClass, fileEntry, loaderEntry);
+                } else if (hostedType.isInstanceClass()) {
+                    return new ClassEntry(typeName, size, classOffset, typeSignature, compressedTypeSignature,
+                            layoutTypeSignature, compressedLayoutTypeSignature, superClass, fileEntry, loaderEntry);
+                } else if (hostedType.isInterface()) {
+                    return new InterfaceClassEntry(typeName, size, classOffset, typeSignature, compressedTypeSignature,
+                            layoutTypeSignature, compressedLayoutTypeSignature, superClass, fileEntry, loaderEntry);
+                } else {
+                    throw new RuntimeException("Unknown type kind " + hostedType.getName());
                 }
             }
-            return null;
         }
     }
 
-    private class NativeImageDebugForeignFieldInfo extends NativeImageDebugFileInfo implements DebugInfoProvider.DebugFieldInfo {
-        StructFieldInfo structFieldInfo;
-
-        NativeImageDebugForeignFieldInfo(HostedType hostedType, StructFieldInfo structFieldInfo) {
-            super(hostedType);
-            this.structFieldInfo = structFieldInfo;
-        }
-
-        @Override
-        public int size() {
-            return structFieldInfo.getSizeInBytes();
-        }
-
-        @Override
-        public int offset() {
-            return structFieldInfo.getOffsetInfo().getProperty();
-        }
-
-        @Override
-        public String name() {
-            return structFieldInfo.getName();
+    @Override
+    public FileEntry lookupFileEntry(ResolvedJavaType type) {
+        if (type instanceof HostedType hostedType) {
+            // unwrap to the underlying class either the original or target class
+            // we want any substituted target if there is one. directly unwrapping will
+            // do what we want.
+            ResolvedJavaType javaType = hostedType.getWrapped().getWrapped();
+            Class<?> clazz = hostedType.getJavaClass();
+            SourceManager sourceManager = ImageSingletons.lookup(SourceManager.class);
+            Path filePath = sourceManager.findAndCacheSource(hostedType, clazz, debugContext);
+            if (filePath != null) {
+                return lookupFileEntry(filePath);
+            }
         }
+        // conjure up an appropriate, unique file name to keep tools happy
+        // even though we cannot find a corresponding source
+        return super.lookupFileEntry(type);
+    }
 
-        @Override
-        public ResolvedJavaType valueType() {
-            // we need to ensure the hosted type identified for the field value gets translated to
-            // an original in order to be consistent with id types for substitutions
-            return getOriginal(getFieldType(structFieldInfo));
+    @Override
+    public FileEntry lookupFileEntry(ResolvedJavaMethod method) {
+        if (method instanceof HostedMethod hostedMethod && hostedMethod.getWrapped().getWrapped() instanceof SubstitutionMethod substitutionMethod) {
+            // we always want to look up the file of the annotated method
+            method = substitutionMethod.getAnnotated();
         }
+        return super.lookupFileEntry(method);
+    }
 
-        @Override
-        public boolean isEmbedded() {
-            // this is true when the field has an ADDRESS accessor type
-            return fieldTypeIsEmbedded(structFieldInfo);
+    private int getTypeSize(HostedType type) {
+        switch (type) {
+            case HostedInstanceClass hostedInstanceClass -> {
+                /* We know the actual instance size in bytes. */
+                return hostedInstanceClass.getInstanceSize();
+            }
+            case HostedArrayClass hostedArrayClass -> {
+                /* Use the size of header common to all arrays of this type. */
+                return getObjectLayout().getArrayBaseOffset(type.getComponentType().getStorageKind());
+            }
+            case HostedInterface hostedInterface -> {
+                /* Use the size of the header common to all implementors. */
+                return getObjectLayout().getFirstFieldOffset();
+            }
+            case HostedPrimitiveType hostedPrimitiveType -> {
+                /* Use the number of bytes needed to store the value. */
+                JavaKind javaKind = type.getStorageKind();
+                return (javaKind == JavaKind.Void ? 0 : javaKind.getByteCount());
+            }
+            default -> {
+                return 0;
+            }
         }
+    }
 
-        @Override
-        public int modifiers() {
-            return 0;
-        }
-    }
-
-    private class NativeImageDebugArrayTypeInfo extends NativeImageDebugTypeInfo implements DebugArrayTypeInfo {
-        HostedArrayClass arrayClass;
-        List<DebugFieldInfo> fieldInfos;
-
-        NativeImageDebugArrayTypeInfo(HostedArrayClass arrayClass) {
-            super(arrayClass);
-            this.arrayClass = arrayClass;
-            this.fieldInfos = new LinkedList<>();
-            JavaKind arrayKind = arrayClass.getBaseType().getJavaKind();
-            int headerSize = getObjectLayout().getArrayBaseOffset(arrayKind);
-            int arrayLengthOffset = getObjectLayout().getArrayLengthOffset();
-            int arrayLengthSize = getObjectLayout().sizeInBytes(JavaKind.Int);
-            assert arrayLengthOffset + arrayLengthSize <= headerSize;
-
-            addField("len", javaKindToHostedType.get(JavaKind.Int), arrayLengthOffset, arrayLengthSize);
-        }
-
-        void addField(String name, ResolvedJavaType valueType, int offset, @SuppressWarnings("hiding") int size) {
-            NativeImageDebugHeaderFieldInfo fieldinfo = new NativeImageDebugHeaderFieldInfo(name, valueType, offset, size);
-            fieldInfos.add(fieldinfo);
-        }
-
-        @Override
-        public long typeSignature(String prefix) {
-            HostedType elementType = hostedType.getComponentType();
-            while (elementType.isArray()) {
-                elementType = elementType.getComponentType();
-            }
-            String loaderId = "";
-            if (elementType.isInstanceClass() || elementType.isInterface() || elementType.isEnum()) {
-                loaderId = UniqueShortNameProvider.singleton().uniqueShortLoaderName(elementType.getJavaClass().getClassLoader());
-            }
-            return super.typeSignature(prefix + loaderId);
-        }
-
-        @Override
-        public DebugTypeKind typeKind() {
-            return DebugTypeKind.ARRAY;
-        }
-
-        @Override
-        public int baseSize() {
-            return getObjectLayout().getArrayBaseOffset(arrayClass.getComponentType().getStorageKind());
-        }
-
-        @Override
-        public int lengthOffset() {
-            return getObjectLayout().getArrayLengthOffset();
-        }
-
-        @Override
-        public ResolvedJavaType elementType() {
-            HostedType elementType = arrayClass.getComponentType();
-            return getOriginal(elementType);
-        }
-
-        @Override
-        public Stream<DebugFieldInfo> fieldInfoProvider() {
-            return fieldInfos.stream();
-        }
-    }
-
-    private class NativeImageDebugPrimitiveTypeInfo extends NativeImageDebugTypeInfo implements DebugPrimitiveTypeInfo {
-        private final HostedPrimitiveType primitiveType;
-
-        NativeImageDebugPrimitiveTypeInfo(HostedPrimitiveType primitiveType) {
-            super(primitiveType);
-            this.primitiveType = primitiveType;
-        }
-
-        @Override
-        public long typeSignature(String prefix) {
-            /*
-             * primitive types never need an indirection so use the same signature for places where
-             * we might want a special type
-             */
-            return super.typeSignature("");
-        }
-
-        @Override
-        public DebugTypeKind typeKind() {
-            return DebugTypeKind.PRIMITIVE;
-        }
-
-        @Override
-        public int bitCount() {
-            JavaKind javaKind = primitiveType.getStorageKind();
-            return (javaKind == JavaKind.Void ? 0 : javaKind.getBitCount());
-        }
-
-        @Override
-        public char typeChar() {
-            return primitiveType.getStorageKind().getTypeChar();
-        }
-
-        @Override
-        public int flags() {
-            char typeChar = primitiveType.getStorageKind().getTypeChar();
-            switch (typeChar) {
-                case 'B':
-                case 'S':
-                case 'I':
-                case 'J': {
-                    return FLAG_NUMERIC | FLAG_INTEGRAL | FLAG_SIGNED;
-                }
-                case 'C': {
-                    return FLAG_NUMERIC | FLAG_INTEGRAL;
-                }
-                case 'F':
-                case 'D': {
-                    return FLAG_NUMERIC;
-                }
-                default: {
-                    assert typeChar == 'V' || typeChar == 'Z';
-                    return 0;
-                }
-            }
-        }
-    }
-
-    @SuppressWarnings("try")
-    private NativeImageDebugTypeInfo createDebugTypeInfo(HostedType hostedType) {
-        try (DebugContext.Scope s = debugContext.scope("DebugTypeInfo", hostedType.toJavaName())) {
-            if (isForeignWordType(hostedType)) {
-                assert hostedType.isInterface() || hostedType.isInstanceClass() : "foreign type must be instance class or interface!";
-                logForeignTypeInfo(hostedType);
-                return new NativeImageDebugForeignTypeInfo(hostedType);
-            } else if (hostedType.isEnum()) {
-                return new NativeImageDebugEnumTypeInfo((HostedInstanceClass) hostedType);
-            } else if (hostedType.isInstanceClass()) {
-                return new NativeImageDebugInstanceTypeInfo(hostedType);
-            } else if (hostedType.isInterface()) {
-                return new NativeImageDebugInterfaceTypeInfo((HostedInterface) hostedType);
-            } else if (hostedType.isArray()) {
-                return new NativeImageDebugArrayTypeInfo((HostedArrayClass) hostedType);
-            } else if (hostedType.isPrimitive()) {
-                return new NativeImageDebugPrimitiveTypeInfo((HostedPrimitiveType) hostedType);
-            } else {
-                throw new RuntimeException("Unknown type kind " + hostedType.getName());
-            }
-        } catch (Throwable e) {
-            throw debugContext.handle(e);
-        }
-    }
-
-    private void logForeignTypeInfo(HostedType hostedType) {
-        if (!isForeignPointerType(hostedType)) {
-            // non pointer type must be an interface because an instance needs to be pointed to
-            assert hostedType.isInterface();
-            // foreign word types never have element info
-            debugContext.log(DebugContext.VERBOSE_LEVEL, "Foreign word type %s", hostedType.toJavaName());
-        } else {
-            ElementInfo elementInfo = nativeLibs.findElementInfo(hostedType);
-            logForeignPointerType(hostedType, elementInfo);
-        }
-    }
-
-    private void logForeignPointerType(HostedType hostedType, ElementInfo elementInfo) {
-        if (elementInfo == null) {
-            // can happen for a generic (void*) pointer or a class
-            if (hostedType.isInterface()) {
-                debugContext.log(DebugContext.VERBOSE_LEVEL, "Foreign pointer type %s", hostedType.toJavaName());
-            } else {
-                debugContext.log(DebugContext.VERBOSE_LEVEL, "Foreign pointer type %s (class)", hostedType.toJavaName());
-            }
-        } else if (elementInfo instanceof PointerToInfo) {
-            logPointerToInfo(hostedType, (PointerToInfo) elementInfo);
-        } else if (elementInfo instanceof StructInfo) {
-            if (elementInfo instanceof RawStructureInfo) {
-                logRawStructureInfo(hostedType, (RawStructureInfo) elementInfo);
-            } else {
-                logStructInfo(hostedType, (StructInfo) elementInfo);
-            }
-        }
-    }
-
-    private void logPointerToInfo(HostedType hostedType, PointerToInfo pointerToInfo) {
-        debugContext.log(DebugContext.VERBOSE_LEVEL, "Foreign pointer type %s %s", hostedType.toJavaName(), elementKind(pointerToInfo));
-        assert hostedType.isInterface();
-        int size = elementSize(pointerToInfo);
-        boolean isUnsigned = pointerToInfo.isUnsigned();
-        String typedefName = pointerToInfo.getTypedefName();
-        debugContext.log("element size = %d", size);
-        debugContext.log("%s", (isUnsigned ? "<unsigned>" : "<signed>"));
-        if (typedefName != null) {
-            debugContext.log("typedefname = %s", typedefName);
-        }
-        dumpElementInfo(pointerToInfo);
-    }
-
-    private void logStructInfo(HostedType hostedType, StructInfo structInfo) {
-        debugContext.log(DebugContext.VERBOSE_LEVEL, "Foreign struct type %s %s", hostedType.toJavaName(), elementKind(structInfo));
-        assert hostedType.isInterface();
-        boolean isIncomplete = structInfo.isIncomplete();
-        if (isIncomplete) {
-            debugContext.log("<incomplete>");
-        } else {
-            debugContext.log("complete : element size = %d", elementSize(structInfo));
-        }
-        String typedefName = structInfo.getTypedefName();
-        if (typedefName != null) {
-            debugContext.log("    typedefName = %s", typedefName);
-        }
-        dumpElementInfo(structInfo);
-    }
-
-    private void logRawStructureInfo(HostedType hostedType, RawStructureInfo rawStructureInfo) {
-        debugContext.log(DebugContext.VERBOSE_LEVEL, "Foreign raw struct type %s %s", hostedType.toJavaName(), elementKind(rawStructureInfo));
-        assert hostedType.isInterface();
-        debugContext.log("element size = %d", elementSize(rawStructureInfo));
-        String typedefName = rawStructureInfo.getTypedefName();
-        if (typedefName != null) {
-            debugContext.log("    typedefName = %s", typedefName);
-        }
-        dumpElementInfo(rawStructureInfo);
-    }
-
-    private int structFieldComparator(StructFieldInfo f1, StructFieldInfo f2) {
-        int offset1 = f1.getOffsetInfo().getProperty();
-        int offset2 = f2.getOffsetInfo().getProperty();
-        return offset1 - offset2;
-    }
-
-    private Stream<StructFieldInfo> orderedFieldsStream(ElementInfo elementInfo) {
-        if (elementInfo instanceof RawStructureInfo || elementInfo instanceof StructInfo) {
-            return elementInfo.getChildren().stream().filter(elt -> isTypedField(elt))
-                            .map(elt -> ((StructFieldInfo) elt))
-                            .sorted(this::structFieldComparator);
-        } else {
-            return Stream.empty();
-        }
-    }
-
-    private void dumpElementInfo(ElementInfo elementInfo) {
-        if (elementInfo != null) {
-            debugContext.log("Element Info {%n%s}", formatElementInfo(elementInfo));
-        } else {
-            debugContext.log("Element Info {}");
-        }
-    }
-
-    private static String formatElementInfo(ElementInfo elementInfo) {
-        StringBuilder stringBuilder = new StringBuilder();
-        formatElementInfo(elementInfo, stringBuilder, 0);
-        return stringBuilder.toString();
-    }
-
-    private static void formatElementInfo(ElementInfo elementInfo, StringBuilder stringBuilder, int indent) {
-        indentElementInfo(stringBuilder, indent);
-        formatSingleElement(elementInfo, stringBuilder);
-        List<ElementInfo> children = elementInfo.getChildren();
-        if (children == null || children.isEmpty()) {
-            stringBuilder.append("\n");
-        } else {
-            stringBuilder.append(" {\n");
-            for (ElementInfo child : children) {
-                formatElementInfo(child, stringBuilder, indent + 1);
-            }
-            indentElementInfo(stringBuilder, indent);
-            stringBuilder.append("}\n");
-        }
-    }
-
-    private static void formatSingleElement(ElementInfo elementInfo, StringBuilder stringBuilder) {
-        stringBuilder.append(ClassUtil.getUnqualifiedName(elementInfo.getClass()));
-        stringBuilder.append(" : ");
-        stringBuilder.append(elementName(elementInfo));
-        if (elementInfo instanceof PropertyInfo<?>) {
-            stringBuilder.append(" = ");
-            formatPropertyInfo((PropertyInfo<?>) elementInfo, stringBuilder);
-        }
-        if (elementInfo instanceof AccessorInfo) {
-            stringBuilder.append(" ");
-            stringBuilder.append(((AccessorInfo) elementInfo).getAccessorKind());
-        }
-    }
-
-    private static <T> void formatPropertyInfo(PropertyInfo<T> propertyInfo, StringBuilder stringBuilder) {
-        stringBuilder.append(propertyInfo.getProperty());
-    }
-
-    private static void indentElementInfo(StringBuilder stringBuilder, int indent) {
-        for (int i = 0; i <= indent; i++) {
-            stringBuilder.append("  ");
-        }
-    }
-
-    protected abstract class NativeImageDebugBaseMethodInfo extends NativeImageDebugFileInfo implements DebugMethodInfo {
-        protected final ResolvedJavaMethod method;
-        protected int line;
-        protected final List<DebugLocalInfo> paramInfo;
-        protected final DebugLocalInfo thisParamInfo;
-
-        NativeImageDebugBaseMethodInfo(ResolvedJavaMethod m) {
-            super(m);
-            // We occasionally see an AnalysisMethod as input to this constructor.
-            // That can happen if the points to analysis builds one into a node
-            // source position when building the initial graph. The global
-            // replacement that is supposed to ensure the compiler sees HostedXXX
-            // types rather than AnalysisXXX types appears to skip translating
-            // method references in node source positions. So, we do the translation
-            // here just to make sure we use a HostedMethod wherever possible.
-            method = promoteAnalysisToHosted(m);
-            LineNumberTable lineNumberTable = method.getLineNumberTable();
-            line = (lineNumberTable != null ? lineNumberTable.getLineNumber(0) : 0);
-            this.paramInfo = createParamInfo(method, line);
-            // We use the target modifiers to decide where to install any first param
-            // even though we may have added it according to whether method is static.
-            // That's because in a few special cases method is static but the original
-            // DebugFrameLocals
-            // from which it is derived is an instance method. This appears to happen
-            // when a C function pointer masquerades as a method. Whatever parameters
-            // we pass through need to match the definition of the original.
-            if (Modifier.isStatic(modifiers())) {
-                this.thisParamInfo = null;
-            } else {
-                this.thisParamInfo = paramInfo.remove(0);
-            }
-        }
-
-        private ResolvedJavaMethod promoteAnalysisToHosted(ResolvedJavaMethod m) {
-            if (m instanceof AnalysisMethod) {
-                return heap.hUniverse.lookup(m);
-            }
-            if (!(m instanceof HostedMethod)) {
-                debugContext.log(DebugContext.DETAILED_LEVEL, "Method is neither Hosted nor Analysis : %s.%s%s", m.getDeclaringClass().getName(), m.getName(),
-                                m.getSignature().toMethodDescriptor());
-            }
-            return m;
-        }
-
-        private ResolvedJavaMethod originalMethod() {
-            // unwrap to an original method as far as we can
-            ResolvedJavaMethod targetMethod = method;
-            while (targetMethod instanceof WrappedJavaMethod) {
-                targetMethod = ((WrappedJavaMethod) targetMethod).getWrapped();
-            }
-            // if we hit a substitution then we can translate to the original
-            // for identity otherwise we use whatever we unwrapped to.
-            if (targetMethod instanceof SubstitutionMethod) {
-                targetMethod = ((SubstitutionMethod) targetMethod).getOriginal();
-            }
-            return targetMethod;
-        }
-
-        /**
-         * Return the unique type that owns this method.
-         * <p/>
-         * In the absence of substitutions the returned type result is simply the original JVMCI
-         * implementation type that declares the associated Java method. Identifying this type may
-         * involve unwrapping from Hosted universe to Analysis universe to the original JVMCI
-         * universe.
-         * <p/>
-         *
-         * In the case where the method itself is either an (annotated) substitution or declared by
-         * a class that is a (annotated) substitution then the link from substitution to original is
-         * also 'unwrapped' to arrive at the original type. In cases where a substituted method has
-         * no original the class of the substitution is used, for want of anything better.
-         * <p/>
-         *
-         * This unwrapping strategy avoids two possible ambiguities that would compromise use of the
-         * returned value as a unique 'owner'. Firstly, if the same method is ever presented via
-         * both its HostedMethod incarnation and its original ResolvedJavaMethod incarnation then
-         * ownership will always be signalled via the original type. This ensures that views of the
-         * method presented via the list of included HostedTypes and via the list of CompiledMethod
-         * objects and their embedded substructure (such as inline caller hierarchies) are correctly
-         * identified.
-         * <p/>
-         *
-         * Secondly, when a substituted method or method of a substituted class is presented it ends
-         * up being collated with methods of the original class rather than the class which actually
-         * defines it, avoiding an artificial split of methods that belong to the same underlying
-         * runtime type into two distinct types in the debuginfo model. As an example, this ensures
-         * that all methods of substitution class DynamicHub are collated with methods of class
-         * java.lang.Class, the latter being the type whose name gets written into the debug info
-         * and gets used to name the method in the debugger. Note that this still allows the
-         * method's line info to be associated with the correct file i.e. it does not compromise
-         * breaking and stepping through the code.
-         *
-         * @return the unique type that owns this method
-         */
-        @Override
-        public ResolvedJavaType ownerType() {
-            ResolvedJavaType result;
-            if (method instanceof HostedMethod) {
-                result = getDeclaringClass((HostedMethod) method, true);
-            } else {
-                result = method.getDeclaringClass();
-            }
-            while (result instanceof WrappedJavaType) {
-                result = ((WrappedJavaType) result).getWrapped();
-            }
-            return result;
-        }
-
-        @Override
-        public ResolvedJavaMethod idMethod() {
-            // translating to the original ensures we equate a
-            // substituted method with the original in case we ever see both
-            return originalMethod();
-        }
-
-        @Override
-        public String name() {
-            ResolvedJavaMethod targetMethod = originalMethod();
-            String name = targetMethod.getName();
-            if (name.equals("<init>")) {
-                if (method instanceof HostedMethod) {
-                    name = getDeclaringClass((HostedMethod) method, true).toJavaName();
-                    if (name.indexOf('.') >= 0) {
-                        name = name.substring(name.lastIndexOf('.') + 1);
-                    }
-                    if (name.indexOf('$') >= 0) {
-                        name = name.substring(name.lastIndexOf('$') + 1);
-                    }
-                } else {
-                    name = targetMethod.format("%h");
-                    if (name.indexOf('$') >= 0) {
-                        name = name.substring(name.lastIndexOf('$') + 1);
-                    }
-                }
-            }
-            return name;
-        }
-
-        @Override
-        public ResolvedJavaType valueType() {
-            ResolvedJavaType resultType = (ResolvedJavaType) method.getSignature().getReturnType(null);
-            if (resultType instanceof HostedType) {
-                return getOriginal((HostedType) resultType);
-            }
-            return resultType;
-        }
-
-        @Override
-        public DebugLocalInfo[] getParamInfo() {
-            return paramInfo.toArray(new DebugLocalInfo[paramInfo.size()]);
-        }
-
-        @Override
-        public DebugLocalInfo getThisParamInfo() {
-            return thisParamInfo;
-        }
-
-        @Override
-        public String symbolNameForMethod() {
-            return NativeImage.localSymbolNameForMethod(method);
-        }
-
-        @Override
-        public boolean isDeoptTarget() {
-            if (method instanceof HostedMethod) {
-                return ((HostedMethod) method).isDeoptTarget();
-            }
-            return name().endsWith(StableMethodNameFormatter.MULTI_METHOD_KEY_SEPARATOR);
-        }
-
-        @Override
-        public int modifiers() {
-            if (method instanceof HostedMethod) {
-                return getOriginalModifiers((HostedMethod) method);
-            }
-            return method.getModifiers();
-        }
-
-        @Override
-        public boolean isConstructor() {
-            return method.isConstructor();
-        }
-
-        @Override
-        public boolean isVirtual() {
-            return method instanceof HostedMethod && ((HostedMethod) method).hasVTableIndex();
-        }
-
-        @Override
-        public boolean isOverride() {
-            return method instanceof HostedMethod && allOverrides.contains(method);
-        }
-
-        @Override
-        public int vtableOffset() {
-            /* TODO - convert index to offset (+ sizeof DynamicHub) */
-            return isVirtual() ? ((HostedMethod) method).getVTableIndex() : -1;
-        }
-    }
-
-    private List<DebugLocalInfo> createParamInfo(ResolvedJavaMethod method, int line) {
-        Signature signature = method.getSignature();
-        int parameterCount = signature.getParameterCount(false);
-        List<DebugLocalInfo> paramInfos = new ArrayList<>(parameterCount);
-        LocalVariableTable table = method.getLocalVariableTable();
-        int slot = 0;
-        ResolvedJavaType ownerType = method.getDeclaringClass();
-        if (!method.isStatic()) {
-            JavaKind kind = ownerType.getJavaKind();
-            JavaKind storageKind = isForeignWordType(ownerType, ownerType) ? JavaKind.Long : kind;
-            assert kind == JavaKind.Object : "must be an object";
-            paramInfos.add(new NativeImageDebugLocalInfo("this", storageKind, ownerType, slot, line));
-            slot += kind.getSlotCount();
-        }
-        for (int i = 0; i < parameterCount; i++) {
-            Local local = (table == null ? null : table.getLocal(slot, 0));
-            String name = (local != null ? local.getName() : "__" + i);
-            ResolvedJavaType paramType = (ResolvedJavaType) signature.getParameterType(i, null);
-            JavaKind kind = paramType.getJavaKind();
-            JavaKind storageKind = isForeignWordType(paramType, ownerType) ? JavaKind.Long : kind;
-            paramInfos.add(new NativeImageDebugLocalInfo(name, storageKind, paramType, slot, line));
-            slot += kind.getSlotCount();
-        }
-        return paramInfos;
-    }
-
-    private static boolean isIntegralKindPromotion(JavaKind promoted, JavaKind original) {
-        return (promoted == JavaKind.Int &&
-                        (original == JavaKind.Boolean || original == JavaKind.Byte || original == JavaKind.Short || original == JavaKind.Char));
-    }
-
-    protected abstract class NativeImageDebugHostedMethodInfo extends NativeImageDebugBaseMethodInfo {
-        protected final HostedMethod hostedMethod;
-
-        NativeImageDebugHostedMethodInfo(HostedMethod method) {
-            super(method);
-            this.hostedMethod = method;
-        }
-
-        @Override
-        public int line() {
-            return line;
-        }
-
-        @Override
-        public boolean isVirtual() {
-            return hostedMethod.hasVTableIndex();
-        }
-
-        @Override
-        public int vtableOffset() {
-            /*
-             * TODO - provide correct offset, not index. In Graal, the vtable is appended after the
-             * dynamicHub object, so can't just multiply by sizeof(pointer).
-             */
-            return hostedMethod.hasVTableIndex() ? hostedMethod.getVTableIndex() : -1;
-        }
-
-        /**
-         * Returns true if this is an override virtual method. Used in Windows CodeView output.
-         *
-         * @return true if this is a virtual method and overrides an existing method.
-         */
-        @Override
-        public boolean isOverride() {
-            return allOverrides.contains(hostedMethod);
-        }
-    }
-
-    /**
-     * Implementation of the DebugCodeInfo API interface that allows code info to be passed to an
-     * ObjectFile when generation of debug info is enabled.
-     */
-    private class NativeImageDebugCodeInfo extends NativeImageDebugHostedMethodInfo implements DebugCodeInfo {
-        private final CompilationResult compilation;
-
-        NativeImageDebugCodeInfo(HostedMethod method, CompilationResult compilation) {
-            super(method);
-            this.compilation = compilation;
-        }
-
-        @SuppressWarnings("try")
-        @Override
-        public void debugContext(Consumer<DebugContext> action) {
-            try (DebugContext.Scope s = debugContext.scope("DebugCodeInfo", hostedMethod)) {
-                action.accept(debugContext);
-            } catch (Throwable e) {
-                throw debugContext.handle(e);
-            }
-        }
-
-        @Override
-        public long addressLo() {
-            return hostedMethod.getCodeAddressOffset();
-        }
-
-        @Override
-        public long addressHi() {
-            return hostedMethod.getCodeAddressOffset() + compilation.getTargetCodeSize();
-        }
-
-        @Override
-        public Stream<DebugLocationInfo> locationInfoProvider() {
-            // can we still provide locals if we have no file name?
-            if (fileName().length() == 0) {
-                return Stream.empty();
-            }
-            boolean omitInline = SubstrateOptions.OmitInlinedMethodDebugLineInfo.getValue();
-            int maxDepth = SubstrateOptions.DebugCodeInfoMaxDepth.getValue();
-            boolean useSourceMappings = SubstrateOptions.DebugCodeInfoUseSourceMappings.getValue();
-            if (omitInline) {
-                if (!SubstrateOptions.DebugCodeInfoMaxDepth.hasBeenSet()) {
-                    /* TopLevelVisitor will not go deeper than level 2 */
-                    maxDepth = 2;
-                }
-                if (!SubstrateOptions.DebugCodeInfoUseSourceMappings.hasBeenSet()) {
-                    /* Skip expensive CompilationResultFrameTree building with SourceMappings */
-                    useSourceMappings = false;
-                }
-            }
-            final CallNode root = new Builder(debugContext, compilation.getTargetCodeSize(), maxDepth, useSourceMappings, true).build(compilation);
-            if (root == null) {
-                return Stream.empty();
-            }
-            final List<DebugLocationInfo> locationInfos = new ArrayList<>();
-            int frameSize = getFrameSize();
-            final Visitor visitor = (omitInline ? new TopLevelVisitor(locationInfos, frameSize) : new MultiLevelVisitor(locationInfos, frameSize));
-            // arguments passed by visitor to apply are
-            // NativeImageDebugLocationInfo caller location info
-            // CallNode nodeToEmbed parent call node to convert to entry code leaf
-            // NativeImageDebugLocationInfo leaf into which current leaf may be merged
-            root.visitChildren(visitor, (Object) null, (Object) null, (Object) null);
-            // try to add a location record for offset zero
-            updateInitialLocation(locationInfos);
-            return locationInfos.stream();
-        }
-
-        private int findMarkOffset(SubstrateMarkId markId) {
-            for (CompilationResult.CodeMark mark : compilation.getMarks()) {
-                if (mark.id.equals(markId)) {
-                    return mark.pcOffset;
-                }
-            }
-            return -1;
-        }
-
-        private void updateInitialLocation(List<DebugLocationInfo> locationInfos) {
-            int prologueEnd = findMarkOffset(SubstrateMarkId.PROLOGUE_END);
-            if (prologueEnd < 0) {
-                // this is not a normal compiled method so give up
-                return;
-            }
-            int stackDecrement = findMarkOffset(SubstrateMarkId.PROLOGUE_DECD_RSP);
-            if (stackDecrement < 0) {
-                // this is not a normal compiled method so give up
-                return;
-            }
-            // if there are any location info records then the first one will be for
-            // a nop which follows the stack decrement, stack range check and pushes
-            // of arguments into the stack frame.
-            //
-            // We can construct synthetic location info covering the first instruction
-            // based on the method arguments and the calling convention and that will
-            // normally be valid right up to the nop. In exceptional cases a call
-            // might pass arguments on the stack, in which case the stack decrement will
-            // invalidate the original stack locations. Providing location info for that
-            // case requires adding two locations, one for initial instruction that does
-            // the stack decrement and another for the range up to the nop. They will
-            // be essentially the same but the stack locations will be adjusted to account
-            // for the different value of the stack pointer.
-
-            if (locationInfos.isEmpty()) {
-                // this is not a normal compiled method so give up
-                return;
-            }
-            NativeImageDebugLocationInfo firstLocation = (NativeImageDebugLocationInfo) locationInfos.get(0);
-            long firstLocationOffset = firstLocation.addressLo();
-
-            if (firstLocationOffset == 0) {
-                // this is not a normal compiled method so give up
-                return;
-            }
-            if (firstLocationOffset < prologueEnd) {
-                // this is not a normal compiled method so give up
-                return;
-            }
-            // create a synthetic location record including details of passed arguments
-            ParamLocationProducer locProducer = new ParamLocationProducer(hostedMethod);
-            debugContext.log(DebugContext.DETAILED_LEVEL, "Add synthetic Location Info : %s (0, %d)", method.getName(), firstLocationOffset - 1);
-            NativeImageDebugLocationInfo locationInfo = new NativeImageDebugLocationInfo(method, firstLocationOffset, locProducer);
-            // if the prologue extends beyond the stack extend and uses the stack then the info
-            // needs
-            // splitting at the extend point with the stack offsets adjusted in the new info
-            if (locProducer.usesStack() && firstLocationOffset > stackDecrement) {
-                NativeImageDebugLocationInfo splitLocationInfo = locationInfo.split(stackDecrement, getFrameSize());
-                debugContext.log(DebugContext.DETAILED_LEVEL, "Split synthetic Location Info : %s (%d, %d) (%d, %d)", locationInfo.name(), 0,
-                                locationInfo.addressLo() - 1, locationInfo.addressLo(), locationInfo.addressHi() - 1);
-                locationInfos.add(0, splitLocationInfo);
-            }
-            locationInfos.add(0, locationInfo);
-        }
-
-        // indices for arguments passed to SingleLevelVisitor::apply
-
-        protected static final int CALLER_INFO = 0;
-        protected static final int PARENT_NODE_TO_EMBED = 1;
-        protected static final int LAST_LEAF_INFO = 2;
-
-        private abstract class SingleLevelVisitor implements Visitor {
-
-            protected final List<DebugLocationInfo> locationInfos;
-            protected final int frameSize;
-
-            SingleLevelVisitor(List<DebugLocationInfo> locationInfos, int frameSize) {
-                this.locationInfos = locationInfos;
-                this.frameSize = frameSize;
-            }
-
-            public NativeImageDebugLocationInfo process(FrameNode node, NativeImageDebugLocationInfo callerInfo) {
-                NativeImageDebugLocationInfo locationInfo;
-                if (node instanceof CallNode) {
-                    // this node represents an inline call range so
-                    // add a locationinfo to cover the range of the call
-                    locationInfo = createCallLocationInfo((CallNode) node, callerInfo, frameSize);
-                } else if (isBadLeaf(node, callerInfo)) {
-                    locationInfo = createBadLeafLocationInfo(node, callerInfo, frameSize);
-                } else {
-                    // this is leaf method code so add details of its range
-                    locationInfo = createLeafLocationInfo(node, callerInfo, frameSize);
-                }
-                return locationInfo;
-            }
-        }
-
-        private class TopLevelVisitor extends SingleLevelVisitor {
-            TopLevelVisitor(List<DebugLocationInfo> locationInfos, int frameSize) {
-                super(locationInfos, frameSize);
-            }
-
-            @Override
-            public void apply(FrameNode node, Object... args) {
-                if (skipNode(node)) {
-                    // this is a bogus wrapper so skip it and transform the wrapped node instead
-                    node.visitChildren(this, args);
-                } else {
-                    NativeImageDebugLocationInfo locationInfo = process(node, null);
-                    if (node instanceof CallNode) {
-                        locationInfos.add(locationInfo);
-                        // erase last leaf (if present) since there is an intervening call range
-                        invalidateMerge(args);
-                    } else {
-                        locationInfo = tryMerge(locationInfo, args);
-                        if (locationInfo != null) {
-                            locationInfos.add(locationInfo);
-                        }
-                    }
-                }
-            }
-        }
-
-        public class MultiLevelVisitor extends SingleLevelVisitor {
-            MultiLevelVisitor(List<DebugLocationInfo> locationInfos, int frameSize) {
-                super(locationInfos, frameSize);
-            }
-
-            @Override
-            public void apply(FrameNode node, Object... args) {
-                if (skipNode(node)) {
-                    // this is a bogus wrapper so skip it and transform the wrapped node instead
-                    node.visitChildren(this, args);
-                } else {
-                    NativeImageDebugLocationInfo callerInfo = (NativeImageDebugLocationInfo) args[CALLER_INFO];
-                    CallNode nodeToEmbed = (CallNode) args[PARENT_NODE_TO_EMBED];
-                    if (nodeToEmbed != null) {
-                        if (embedWithChildren(nodeToEmbed, node)) {
-                            // embed a leaf range for the method start that was included in the
-                            // parent CallNode
-                            // its end range is determined by the start of the first node at this
-                            // level
-                            NativeImageDebugLocationInfo embeddedLocationInfo = createEmbeddedParentLocationInfo(nodeToEmbed, node, callerInfo, frameSize);
-                            locationInfos.add(embeddedLocationInfo);
-                            // since this is a leaf node we can merge later leafs into it
-                            initMerge(embeddedLocationInfo, args);
-                        }
-                        // reset args so we only embed the parent node before the first node at
-                        // this level
-                        args[PARENT_NODE_TO_EMBED] = nodeToEmbed = null;
-                    }
-                    NativeImageDebugLocationInfo locationInfo = process(node, callerInfo);
-                    if (node instanceof CallNode) {
-                        CallNode callNode = (CallNode) node;
-                        locationInfos.add(locationInfo);
-                        // erase last leaf (if present) since there is an intervening call range
-                        invalidateMerge(args);
-                        if (hasChildren(callNode)) {
-                            // a call node may include an initial leaf range for the call that must
-                            // be
-                            // embedded under the newly created location info so pass it as an
-                            // argument
-                            callNode.visitChildren(this, locationInfo, callNode, (Object) null);
-                        } else {
-                            // we need to embed a leaf node for the whole call range
-                            locationInfo = createEmbeddedParentLocationInfo(callNode, null, locationInfo, frameSize);
-                            locationInfos.add(locationInfo);
-                        }
-                    } else {
-                        locationInfo = tryMerge(locationInfo, args);
-                        if (locationInfo != null) {
-                            locationInfos.add(locationInfo);
-                        }
-                    }
-                }
-            }
-        }
-
-        /**
-         * Report whether a call node has any children.
-         *
-         * @param callNode the node to check
-         * @return true if it has any children otherwise false.
-         */
-        private boolean hasChildren(CallNode callNode) {
-            Object[] result = new Object[]{false};
-            callNode.visitChildren(new Visitor() {
-                @Override
-                public void apply(FrameNode node, Object... args) {
-                    args[0] = true;
-                }
-            }, result);
-            return (boolean) result[0];
-        }
-
-        /**
-         * Create a location info record for a leaf subrange.
-         *
-         * @param node is a simple FrameNode
-         * @return the newly created location info record
-         */
-        private NativeImageDebugLocationInfo createLeafLocationInfo(FrameNode node, NativeImageDebugLocationInfo callerInfo, int framesize) {
-            assert !(node instanceof CallNode);
-            NativeImageDebugLocationInfo locationInfo = new NativeImageDebugLocationInfo(node, callerInfo, framesize);
-            debugContext.log(DebugContext.DETAILED_LEVEL, "Create leaf Location Info : %s depth %d (%d, %d)", locationInfo.name(), locationInfo.depth(), locationInfo.addressLo(),
-                            locationInfo.addressHi() - 1);
-            return locationInfo;
-        }
-
-        /**
-         * Create a location info record for a subrange that encloses an inline call.
-         *
-         * @param callNode is the top level inlined call frame
-         * @return the newly created location info record
-         */
-        private NativeImageDebugLocationInfo createCallLocationInfo(CallNode callNode, NativeImageDebugLocationInfo callerInfo, int framesize) {
-            BytecodePosition callerPos = realCaller(callNode);
-            NativeImageDebugLocationInfo locationInfo = new NativeImageDebugLocationInfo(callerPos, callNode.getStartPos(), callNode.getEndPos() + 1, callerInfo, framesize);
-            debugContext.log(DebugContext.DETAILED_LEVEL, "Create call Location Info : %s depth %d (%d, %d)", locationInfo.name(), locationInfo.depth(), locationInfo.addressLo(),
-                            locationInfo.addressHi() - 1);
-            return locationInfo;
-        }
-
-        /**
-         * Create a location info record for the initial range associated with a parent call node
-         * whose position and start are defined by that call node and whose end is determined by the
-         * first child of the call node.
-         *
-         * @param parentToEmbed a parent call node which has already been processed to create the
-         *            caller location info
-         * @param firstChild the first child of the call node
-         * @param callerLocation the location info created to represent the range for the call
-         * @return a location info to be embedded as the first child range of the caller location.
-         */
-        private NativeImageDebugLocationInfo createEmbeddedParentLocationInfo(CallNode parentToEmbed, FrameNode firstChild, NativeImageDebugLocationInfo callerLocation, int framesize) {
-            BytecodePosition pos = parentToEmbed.frame;
-            int startPos = parentToEmbed.getStartPos();
-            int endPos = (firstChild != null ? firstChild.getStartPos() : parentToEmbed.getEndPos() + 1);
-            NativeImageDebugLocationInfo locationInfo = new NativeImageDebugLocationInfo(pos, startPos, endPos, callerLocation, framesize);
-            debugContext.log(DebugContext.DETAILED_LEVEL, "Embed leaf Location Info : %s depth %d (%d, %d)", locationInfo.name(), locationInfo.depth(), locationInfo.addressLo(),
-                            locationInfo.addressHi() - 1);
-            return locationInfo;
-        }
-
-        private NativeImageDebugLocationInfo createBadLeafLocationInfo(FrameNode node, NativeImageDebugLocationInfo callerLocation, int framesize) {
-            assert !(node instanceof CallNode) : "bad leaf location cannot be a call node!";
-            assert callerLocation == null : "should only see bad leaf at top level!";
-            BytecodePosition pos = node.frame;
-            BytecodePosition callerPos = pos.getCaller();
-            assert callerPos != null : "bad leaf must have a caller";
-            assert callerPos.getCaller() == null : "bad leaf caller must be root method";
-            int startPos = node.getStartPos();
-            int endPos = node.getEndPos() + 1;
-            NativeImageDebugLocationInfo locationInfo = new NativeImageDebugLocationInfo(callerPos, startPos, endPos, null, framesize);
-            debugContext.log(DebugContext.DETAILED_LEVEL, "Embed leaf Location Info : %s depth %d (%d, %d)", locationInfo.name(), locationInfo.depth(), locationInfo.addressLo(),
-                            locationInfo.addressHi() - 1);
-            return locationInfo;
-        }
-
-        private boolean isBadLeaf(FrameNode node, NativeImageDebugLocationInfo callerLocation) {
-            // Sometimes we see a leaf node marked as belonging to an inlined method
-            // that sits directly under the root method rather than under a call node.
-            // It needs replacing with a location info for the root method that covers
-            // the relevant code range.
-            if (callerLocation == null) {
-                BytecodePosition pos = node.frame;
-                BytecodePosition callerPos = pos.getCaller();
-                if (callerPos != null && !callerPos.getMethod().equals(pos.getMethod())) {
-                    if (callerPos.getCaller() == null) {
-                        return true;
-                    }
-                }
-            }
-            return false;
-        }
-
-        /**
-         * Test whether a bytecode position represents a bogus frame added by the compiler when a
-         * substitution or snippet call is injected.
-         *
-         * @param pos the position to be tested
-         * @return true if the frame is bogus otherwise false
-         */
-        private boolean skipPos(BytecodePosition pos) {
-            return (pos.getBCI() == -1 && pos instanceof NodeSourcePosition && ((NodeSourcePosition) pos).isSubstitution());
-        }
-
-        /**
-         * Skip caller nodes with bogus positions, as determined by
-         * {@link #skipPos(BytecodePosition)}, returning first caller node position that is not
-         * bogus.
-         *
-         * @param node the node whose callers are to be traversed
-         * @return the first non-bogus position in the caller chain.
-         */
-        private BytecodePosition realCaller(CallNode node) {
-            BytecodePosition pos = node.frame.getCaller();
-            while (skipPos(pos)) {
-                pos = pos.getCaller();
-            }
-            return pos;
-        }
-
-        /**
-         * Test whether the position associated with a child node should result in an entry in the
-         * inline tree. The test is for a call node with a bogus position as determined by
-         * {@link #skipPos(BytecodePosition)}.
-         *
-         * @param node A node associated with a child frame in the compilation result frame tree.
-         * @return True an entry should be included or false if it should be omitted.
-         */
-        private boolean skipNode(FrameNode node) {
-            return node instanceof CallNode && skipPos(node.frame);
-        }
-
-        /*
-         * Test whether the position associated with a call node frame should be embedded along with
-         * the locations generated for the node's children. This is needed because call frames
-         * include a valid source position that precedes the first child position.
-         *
-         * @param node A node associated with a frame in the compilation result frame tree.
-         *
-         * @return True if an inline frame should be included or false if it should be omitted.
-         */
-
-        /**
-         * Test whether the position associated with a call node frame should be embedded along with
-         * the locations generated for the node's children. This is needed because call frames may
-         * include a valid source position that precedes the first child position.
-         *
-         * @param parent The call node whose children are currently being visited
-         * @param firstChild The first child of that call node
-         * @return true if the node should be embedded otherwise false
-         */
-        private boolean embedWithChildren(CallNode parent, FrameNode firstChild) {
-            // we only need to insert a range for the caller if it fills a gap
-            // at the start of the caller range before the first child
-            if (parent.getStartPos() < firstChild.getStartPos()) {
-                return true;
-            }
-            return false;
-        }
-
-        /**
-         * Try merging a new location info for a leaf range into the location info for the last leaf
-         * range added at this level.
-         *
-         * @param newLeaf the new leaf location info
-         * @param args the visitor argument vector used to pass parameters from one child visit to
-         *            the next possibly including the last leaf
-         * @return the new location info if it could not be merged or null to indicate that it was
-         *         merged
-         */
-        private NativeImageDebugLocationInfo tryMerge(NativeImageDebugLocationInfo newLeaf, Object[] args) {
-            // last leaf node added at this level is 3rd element of arg vector
-            NativeImageDebugLocationInfo lastLeaf = (NativeImageDebugLocationInfo) args[LAST_LEAF_INFO];
-
-            if (lastLeaf != null) {
-                // try merging new leaf into last one
-                lastLeaf = lastLeaf.merge(newLeaf);
-                if (lastLeaf != null) {
-                    // null return indicates new leaf has been merged into last leaf
-                    return null;
-                }
-            }
-            // update last leaf and return new leaf for addition to local info list
-            args[LAST_LEAF_INFO] = newLeaf;
-            return newLeaf;
-        }
-
-        /**
-         * Set the last leaf node at the current level to the supplied leaf node.
-         *
-         * @param lastLeaf the last leaf node created at this level
-         * @param args the visitor argument vector used to pass parameters from one child visit to
-         *            the next
-         */
-        private void initMerge(NativeImageDebugLocationInfo lastLeaf, Object[] args) {
-            args[LAST_LEAF_INFO] = lastLeaf;
-        }
-
-        /**
-         * Clear the last leaf node at the current level from the visitor arguments by setting the
-         * arg vector entry to null.
-         *
-         * @param args the visitor argument vector used to pass parameters from one child visit to
-         *            the next
-         */
-        private void invalidateMerge(Object[] args) {
-            args[LAST_LEAF_INFO] = null;
-        }
-
-        @Override
-        public int getFrameSize() {
-            return compilation.getTotalFrameSize();
-        }
-
-        @Override
-        public List<DebugFrameSizeChange> getFrameSizeChanges() {
-            List<DebugFrameSizeChange> frameSizeChanges = new LinkedList<>();
-            for (CompilationResult.CodeMark mark : compilation.getMarks()) {
-                /* We only need to observe stack increment or decrement points. */
-                if (mark.id.equals(SubstrateMarkId.PROLOGUE_DECD_RSP)) {
-                    NativeImageDebugFrameSizeChange sizeChange = new NativeImageDebugFrameSizeChange(mark.pcOffset, EXTEND);
-                    frameSizeChanges.add(sizeChange);
-                    // } else if (mark.id.equals("PROLOGUE_END")) {
-                    // can ignore these
-                    // } else if (mark.id.equals("EPILOGUE_START")) {
-                    // can ignore these
-                } else if (mark.id.equals(SubstrateMarkId.EPILOGUE_INCD_RSP)) {
-                    NativeImageDebugFrameSizeChange sizeChange = new NativeImageDebugFrameSizeChange(mark.pcOffset, CONTRACT);
-                    frameSizeChanges.add(sizeChange);
-                } else if (mark.id.equals(SubstrateMarkId.EPILOGUE_END) && mark.pcOffset < compilation.getTargetCodeSize()) {
-                    /* There is code after this return point so notify a stack extend again. */
-                    NativeImageDebugFrameSizeChange sizeChange = new NativeImageDebugFrameSizeChange(mark.pcOffset, EXTEND);
-                    frameSizeChanges.add(sizeChange);
-                }
-            }
-            return frameSizeChanges;
-        }
-    }
-
-    /**
-     * Implementation of the DebugLocationInfo API interface that allows line number and local var
-     * info to be passed to an ObjectFile when generation of debug info is enabled.
-     */
-    private class NativeImageDebugLocationInfo extends NativeImageDebugBaseMethodInfo implements DebugLocationInfo {
-        private final int bci;
-        private long lo;
-        private long hi;
-        private DebugLocationInfo callersLocationInfo;
-        private List<DebugLocalValueInfo> localInfoList;
-        private boolean isLeaf;
-
-        NativeImageDebugLocationInfo(FrameNode frameNode, NativeImageDebugLocationInfo callersLocationInfo, int framesize) {
-            this(frameNode.frame, frameNode.getStartPos(), frameNode.getEndPos() + 1, callersLocationInfo, framesize);
-        }
-
-        NativeImageDebugLocationInfo(BytecodePosition bcpos, long lo, long hi, NativeImageDebugLocationInfo callersLocationInfo, int framesize) {
-            super(bcpos.getMethod());
-            this.bci = bcpos.getBCI();
-            this.lo = lo;
-            this.hi = hi;
-            this.callersLocationInfo = callersLocationInfo;
-            this.localInfoList = initLocalInfoList(bcpos, framesize);
-            // assume this is a leaf until we find out otherwise
-            this.isLeaf = true;
-            // tag the caller as a non-leaf
-            if (callersLocationInfo != null) {
-                callersLocationInfo.isLeaf = false;
-            }
-        }
-
-        // special constructor for synthetic location info added at start of method
-        NativeImageDebugLocationInfo(ResolvedJavaMethod method, long hi, ParamLocationProducer locProducer) {
-            super(method);
-            // bci is always 0 and lo is always 0.
-            this.bci = 0;
-            this.lo = 0;
-            this.hi = hi;
-            // this is always going to be a top-level leaf range.
-            this.callersLocationInfo = null;
-            // location info is synthesized off the method signature
-            this.localInfoList = initSyntheticInfoList(locProducer);
-            // assume this is a leaf until we find out otherwise
-            this.isLeaf = true;
-        }
-
-        // special constructor for synthetic location info which splits off the initial segment
-        // of the first range to accommodate a stack access prior to the stack extend
-        NativeImageDebugLocationInfo(NativeImageDebugLocationInfo toSplit, int stackDecrement, int frameSize) {
-            super(toSplit.method);
-            this.lo = stackDecrement;
-            this.hi = toSplit.hi;
-            toSplit.hi = this.lo;
-            this.bci = toSplit.bci;
-            this.callersLocationInfo = toSplit.callersLocationInfo;
-            this.isLeaf = toSplit.isLeaf;
-            toSplit.isLeaf = true;
-            this.localInfoList = new ArrayList<>(toSplit.localInfoList.size());
-            for (DebugLocalValueInfo localInfo : toSplit.localInfoList) {
-                if (localInfo.localKind() == DebugLocalValueInfo.LocalKind.STACKSLOT) {
-                    // need to redefine the value for this param using a stack slot value
-                    // that allows for the stack being extended by framesize. however we
-                    // also need to remove any adjustment that was made to allow for the
-                    // difference between the caller SP and the pre-extend callee SP
-                    // because of a stacked return address.
-                    int adjustment = frameSize - PRE_EXTEND_FRAME_SIZE;
-                    NativeImageDebugLocalValue value = NativeImageDebugStackValue.create(localInfo, adjustment);
-                    NativeImageDebugLocalValueInfo nativeLocalInfo = (NativeImageDebugLocalValueInfo) localInfo;
-                    NativeImageDebugLocalValueInfo newLocalinfo = new NativeImageDebugLocalValueInfo(nativeLocalInfo.name,
-                                    value,
-                                    nativeLocalInfo.kind,
-                                    nativeLocalInfo.type,
-                                    nativeLocalInfo.slot,
-                                    nativeLocalInfo.line);
-                    localInfoList.add(newLocalinfo);
-                } else {
-                    localInfoList.add(localInfo);
-                }
-            }
-        }
-
-        private List<DebugLocalValueInfo> initLocalInfoList(BytecodePosition bcpos, int framesize) {
-            if (!(bcpos instanceof BytecodeFrame)) {
-                return null;
-            }
-
-            BytecodeFrame frame = (BytecodeFrame) bcpos;
-            if (frame.numLocals == 0) {
-                return null;
-            }
-            // deal with any inconsistencies in the layout of the frame locals
-            // NativeImageDebugFrameInfo debugFrameInfo = new NativeImageDebugFrameInfo(frame);
-
-            LineNumberTable lineNumberTable = frame.getMethod().getLineNumberTable();
-            Local[] localsBySlot = getLocalsBySlot();
-            if (localsBySlot == null) {
-                return Collections.emptyList();
-            }
-            int count = Integer.min(localsBySlot.length, frame.numLocals);
-            ArrayList<DebugLocalValueInfo> localInfos = new ArrayList<>(count);
-            for (int i = 0; i < count; i++) {
-                Local l = localsBySlot[i];
-                if (l != null) {
-                    // we have a local with a known name, type and slot
-                    String name = l.getName();
-                    ResolvedJavaType ownerType = method.getDeclaringClass();
-                    ResolvedJavaType type = l.getType().resolve(ownerType);
-                    JavaKind kind = type.getJavaKind();
-                    int slot = l.getSlot();
-                    debugContext.log(DebugContext.DETAILED_LEVEL, "locals[%d] %s type %s slot %d", i, name, type.getName(), slot);
-                    JavaValue value = (slot < frame.numLocals ? frame.getLocalValue(slot) : Value.ILLEGAL);
-                    JavaKind storageKind = (slot < frame.numLocals ? frame.getLocalValueKind(slot) : JavaKind.Illegal);
-                    debugContext.log(DebugContext.DETAILED_LEVEL, "  =>  %s kind %s", value, storageKind);
-                    int bciStart = l.getStartBCI();
-                    int firstLine = (lineNumberTable != null ? lineNumberTable.getLineNumber(bciStart) : -1);
-                    // only add the local if the kinds match
-                    if ((storageKind == kind) ||
-                                    isIntegralKindPromotion(storageKind, kind) ||
-                                    (isForeignWordType(type, ownerType) && kind == JavaKind.Object && storageKind == JavaKind.Long)) {
-                        localInfos.add(new NativeImageDebugLocalValueInfo(name, value, framesize, storageKind, type, slot, firstLine));
-                    } else if (storageKind != JavaKind.Illegal) {
-                        debugContext.log(DebugContext.DETAILED_LEVEL, "  value kind incompatible with var kind %s!", type.getJavaKind());
-                    }
-                }
-            }
-            return localInfos;
-        }
-
-        private List<DebugLocalValueInfo> initSyntheticInfoList(ParamLocationProducer locProducer) {
-            Signature signature = method.getSignature();
-            int parameterCount = signature.getParameterCount(false);
-            ArrayList<DebugLocalValueInfo> localInfos = new ArrayList<>();
-            LocalVariableTable table = method.getLocalVariableTable();
-            LineNumberTable lineNumberTable = method.getLineNumberTable();
-            int firstLine = (lineNumberTable != null ? lineNumberTable.getLineNumber(0) : -1);
-            int slot = 0;
-            int localIdx = 0;
-            ResolvedJavaType ownerType = method.getDeclaringClass();
-            if (!method.isStatic()) {
-                String name = "this";
-                JavaKind kind = ownerType.getJavaKind();
-                JavaKind storageKind = isForeignWordType(ownerType, ownerType) ? JavaKind.Long : kind;
-                assert kind == JavaKind.Object : "must be an object";
-                NativeImageDebugLocalValue value = locProducer.thisLocation();
-                debugContext.log(DebugContext.DETAILED_LEVEL, "locals[%d] %s type %s slot %d", localIdx, name, ownerType.getName(), slot);
-                debugContext.log(DebugContext.DETAILED_LEVEL, "  =>  %s kind %s", value, storageKind);
-                localInfos.add(new NativeImageDebugLocalValueInfo(name, value, storageKind, ownerType, slot, firstLine));
-                slot += kind.getSlotCount();
-                localIdx++;
-            }
-            for (int i = 0; i < parameterCount; i++) {
-                Local local = (table == null ? null : table.getLocal(slot, 0));
-                String name = (local != null ? local.getName() : "__" + i);
-                ResolvedJavaType paramType = (ResolvedJavaType) signature.getParameterType(i, ownerType);
-                JavaKind kind = paramType.getJavaKind();
-                JavaKind storageKind = isForeignWordType(paramType, ownerType) ? JavaKind.Long : kind;
-                NativeImageDebugLocalValue value = locProducer.paramLocation(i);
-                debugContext.log(DebugContext.DETAILED_LEVEL, "locals[%d] %s type %s slot %d", localIdx, name, ownerType.getName(), slot);
-                debugContext.log(DebugContext.DETAILED_LEVEL, "  =>  %s kind %s", value, storageKind);
-                localInfos.add(new NativeImageDebugLocalValueInfo(name, value, storageKind, paramType, slot, firstLine));
-                slot += kind.getSlotCount();
-                localIdx++;
-            }
-            return localInfos;
-        }
-
-        private Local[] getLocalsBySlot() {
-            LocalVariableTable lvt = method.getLocalVariableTable();
-            Local[] nonEmptySortedLocals = null;
-            if (lvt != null) {
-                Local[] locals = lvt.getLocalsAt(bci);
-                if (locals != null && locals.length > 0) {
-                    nonEmptySortedLocals = Arrays.copyOf(locals, locals.length);
-                    Arrays.sort(nonEmptySortedLocals, (Local l1, Local l2) -> l1.getSlot() - l2.getSlot());
-                }
-            }
-            return nonEmptySortedLocals;
-        }
-
-        @Override
-        public long addressLo() {
-            return lo;
-        }
-
-        @Override
-        public long addressHi() {
-            return hi;
-        }
-
-        @Override
-        public int line() {
-            LineNumberTable lineNumberTable = method.getLineNumberTable();
-            if (lineNumberTable != null && bci >= 0) {
-                return lineNumberTable.getLineNumber(bci);
-            }
-            return -1;
-        }
-
-        @Override
-        public DebugLocationInfo getCaller() {
-            return callersLocationInfo;
-        }
-
-        @Override
-        public DebugLocalValueInfo[] getLocalValueInfo() {
-            if (localInfoList != null) {
-                return localInfoList.toArray(new DebugLocalValueInfo[localInfoList.size()]);
-            } else {
-                return EMPTY_LOCAL_VALUE_INFOS;
-            }
-        }
-
-        @Override
-        public boolean isLeaf() {
-            return isLeaf;
-        }
-
-        public int depth() {
-            int depth = 1;
-            DebugLocationInfo caller = getCaller();
-            while (caller != null) {
-                depth++;
-                caller = caller.getCaller();
-            }
-            return depth;
-        }
-
-        private int localsSize() {
-            if (localInfoList != null) {
-                return localInfoList.size();
-            } else {
-                return 0;
-            }
-        }
-
-        /**
-         * Merge the supplied leaf location info into this leaf location info if they have
-         * contiguous ranges, the same method and line number and the same live local variables with
-         * the same values.
-         *
-         * @param that a leaf location info to be merged into this one
-         * @return this leaf location info if the merge was performed otherwise null
-         */
-        NativeImageDebugLocationInfo merge(NativeImageDebugLocationInfo that) {
-            assert callersLocationInfo == that.callersLocationInfo;
-            assert isLeaf == that.isLeaf;
-            assert depth() == that.depth() : "should only compare sibling ranges";
-            assert this.hi <= that.lo : "later nodes should not overlap earlier ones";
-            if (this.hi != that.lo) {
-                return null;
-            }
-            if (!method.equals(that.method)) {
-                return null;
-            }
-            if (line() != that.line()) {
-                return null;
-            }
-            int size = localsSize();
-            if (size != that.localsSize()) {
-                return null;
-            }
-            for (int i = 0; i < size; i++) {
-                NativeImageDebugLocalValueInfo thisLocal = (NativeImageDebugLocalValueInfo) localInfoList.get(i);
-                NativeImageDebugLocalValueInfo thatLocal = (NativeImageDebugLocalValueInfo) that.localInfoList.get(i);
-                if (!thisLocal.equals(thatLocal)) {
-                    return null;
-                }
-            }
-            debugContext.log(DebugContext.DETAILED_LEVEL, "Merge  leaf Location Info : %s depth %d (%d, %d) into (%d, %d)", that.name(), that.depth(), that.lo, that.hi - 1, this.lo, this.hi - 1);
-            // merging just requires updating lo and hi range as everything else is equal
-            this.hi = that.hi;
-
-            return this;
-        }
-
-        public NativeImageDebugLocationInfo split(int stackDecrement, int frameSize) {
-            // this should be for an initial range extending beyond the stack decrement
-            assert lo == 0 && lo < stackDecrement && stackDecrement < hi : "invalid split request";
-            return new NativeImageDebugLocationInfo(this, stackDecrement, frameSize);
-        }
-
-    }
-
-    private static final DebugLocalValueInfo[] EMPTY_LOCAL_VALUE_INFOS = new DebugLocalValueInfo[0];
-
-    static final Register[] AARCH64_GPREG = {
-                    AArch64.r0,
-                    AArch64.r1,
-                    AArch64.r2,
-                    AArch64.r3,
-                    AArch64.r4,
-                    AArch64.r5,
-                    AArch64.r6,
-                    AArch64.r7
-    };
-    static final Register[] AARCH64_FREG = {
-                    AArch64.v0,
-                    AArch64.v1,
-                    AArch64.v2,
-                    AArch64.v3,
-                    AArch64.v4,
-                    AArch64.v5,
-                    AArch64.v6,
-                    AArch64.v7
-    };
-    static final Register[] AMD64_GPREG_LINUX = {
-                    AMD64.rdi,
-                    AMD64.rsi,
-                    AMD64.rdx,
-                    AMD64.rcx,
-                    AMD64.r8,
-                    AMD64.r9
-    };
-    static final Register[] AMD64_FREG_LINUX = {
-                    AMD64.xmm0,
-                    AMD64.xmm1,
-                    AMD64.xmm2,
-                    AMD64.xmm3,
-                    AMD64.xmm4,
-                    AMD64.xmm5,
-                    AMD64.xmm6,
-                    AMD64.xmm7
-    };
-    static final Register[] AMD64_GPREG_WINDOWS = {
-                    AMD64.rdx,
-                    AMD64.r8,
-                    AMD64.r9,
-                    AMD64.rdi,
-                    AMD64.rsi,
-                    AMD64.rcx
-    };
-    static final Register[] AMD64_FREG_WINDOWS = {
-                    AMD64.xmm0,
-                    AMD64.xmm1,
-                    AMD64.xmm2,
-                    AMD64.xmm3
-    };
-
-    /**
-     * Size in bytes of the frame at call entry before any stack extend. Essentially this accounts
-     * for any automatically pushed return address whose presence depends upon the architecture.
-     */
-    static final int PRE_EXTEND_FRAME_SIZE = ConfigurationValues.getTarget().arch.getReturnAddressSize();
-
-    class ParamLocationProducer {
-        private final HostedMethod hostedMethod;
-        private final CallingConvention callingConvention;
-        private boolean usesStack;
-
-        ParamLocationProducer(HostedMethod method) {
-            this.hostedMethod = method;
-            this.callingConvention = getCallingConvention(hostedMethod);
-            // assume no stack slots until we find out otherwise
-            this.usesStack = false;
-        }
-
-        NativeImageDebugLocalValue thisLocation() {
-            assert !hostedMethod.isStatic();
-            return unpack(callingConvention.getArgument(0));
-        }
-
-        NativeImageDebugLocalValue paramLocation(int paramIdx) {
-            assert paramIdx < hostedMethod.getSignature().getParameterCount(false);
-            int idx = paramIdx;
-            if (!hostedMethod.isStatic()) {
-                idx++;
-            }
-            return unpack(callingConvention.getArgument(idx));
-        }
-
-        private NativeImageDebugLocalValue unpack(AllocatableValue value) {
-            if (value instanceof RegisterValue) {
-                RegisterValue registerValue = (RegisterValue) value;
-                return NativeImageDebugRegisterValue.create(registerValue);
-            } else {
-                // call argument must be a stack slot if it is not a register
-                StackSlot stackSlot = (StackSlot) value;
-                this.usesStack = true;
-                // the calling convention provides offsets from the SP relative to the current
-                // frame size. At the point of call the frame may or may not include a return
-                // address depending on the architecture.
-                return NativeImageDebugStackValue.create(stackSlot, PRE_EXTEND_FRAME_SIZE);
-            }
-        }
-
-        public boolean usesStack() {
-            return usesStack;
-        }
-    }
-
-    public class NativeImageDebugLocalInfo implements DebugLocalInfo {
-        protected final String name;
-        protected ResolvedJavaType type;
-        protected final JavaKind kind;
-        protected int slot;
-        protected int line;
-
-        NativeImageDebugLocalInfo(String name, JavaKind kind, ResolvedJavaType resolvedType, int slot, int line) {
-            this.name = name;
-            this.kind = kind;
-            this.slot = slot;
-            this.line = line;
-            // if we don't have a type default it for the JavaKind
-            // it may still end up null when kind is Undefined.
-            this.type = (resolvedType != null ? resolvedType : hostedTypeForKind(kind));
-        }
-
-        @Override
-        public ResolvedJavaType valueType() {
-            if (type != null && type instanceof HostedType) {
-                return getOriginal((HostedType) type);
-            }
-            return type;
-        }
-
-        @Override
-        public String name() {
-            return name;
-        }
-
-        @Override
-        public String typeName() {
-            ResolvedJavaType valueType = valueType();
-            return (valueType == null ? "" : valueType().toJavaName());
-        }
-
-        @Override
-        public int slot() {
-            return slot;
-        }
-
-        @Override
-        public int slotCount() {
-            return kind.getSlotCount();
-        }
-
-        @Override
-        public JavaKind javaKind() {
-            return kind;
-        }
-
-        @Override
-        public int line() {
-            return line;
-        }
-    }
-
-    public class NativeImageDebugLocalValueInfo extends NativeImageDebugLocalInfo implements DebugLocalValueInfo {
-        private final NativeImageDebugLocalValue value;
-        private LocalKind localKind;
-
-        NativeImageDebugLocalValueInfo(String name, JavaValue value, int framesize, JavaKind kind, ResolvedJavaType resolvedType, int slot, int line) {
-            super(name, kind, resolvedType, slot, line);
-            if (value instanceof RegisterValue) {
-                this.localKind = LocalKind.REGISTER;
-                this.value = NativeImageDebugRegisterValue.create((RegisterValue) value);
-            } else if (value instanceof StackSlot) {
-                this.localKind = LocalKind.STACKSLOT;
-                this.value = NativeImageDebugStackValue.create((StackSlot) value, framesize);
-            } else if (value instanceof JavaConstant) {
-                JavaConstant constant = (JavaConstant) value;
-                if (constant instanceof PrimitiveConstant || constant.isNull()) {
-                    this.localKind = LocalKind.CONSTANT;
-                    this.value = NativeImageDebugConstantValue.create(constant);
-                } else {
-                    long heapOffset = objectOffset(constant);
-                    if (heapOffset >= 0) {
-                        this.localKind = LocalKind.CONSTANT;
-                        this.value = new NativeImageDebugConstantValue(constant, heapOffset);
-                    } else {
-                        this.localKind = LocalKind.UNDEFINED;
-                        this.value = null;
-                    }
-                }
-            } else {
-                this.localKind = LocalKind.UNDEFINED;
-                this.value = null;
-            }
-        }
-
-        NativeImageDebugLocalValueInfo(String name, NativeImageDebugLocalValue value, JavaKind kind, ResolvedJavaType type, int slot, int line) {
-            super(name, kind, type, slot, line);
-            if (value == null) {
-                this.localKind = LocalKind.UNDEFINED;
-            } else if (value instanceof NativeImageDebugRegisterValue) {
-                this.localKind = LocalKind.REGISTER;
-            } else if (value instanceof NativeImageDebugStackValue) {
-                this.localKind = LocalKind.STACKSLOT;
-            } else if (value instanceof NativeImageDebugConstantValue) {
-                this.localKind = LocalKind.CONSTANT;
-            }
-            this.value = value;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (!(o instanceof NativeImageDebugLocalValueInfo)) {
-                return false;
-            }
-            NativeImageDebugLocalValueInfo that = (NativeImageDebugLocalValueInfo) o;
-            // values need to have the same name
-            if (!name().equals(that.name())) {
-                return false;
-            }
-            // values need to be for the same line
-            if (line != that.line) {
-                return false;
-            }
-            // location kinds must match
-            if (localKind != that.localKind) {
-                return false;
-            }
-            // locations must match
-            switch (localKind) {
-                case REGISTER:
-                case STACKSLOT:
-                case CONSTANT:
-                    return value.equals(that.value);
-                default:
-                    return true;
-            }
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(name(), value) * 31 + line;
-        }
-
-        @Override
-        public String toString() {
-            switch (localKind) {
-                case REGISTER:
-                    return "reg[" + regIndex() + "]";
-                case STACKSLOT:
-                    return "stack[" + stackSlot() + "]";
-                case CONSTANT:
-                    return "constant[" + (constantValue() != null ? constantValue().toValueString() : "null") + "]";
-                default:
-                    return "-";
-            }
-        }
-
-        @Override
-        public LocalKind localKind() {
-            return localKind;
-        }
-
-        @Override
-        public int regIndex() {
-            return ((NativeImageDebugRegisterValue) value).getNumber();
-        }
-
-        @Override
-        public int stackSlot() {
-            return ((NativeImageDebugStackValue) value).getOffset();
-        }
-
-        @Override
-        public long heapOffset() {
-            return ((NativeImageDebugConstantValue) value).getHeapOffset();
-        }
-
-        @Override
-        public JavaConstant constantValue() {
-            return ((NativeImageDebugConstantValue) value).getConstant();
-        }
-    }
-
-    public abstract static class NativeImageDebugLocalValue {
-    }
-
-    public static final class NativeImageDebugRegisterValue extends NativeImageDebugLocalValue {
-        private static EconomicMap<Integer, NativeImageDebugRegisterValue> registerValues = EconomicMap.create();
-        private int number;
-        private String name;
-
-        private NativeImageDebugRegisterValue(int number, String name) {
-            this.number = number;
-            this.name = "reg:" + name;
-        }
-
-        static NativeImageDebugRegisterValue create(RegisterValue value) {
-            int number = value.getRegister().number;
-            String name = value.getRegister().name;
-            return memoizedCreate(number, name);
-        }
-
-        static NativeImageDebugRegisterValue memoizedCreate(int number, String name) {
-            NativeImageDebugRegisterValue reg = registerValues.get(number);
-            if (reg == null) {
-                reg = new NativeImageDebugRegisterValue(number, name);
-                registerValues.put(number, reg);
-            }
-            return reg;
-        }
-
-        public int getNumber() {
-            return number;
-        }
-
-        @Override
-        public String toString() {
-            return name;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (!(o instanceof NativeImageDebugRegisterValue)) {
-                return false;
-            }
-            NativeImageDebugRegisterValue that = (NativeImageDebugRegisterValue) o;
-            return number == that.number;
-        }
-
-        @Override
-        public int hashCode() {
-            return number * 31;
-        }
-    }
-
-    public static final class NativeImageDebugStackValue extends NativeImageDebugLocalValue {
-        private static EconomicMap<Integer, NativeImageDebugStackValue> stackValues = EconomicMap.create();
-        private int offset;
-        private String name;
-
-        private NativeImageDebugStackValue(int offset) {
-            this.offset = offset;
-            this.name = "stack:" + offset;
-        }
-
-        static NativeImageDebugStackValue create(StackSlot value, int framesize) {
-            // Work around a problem on AArch64 where StackSlot asserts if it is
-            // passed a zero frame size, even though this is what is expected
-            // for stack slot offsets provided at the point of entry (because,
-            // unlike x86, lr has not been pushed).
-            int offset = (framesize == 0 ? value.getRawOffset() : value.getOffset(framesize));
-            return memoizedCreate(offset);
-        }
-
-        static NativeImageDebugStackValue create(DebugLocalValueInfo previous, int adjustment) {
-            assert previous.localKind() == DebugLocalValueInfo.LocalKind.STACKSLOT;
-            return memoizedCreate(previous.stackSlot() + adjustment);
-        }
-
-        private static NativeImageDebugStackValue memoizedCreate(int offset) {
-            NativeImageDebugStackValue value = stackValues.get(offset);
-            if (value == null) {
-                value = new NativeImageDebugStackValue(offset);
-                stackValues.put(offset, value);
-            }
-            return value;
-        }
-
-        public int getOffset() {
-            return offset;
-        }
-
-        @Override
-        public String toString() {
-            return name;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (!(o instanceof NativeImageDebugStackValue)) {
-                return false;
-            }
-            NativeImageDebugStackValue that = (NativeImageDebugStackValue) o;
-            return offset == that.offset;
-        }
-
-        @Override
-        public int hashCode() {
-            return offset * 31;
-        }
-    }
-
-    public static final class NativeImageDebugConstantValue extends NativeImageDebugLocalValue {
-        private static EconomicMap<JavaConstant, NativeImageDebugConstantValue> constantValues = EconomicMap.create();
-        private JavaConstant value;
-        private long heapoffset;
-
-        private NativeImageDebugConstantValue(JavaConstant value, long heapoffset) {
-            this.value = value;
-            this.heapoffset = heapoffset;
-        }
-
-        static NativeImageDebugConstantValue create(JavaConstant value) {
-            return create(value, -1);
-        }
-
-        static NativeImageDebugConstantValue create(JavaConstant value, long heapoffset) {
-            NativeImageDebugConstantValue c = constantValues.get(value);
-            if (c == null) {
-                c = new NativeImageDebugConstantValue(value, heapoffset);
-                constantValues.put(value, c);
-            }
-            assert c.heapoffset == heapoffset;
-            return c;
-        }
-
-        public JavaConstant getConstant() {
-            return value;
-        }
-
-        public long getHeapOffset() {
-            return heapoffset;
-        }
-
-        @Override
-        public String toString() {
-            return "constant:" + value.toString();
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (!(o instanceof NativeImageDebugConstantValue)) {
-                return false;
-            }
-            NativeImageDebugConstantValue that = (NativeImageDebugConstantValue) o;
-            return heapoffset == that.heapoffset && value.equals(that.value);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(value) * 31 + (int) heapoffset;
+    private long getClassOffset(HostedType type) {
+        /*
+         * Only query the heap for reachable types. These are guaranteed to have been seen by
+         * the analysis and to exist in the shadow heap.
+         */
+        if (type.getWrapped().isReachable()) {
+            NativeImageHeap.ObjectInfo objectInfo = heap.getObjectInfo(type.getHub());
+            if (objectInfo != null) {
+                return objectInfo.getOffset();
+            }
         }
+        return -1;
     }
 
     /**
-     * Implementation of the DebugFrameSizeChange API interface that allows stack frame size change
-     * info to be passed to an ObjectFile when generation of debug info is enabled.
+     * Return the offset into the initial heap at which the object identified by constant is located
+     * or -1 if the object is not present in the initial heap.
+     *
+     * @param constant must have JavaKind Object and must be non-null.
+     * @return the offset into the initial heap at which the object identified by constant is
+     *         located or -1 if the object is not present in the initial heap.
      */
-    private class NativeImageDebugFrameSizeChange implements DebugFrameSizeChange {
-        private int offset;
-        private Type type;
-
-        NativeImageDebugFrameSizeChange(int offset, Type type) {
-            this.offset = offset;
-            this.type = type;
-        }
-
-        @Override
-        public int getOffset() {
-            return offset;
-        }
-
-        @Override
-        public Type getType() {
-            return type;
-        }
-    }
-
-    private class NativeImageDebugDataInfo implements DebugDataInfo {
-        private final NativeImageHeap.ObjectInfo objectInfo;
-
-        @SuppressWarnings("try")
-        @Override
-        public void debugContext(Consumer<DebugContext> action) {
-            try (DebugContext.Scope s = debugContext.scope("DebugDataInfo")) {
-                action.accept(debugContext);
-            } catch (Throwable e) {
-                throw debugContext.handle(e);
-            }
-        }
-
-        /* Accessors. */
-
-        NativeImageDebugDataInfo(ObjectInfo objectInfo) {
-            this.objectInfo = objectInfo;
-        }
-
-        @Override
-        public String getProvenance() {
-            return objectInfo.toString();
-        }
-
-        @Override
-        public String getTypeName() {
-            return objectInfo.getClazz().toJavaName();
-        }
-
-        @Override
-        public String getPartition() {
-            ImageHeapPartition partition = objectInfo.getPartition();
-            return partition.getName() + "{" + partition.getSize() + "}@" + partition.getStartOffset();
-        }
-
-        @Override
-        public long getOffset() {
+    public long objectOffset(JavaConstant constant) {
+        assert constant.getJavaKind() == JavaKind.Object && !constant.isNull() : "invalid constant for object offset lookup";
+        NativeImageHeap.ObjectInfo objectInfo = heap.getConstantInfo(constant);
+        if (objectInfo != null) {
             return objectInfo.getOffset();
         }
-
-        @Override
-        public long getSize() {
-            return objectInfo.getSize();
-        }
-    }
-
-    private boolean acceptObjectInfo(ObjectInfo objectInfo) {
-        /* This condition rejects filler partition objects. */
-        return (objectInfo.getPartition().getStartOffset() > 0);
-    }
-
-    private DebugDataInfo createDebugDataInfo(ObjectInfo objectInfo) {
-        return new NativeImageDebugDataInfo(objectInfo);
-    }
-
-    @Override
-    public void recordActivity() {
-        DeadlockWatchdog.singleton().recordActivity();
+        return -1;
     }
 }

From e030e39401f6f41a54a43ef57a7514e5e800db74 Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Mon, 2 Dec 2024 21:17:59 +0100
Subject: [PATCH 09/34] Fix concurrency problems and some cleanup

---
 .../objectfile/debugentry/ClassEntry.java     |  51 +++---
 .../objectfile/debugentry/LocalEntry.java     |  58 +------
 .../debugentry/LocalValueEntry.java           |   2 +-
 .../objectfile/debugentry/MethodEntry.java    |  94 ++++-------
 .../objectfile/debugentry/range/Range.java    |   2 +-
 .../elf/dwarf/DwarfInfoSectionImpl.java       |  20 +--
 .../elf/dwarf/DwarfLocSectionImpl.java        |   2 +-
 .../core/debug/SharedDebugInfoProvider.java   | 151 ++++++++++--------
 8 files changed, 158 insertions(+), 222 deletions(-)

diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
index d23f3afdf961..ade8b9f76843 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
@@ -29,7 +29,7 @@
 import com.oracle.objectfile.debugentry.range.Range;
 
 import java.util.ArrayList;
-import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 
 /**
@@ -76,38 +76,43 @@ public ClassEntry(String typeName, int size, long classOffset, long typeSignatur
         this.superClass = superClass;
         this.fileEntry = fileEntry;
         this.loader = loader;
-
         this.methods = new ArrayList<>();
         this.compiledMethods = new ArrayList<>();
         this.files = new ArrayList<>();
         this.dirs = new ArrayList<>();
-
-        // add own file to file/dir entries
-        if (fileEntry != null && !fileEntry.fileName().isEmpty()) {
-            files.add(fileEntry);
-            DirEntry dirEntry = fileEntry.dirEntry();
-            if (dirEntry != null && !dirEntry.getPathString().isEmpty()) {
-                dirs.add(dirEntry);
-            }
-        }
     }
 
-    @Override
-    public void addField(FieldEntry field) {
-        addFile(field.getFileEntry());
-        super.addField(field);
+    public void collectFilesAndDirs() {
+        HashSet<FileEntry> fileSet = new HashSet<>();
+
+        // add containing file
+        fileSet.add(fileEntry);
+
+        // add all files of declared methods
+        fileSet.addAll(methods.parallelStream().map(MethodEntry::getFileEntry).toList());
+
+        // add all files required for compilations
+        // no need to add the primary range as this is the same as the corresponding method declaration file
+        fileSet.addAll(compiledMethods.parallelStream()
+                .flatMap(CompiledMethodEntry::topDownRangeStream)
+                .map(Range::getFileEntry)
+                .toList()
+        );
+
+        // add all files of fields
+        fileSet.addAll(getFields().parallelStream().map(FieldEntry::getFileEntry).toList());
+
+        // fill file list from set
+        fileSet.forEach(this::addFile);
     }
 
     public void addMethod(MethodEntry methodEntry) {
         if (!methods.contains(methodEntry)) {
-            addFile(methodEntry.getFileEntry());
             methods.add(methodEntry);
         }
     }
 
     public void addCompiledMethod(CompiledMethodEntry compiledMethodEntry) {
-        addFile(compiledMethodEntry.primary().getFileEntry());
-        compiledMethodEntry.topDownRangeStream().forEach(subRange -> addFile(subRange.getFileEntry()));
         compiledMethods.add(compiledMethodEntry);
     }
 
@@ -163,7 +168,7 @@ public int getFileIdx() {
     }
 
     public int getFileIdx(FileEntry file) {
-        if (file == null || files.isEmpty()) {
+        if (file == null || files.isEmpty() || !files.contains(file)) {
             return 0;
         }
         return files.indexOf(file) + 1;
@@ -198,7 +203,7 @@ public String getLoaderId() {
      * @return a list of all compiled method entries for this class.
      */
     public List<CompiledMethodEntry> compiledMethods() {
-        return Collections.unmodifiableList(compiledMethods);
+        return List.copyOf(compiledMethods);
     }
 
     public boolean hasCompiledMethods() {
@@ -210,7 +215,7 @@ public ClassEntry getSuperClass() {
     }
 
     public List<MethodEntry> getMethods() {
-        return Collections.unmodifiableList(methods);
+        return List.copyOf(methods);
     }
 
     /**
@@ -243,7 +248,7 @@ public long hipc() {
      * @return a stream of all referenced files
      */
     public List<FileEntry> getFiles() {
-        return Collections.unmodifiableList(files);
+        return List.copyOf(files);
     }
 
     /**
@@ -253,6 +258,6 @@ public List<FileEntry> getFiles() {
      * @return a stream of all referenced directories
      */
     public List<DirEntry> getDirs() {
-        return Collections.unmodifiableList(dirs);
+        return List.copyOf(dirs);
     }
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalEntry.java
index 341d04f21ed7..c698d3c0d1bf 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalEntry.java
@@ -2,63 +2,7 @@
 
 import jdk.vm.ci.meta.JavaKind;
 
-import java.util.Objects;
-
-public final class LocalEntry {
-    private final String name;
-    private final TypeEntry type;
-    private final JavaKind kind;
-    private final int slot;
-    private int line;
-
-    public LocalEntry(String name, TypeEntry type, JavaKind kind, int slot, int line) {
-        this.name = name;
-        this.type = type;
-        this.kind = kind;
-        this.slot = slot;
-        this.line = line;
-    }
-
-    public String name() {
-        return name;
-    }
-
-    public TypeEntry type() {
-        return type;
-    }
-
-    public JavaKind kind() {
-        return kind;
-    }
-
-    public int slot() {
-        return slot;
-    }
-
-    public int line() {
-        return line;
-    }
-
-    public void setLine(int line) {
-        this.line = line;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj == this) return true;
-        if (obj == null || obj.getClass() != this.getClass()) return false;
-        var that = (LocalEntry) obj;
-        return Objects.equals(this.name, that.name) &&
-                Objects.equals(this.type, that.type) &&
-                Objects.equals(this.kind, that.kind) &&
-                this.slot == that.slot &&
-                this.line == that.line;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(name, type, kind, slot, line);
-    }
+public record LocalEntry(String name, TypeEntry type, JavaKind kind, int slot, int line) {
 
     @Override
     public String toString() {
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalValueEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalValueEntry.java
index e177f45669f7..e933e69b5abb 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalValueEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalValueEntry.java
@@ -3,7 +3,7 @@
 import com.oracle.objectfile.debuginfo.DebugInfoProvider.LocalValueKind;
 import jdk.vm.ci.meta.JavaConstant;
 
-public record LocalValueEntry(int line, String name, TypeEntry type, int regIndex, int stackSlot, long heapOffset, JavaConstant constant, LocalValueKind localKind, LocalEntry local) {
+public record LocalValueEntry(int regIndex, int stackSlot, long heapOffset, JavaConstant constant, LocalValueKind localKind, LocalEntry local) {
 
     @Override
     public String toString() {
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 ec17d2e4f724..6cd673f8bc2d 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
@@ -26,9 +26,6 @@
 
 package com.oracle.objectfile.debugentry;
 
-import jdk.vm.ci.meta.JavaKind;
-
-import java.util.ArrayList;
 import java.util.List;
 
 public class MethodEntry extends MemberEntry {
@@ -50,7 +47,7 @@ public class MethodEntry extends MemberEntry {
     public MethodEntry(FileEntry fileEntry, int line, String methodName, StructureTypeEntry ownerType,
                        TypeEntry valueType, int modifiers, List<LocalEntry> paramInfos, LocalEntry thisParam,
                        String symbolName, boolean isDeopt, boolean isOverride, boolean isConstructor, int vtableOffset,
-                       int firstLocalSlot) {
+                       int firstLocalSlot, List<LocalEntry> locals) {
         super(fileEntry, line, methodName, ownerType, valueType, modifiers);
         this.paramInfos = paramInfos;
         this.thisParam = thisParam;
@@ -60,8 +57,8 @@ public MethodEntry(FileEntry fileEntry, int line, String methodName, StructureTy
         this.isConstructor = isConstructor;
         this.vtableOffset = vtableOffset;
         this.firstLocalSlot = firstLocalSlot;
+        this.locals = locals;
 
-        this.locals = new ArrayList<>();
         this.isInRange = false;
         this.isInlined = false;
     }
@@ -90,39 +87,48 @@ public List<TypeEntry> getParamTypes() {
         return paramInfos.stream().map(LocalEntry::type).toList();
     }
 
-    public String getParamTypeName(int idx) {
-        assert idx < paramInfos.size();
-        return paramInfos.get(idx).type().getTypeName();
-    }
-
-    public String getParamName(int idx) {
-        assert idx < paramInfos.size();
-        /* N.b. param names may be null. */
-        return paramInfos.get(idx).name();
-    }
-
-    public int getParamLine(int idx) {
-        assert idx < paramInfos.size();
-        /* N.b. param names may be null. */
-        return paramInfos.get(idx).line();
-    }
-
     public LocalEntry getParam(int i) {
         assert i >= 0 && i < paramInfos.size() : "bad param index";
         return paramInfos.get(i);
     }
 
+    public List<LocalEntry> getParams() {
+        return List.copyOf(paramInfos);
+    }
+
     public LocalEntry getThisParam() {
         return thisParam;
     }
 
-    public int getLocalCount() {
-        return locals.size();
+    public LocalEntry getLocalEntry(String name, int slot, TypeEntry type) {
+        if (slot < 0) {
+            return null;
+        }
+
+        if (slot < firstLocalSlot) {
+            if (thisParam != null) {
+                if (thisParam.slot() == slot && thisParam.name().equals(name) && thisParam.type() == type) {
+                    return thisParam;
+                }
+            }
+            for (LocalEntry param : paramInfos) {
+                if (param.slot() == slot && param.name().equals(name) && param.type() == type) {
+                    return param;
+                }
+            }
+        } else {
+            for (LocalEntry local : locals) {
+                if (local.slot() == slot && local.name().equals(name) && local.type() == type) {
+                    return local;
+                }
+            }
+        }
+
+        return null;
     }
 
-    public LocalEntry getLocal(int i) {
-        assert i >= 0 && i < locals.size() : "bad param index";
-        return locals.get(i);
+    public List<LocalEntry> getLocals() {
+        return List.copyOf(locals);
     }
 
     public boolean isDeopt() {
@@ -164,38 +170,4 @@ public int getVtableOffset() {
     public String getSymbolName() {
         return symbolName;
     }
-
-    public LocalEntry lookupLocalEntry(String name, int slot, TypeEntry type, JavaKind kind, int line) {
-        if (slot < 0) {
-            return null;
-        } else if (slot < firstLocalSlot) {
-            if (thisParam != null) {
-                if (thisParam.slot() == slot && thisParam.name().equals(name) && thisParam.type() == type) {
-                    return thisParam;
-                }
-            }
-            for (LocalEntry param : paramInfos) {
-                if (param.slot() == slot && param.name().equals(name) && param.type() == type) {
-                    return param;
-                }
-            }
-            return null;
-        } else {
-            for (LocalEntry local : locals) {
-                if (local.slot() == slot && local.name().equals(name) && local.type() == type) {
-                    if (line >= 0 && (local.line() < 0 || line < local.line())) {
-                        local.setLine(line);
-                    }
-                    return local;
-                } else if (local.slot() > slot) {
-                    LocalEntry newLocal = new LocalEntry(name, type, kind, slot, line);
-                    locals.add(locals.indexOf(local), newLocal);
-                    return newLocal;
-                }
-            }
-            LocalEntry newLocal = new LocalEntry(name, type, kind, slot, line);
-            locals.add(newLocal);
-            return newLocal;
-        }
-    }
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java
index 19da7c57e9be..3bb5dcb35936 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java
@@ -172,7 +172,7 @@ public Range split(int stackDecrement, int frameSize, int preExtendFrameSize) {
                 // difference between the caller SP and the pre-extend callee SP
                 // because of a stacked return address.
                 int adjustment = frameSize - preExtendFrameSize;
-                newRange.localValueInfos.add(new LocalValueEntry(localInfo.line(), localInfo.name(), localInfo.type(), localInfo.regIndex(), localInfo.stackSlot() + adjustment, localInfo.heapOffset(), localInfo.constant(), localInfo.localKind(), localInfo.local()));
+                newRange.localValueInfos.add(new LocalValueEntry(localInfo.regIndex(), localInfo.stackSlot() + adjustment, localInfo.heapOffset(), localInfo.constant(), localInfo.localKind(), localInfo.local()));
             } else {
                 newRange.localValueInfos.add(localInfo);
             }
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 1c81b8749c0e..b9fa9f2eaef6 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
@@ -1268,12 +1268,9 @@ private int writeMethodParameterDeclaration(DebugContext context, LocalEntry par
 
     private int writeMethodLocalDeclarations(DebugContext context, ClassEntry classEntry, MethodEntry method, int fileIdx, int level, byte[] buffer, int p) {
         int pos = p;
-        int refAddr;
-        for (int i = 0; i < method.getLocalCount(); i++) {
-            refAddr = pos;
-            LocalEntry localInfo = method.getLocal(i);
-            setMethodLocalIndex(classEntry, method, localInfo, refAddr);
-            pos = writeMethodLocalDeclaration(context, localInfo, fileIdx, level, buffer, pos);
+        for (LocalEntry local : method.getLocals()) {
+            setMethodLocalIndex(classEntry, method, local, pos);
+            pos = writeMethodLocalDeclaration(context, local, fileIdx, level, buffer, pos);
         }
         return pos;
     }
@@ -1829,11 +1826,10 @@ private int writeMethodLocalLocations(DebugContext context, ClassEntry classEntr
             assert !range.isLeaf() : "should only be looking up var ranges for inlined calls";
             methodEntry = range.getCallees().getFirst().getMethodEntry();
         }
-        int count = methodEntry.getLocalCount();
-        for (int i = 0; i < count; i++) {
-            LocalEntry localInfo = methodEntry.getLocal(i);
-            int refAddr = getMethodLocalIndex(classEntry, methodEntry, localInfo);
-            pos = writeMethodLocalLocation(context, range, localInfo, varRangeMap, refAddr, depth, false, buffer, pos);
+
+        for (LocalEntry local : methodEntry.getLocals()) {
+            int refAddr = getMethodLocalIndex(classEntry, methodEntry, local);
+            pos = writeMethodLocalLocation(context, range, local, varRangeMap, refAddr, depth, false, buffer, pos);
         }
         return pos;
     }
@@ -1847,7 +1843,7 @@ private int writeMethodLocalLocation(DebugContext context, Range range, LocalEnt
         for (Range subrange : varRangeMap.getOrDefault(localInfo, new ArrayList<>())) {
             LocalValueEntry value = subrange.lookupValue(localInfo);
             if (value != null) {
-                log(context, "  [0x%08x]     local  %s:%s [0x%x, 0x%x] = %s", pos, value.name(), value.type().getTypeName(), subrange.getLo(), subrange.getHi(), formatValue(value));
+                log(context, "  [0x%08x]     local  %s:%s [0x%x, 0x%x] = %s", pos, value.local().name(), value.local().type().getTypeName(), subrange.getLo(), subrange.getHi(), formatValue(value));
                 DebugInfoProvider.LocalValueKind localKind = value.localKind();
                 // can only handle primitive or null constants just now
                 if (localKind == DebugInfoProvider.LocalValueKind.REGISTER || localKind == DebugInfoProvider.LocalValueKind.STACK || (
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLocSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLocSectionImpl.java
index 21c56d9b53fa..4e722966a7f9 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLocSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLocSectionImpl.java
@@ -237,7 +237,7 @@ private int writeVarLocations(DebugContext context, LocalEntry local, long base,
         for (LocalValueExtent extent : extents) {
             LocalValueEntry value = extent.value;
             assert (value != null);
-            log(context, "  [0x%08x]     local  %s:%s [0x%x, 0x%x] = %s", pos, value.name(), value.type().getTypeName(), extent.getLo(), extent.getHi(), formatValue(value));
+            log(context, "  [0x%08x]     local  %s:%s [0x%x, 0x%x] = %s", pos, value.local().name(), value.local().type().getTypeName(), extent.getLo(), extent.getHi(), formatValue(value));
             pos = writeLocationListEntry(DwarfLocationListEntry.DW_LLE_offset_pair, buffer, pos);
             pos = writeULEB(extent.getLo() - base, buffer, pos);
             pos = writeULEB(extent.getHi() - base, 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 88ed01705f2f..e6ab435875e1 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
@@ -69,9 +69,8 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Comparator;
 import java.util.List;
+import java.util.Optional;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Stream;
 
@@ -220,6 +219,13 @@ public void installDebugInfo() {
 
         // create the header type
         lookupHeaderTypeEntry();
+
+        // collect all files and directories referenced by each classEntry
+        typeIndex.values().stream().parallel().forEach(typeEntry -> {
+            if (typeEntry instanceof ClassEntry classEntry) {
+                classEntry.collectFilesAndDirs();
+            }
+        });
     }
 
     private void handleDataInfo(Object data) {
@@ -318,10 +324,15 @@ protected MethodEntry installMethodEntry(SharedMethod method) {
         TypeEntry valueType = lookupTypeEntry((SharedType) method.getSignature().getReturnType(null));
         int modifiers = getModifiers(method);
 
+        // check the local variable table for parameters
+        // if the params are not in the table, we create synthetic ones from the method signature
         List<LocalEntry> paramInfos = getParamEntries(method, line);
         int firstLocalSlot = paramInfos.isEmpty() ? 0 : paramInfos.getLast().slot() + paramInfos.getLast().kind().getSlotCount();
         LocalEntry thisParam = Modifier.isStatic(modifiers) ? null : paramInfos.removeFirst();
 
+        // look for locals in the methods local variable table
+        List<LocalEntry> locals = getLocalEntries(method, firstLocalSlot);
+
         String symbolName = getSymbolName(method); //stringTable.uniqueDebugString(getSymbolName(method));
         int vTableOffset = getVTableOffset(method);
 
@@ -331,7 +342,7 @@ protected MethodEntry installMethodEntry(SharedMethod method) {
 
         return methodIndex.computeIfAbsent(method, m -> new MethodEntry(fileEntry, line, methodName, ownerType,
                 valueType, modifiers, paramInfos, thisParam, symbolName, isDeopt, isOverride, isConstructor,
-                vTableOffset, firstLocalSlot));
+                vTableOffset, firstLocalSlot, locals));
     }
 
     private TypeEntry installTypeEntry(SharedType type) {
@@ -395,6 +406,47 @@ public String getMethodName(SharedMethod method) {
         return name;
     }
 
+    public List<LocalEntry> getLocalEntries(SharedMethod method, int firstLocalSlot) {
+        ArrayList<LocalEntry> localEntries = new ArrayList<>();
+
+        LineNumberTable lnt = method.getLineNumberTable();
+        LocalVariableTable lvt = method.getLocalVariableTable();
+
+        // we do not have any information on local variables
+        if (lvt == null) {
+            return localEntries;
+        }
+
+        SharedType ownerType = (SharedType) method.getDeclaringClass();
+        for (Local local : lvt.getLocals()) {
+            // check if this is a local (slot is after last param slot)
+            if (local != null && local.getSlot() >= firstLocalSlot) {
+                // we have a local with a known name, type and slot
+                String name = local.getName(); //stringTable.uniqueDebugString(l.getName());
+                SharedType type = (SharedType) local.getType().resolve(ownerType);
+                int slot = local.getSlot();
+                JavaKind storageKind = type.getStorageKind();
+                int bciStart = local.getStartBCI();
+                int line = lnt == null ? 0 : lnt.getLineNumber(bciStart);
+                TypeEntry typeEntry = lookupTypeEntry(type);
+
+                Optional<LocalEntry> existingEntry = localEntries.stream()
+                        .filter(le -> le.slot() == slot && le.type() == typeEntry && le.name().equals(name))
+                        .findFirst();
+
+                LocalEntry localEntry = new LocalEntry(name, typeEntry, storageKind, slot, line);
+                if (existingEntry.isEmpty()) {
+                    localEntries.add(localEntry);
+                } else if (existingEntry.get().line() > line) {
+                    localEntries.remove(existingEntry.get());
+                    localEntries.add(localEntry);
+                }
+            }
+        }
+
+        return localEntries;
+    }
+
     public List<LocalEntry> getParamEntries(SharedMethod method, int line) {
         Signature signature = method.getSignature();
         int parameterCount = signature.getParameterCount(false);
@@ -404,17 +456,17 @@ public List<LocalEntry> getParamEntries(SharedMethod method, int line) {
         SharedType ownerType = (SharedType) method.getDeclaringClass();
         if (!method.isStatic()) {
             JavaKind kind = ownerType.getJavaKind();
-            JavaKind storageKind = isForeignWordType(ownerType, ownerType) ? JavaKind.Long : kind;
+            JavaKind storageKind = ownerType.getStorageKind(); // isForeignWordType(ownerType, ownerType) ? JavaKind.Long : kind;
             assert kind == JavaKind.Object : "must be an object";
             paramInfos.add(new LocalEntry("this", lookupTypeEntry(ownerType), storageKind, slot, line));
             slot += kind.getSlotCount();
         }
         for (int i = 0; i < parameterCount; i++) {
-            Local local = (table == null ? null : table.getLocal(slot, 0));
+            Local local = table == null ? null : table.getLocal(slot, 0);
             String name = local != null ? local.getName() : "__" + i; // stringTable.uniqueDebugString(local != null ? local.getName() : "__" + i);
             SharedType paramType = (SharedType) signature.getParameterType(i, null);
             JavaKind kind = paramType.getJavaKind();
-            JavaKind storageKind = isForeignWordType(paramType, ownerType) ? JavaKind.Long : kind;
+            JavaKind storageKind = paramType.getStorageKind(); // isForeignWordType(paramType, ownerType) ? JavaKind.Long : kind;
             paramInfos.add(new LocalEntry(name, lookupTypeEntry(paramType), storageKind, slot, line));
             slot += kind.getSlotCount();
         }
@@ -654,7 +706,7 @@ private void updateInitialLocation(PrimaryRange primary, List<Range> locationInf
         MethodEntry methodEntry = lookupMethodEntry(method);
         Range locationInfo = Range.createSubrange(primary, methodEntry, 0, firstLocationOffset, methodEntry.getLine(), primary, true, true);
 
-        locationInfo.setLocalValueInfo(initSyntheticInfoList(locProducer, method));
+        locationInfo.setLocalValueInfo(initSyntheticInfoList(locProducer, methodEntry));
 
         // if the prologue extends beyond the stack extend and uses the stack then the info
         // needs
@@ -666,39 +718,20 @@ private void updateInitialLocation(PrimaryRange primary, List<Range> locationInf
         locationInfos.addFirst(locationInfo);
     }
 
-    private List<LocalValueEntry> initSyntheticInfoList(ParamLocationProducer locProducer, SharedMethod method) {
-        Signature signature = method.getSignature();
-        int parameterCount = signature.getParameterCount(false);
-        ArrayList<LocalValueEntry> localInfos = new ArrayList<>();
-        LocalVariableTable table = method.getLocalVariableTable();
-        LineNumberTable lineNumberTable = method.getLineNumberTable();
-        int firstLine = (lineNumberTable != null ? lineNumberTable.getLineNumber(0) : -1);
-        int slot = 0;
-        SharedType ownerType = (SharedType) method.getDeclaringClass();
-        if (!method.isStatic()) {
-            String name = "this"; //stringTable.uniqueDebugString("this");
-            JavaKind kind = ownerType.getJavaKind();
-            JavaKind storageKind = isForeignWordType(ownerType, ownerType) ? JavaKind.Long : kind;
-            assert kind == JavaKind.Object : "must be an object";
+    private List<LocalValueEntry> initSyntheticInfoList(ParamLocationProducer locProducer, MethodEntry methodEntry) {
+        ArrayList<LocalValueEntry> localValueInfos = new ArrayList<>();
+        if (methodEntry.getThisParam() != null) {
             JavaValue value = locProducer.thisLocation();
-            TypeEntry type = lookupTypeEntry(ownerType);
-            LocalEntry local = lookupMethodEntry(method).lookupLocalEntry(name, slot, type, storageKind, firstLine);
-            localInfos.add(createLocalValueEntry(name, value, PRE_EXTEND_FRAME_SIZE, storageKind, type, slot, firstLine, local));
-            slot += kind.getSlotCount();
+            LocalEntry thisParam = methodEntry.getThisParam();
+            localValueInfos.add(createLocalValueEntry(value, PRE_EXTEND_FRAME_SIZE, thisParam));
         }
-        for (int i = 0; i < parameterCount; i++) {
-            Local local = (table == null ? null : table.getLocal(slot, 0));
-            String name = local != null ? local.getName() : "__" + i; //stringTable.uniqueDebugString(local != null ? local.getName() : "__" + i);
-            SharedType paramType = (SharedType) signature.getParameterType(i, ownerType);
-            JavaKind kind = paramType.getJavaKind();
-            JavaKind storageKind = isForeignWordType(paramType, ownerType) ? JavaKind.Long : kind;
-            JavaValue value = locProducer.paramLocation(i);
-            TypeEntry type = lookupTypeEntry(paramType);
-            LocalEntry localEntry = lookupMethodEntry(method).lookupLocalEntry(name, slot, type, storageKind, firstLine);
-            localInfos.add(createLocalValueEntry(name, value, PRE_EXTEND_FRAME_SIZE, storageKind, type, slot, firstLine, localEntry));
-            slot += kind.getSlotCount();
+        int paramIdx = 0;
+        for (LocalEntry param : methodEntry.getParams()) {
+            JavaValue value = locProducer.paramLocation(paramIdx);
+            localValueInfos.add(createLocalValueEntry(value, PRE_EXTEND_FRAME_SIZE, param));
+            paramIdx++;
         }
-        return localInfos;
+        return localValueInfos;
     }
 
 
@@ -865,40 +898,26 @@ protected List<LocalValueEntry> initLocalInfoList(BytecodePosition pos, MethodEn
 
         if (pos instanceof BytecodeFrame frame && frame.numLocals > 0) {
             // deal with any inconsistencies in the layout of the frame locals
-            LineNumberTable lineNumberTable = frame.getMethod().getLineNumberTable();
-
             LocalVariableTable lvt = pos.getMethod().getLocalVariableTable();
-            Local[] localsBySlot = null;
-            if (lvt != null) {
-                Local[] locals = lvt.getLocalsAt(pos.getBCI());
-                if (locals != null && locals.length > 0) {
-                    localsBySlot = Arrays.copyOf(locals, locals.length);
-                    Arrays.sort(localsBySlot, Comparator.comparingInt(Local::getSlot));
-                }
-            }
 
-            if (localsBySlot != null) {
-                int count = Integer.min(localsBySlot.length, frame.numLocals);
-                for (int i = 0; i < count; i++) {
-                    Local l = localsBySlot[i];
-                    if (l != null) {
+            if (lvt != null) {
+                SharedType ownerType = (SharedType) pos.getMethod().getDeclaringClass();
+                for (Local local : lvt.getLocalsAt(pos.getBCI())) {
+                    if (local != null) {
                         // we have a local with a known name, type and slot
-                        String name = l.getName(); //stringTable.uniqueDebugString(l.getName());
-                        SharedType ownerType = (SharedType) pos.getMethod().getDeclaringClass();
-                        SharedType type = (SharedType) l.getType().resolve(ownerType);
+                        String name = local.getName(); //stringTable.uniqueDebugString(l.getName());
+                        int slot = local.getSlot();
+                        SharedType type = (SharedType) local.getType().resolve(ownerType);
+                        TypeEntry typeEntry = lookupTypeEntry(type);
+
                         JavaKind kind = type.getJavaKind();
-                        int slot = l.getSlot();
                         JavaValue value = (slot < frame.numLocals ? frame.getLocalValue(slot) : Value.ILLEGAL);
                         JavaKind storageKind = (slot < frame.numLocals ? frame.getLocalValueKind(slot) : JavaKind.Illegal);
-                        int bciStart = l.getStartBCI();
-                        int firstLine = (lineNumberTable != null ? lineNumberTable.getLineNumber(bciStart) : -1);
-                        // only add the local if the kinds match
-                        if ((storageKind == kind) ||
-                                isIntegralKindPromotion(storageKind, kind) ||
+
+                        if ((storageKind == kind) || isIntegralKindPromotion(storageKind, kind) ||
                                 (isForeignWordType(type, ownerType) && kind == JavaKind.Object && storageKind == JavaKind.Long)) {
-                            TypeEntry typeEntry = lookupTypeEntry(type);
-                            LocalEntry localEntry = method.lookupLocalEntry(name, slot, typeEntry, storageKind, firstLine);
-                            localInfos.add(createLocalValueEntry(name, value, frameSize, storageKind, typeEntry, slot, firstLine, localEntry));
+                            LocalEntry localEntry = method.getLocalEntry(name, slot, typeEntry);
+                            localInfos.add(createLocalValueEntry(value, frameSize, localEntry));
                         }
                     }
                 }
@@ -908,7 +927,7 @@ protected List<LocalValueEntry> initLocalInfoList(BytecodePosition pos, MethodEn
         return localInfos;
     }
 
-    private LocalValueEntry createLocalValueEntry(String name, JavaValue value, int frameSize, JavaKind kind, TypeEntry type, int slot, int line, LocalEntry local) {
+    private LocalValueEntry createLocalValueEntry(JavaValue value, int frameSize, LocalEntry local) {
         LocalValueKind localKind;
         int regIndex = -1;
         int stackSlot = -1;
@@ -941,7 +960,7 @@ private LocalValueEntry createLocalValueEntry(String name, JavaValue value, int
             case null, default -> localKind = LocalValueKind.UNDEFINED;
         }
 
-        return new LocalValueEntry(line, name, type, regIndex, stackSlot, heapOffset, constant, localKind, local);
+        return new LocalValueEntry(regIndex, stackSlot, heapOffset, constant, localKind, local);
     }
 
 

From 0678b9375bdcd6bea469bd4f0194f2e7ca333a0d Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Thu, 5 Dec 2024 18:20:19 +0100
Subject: [PATCH 10/34] Add some logging, Create more local info based on frame
 values, Bugfixes and cleanup

---
 .../objectfile/debugentry/ArrayTypeEntry.java |   6 +-
 .../objectfile/debugentry/ClassEntry.java     |  24 +-
 .../objectfile/debugentry/DebugInfoBase.java  |   9 +-
 .../objectfile/debugentry/EnumClassEntry.java |   6 +-
 .../debugentry/ForeignTypeEntry.java          |   8 +-
 .../debugentry/HeaderTypeEntry.java           |   2 +-
 .../debugentry/InterfaceClassEntry.java       |   6 +-
 .../objectfile/debugentry/MethodEntry.java    |  18 +-
 .../debugentry/StructureTypeEntry.java        |   6 +-
 .../debuginfo/DebugInfoProvider.java          |   3 +-
 .../elf/dwarf/DwarfInfoSectionImpl.java       |  10 +-
 .../core/code/CompilationResultFrameTree.java |  10 +-
 .../core/debug/SharedDebugInfoProvider.java   | 550 ++++++++++++------
 .../debug/SubstrateDebugInfoInstaller.java    |  61 +-
 .../debug/SubstrateDebugInfoProvider.java     |  33 +-
 .../image/NativeImageDebugInfoProvider.java   | 309 ++++++++--
 .../NativeImageDebugInfoProviderBase.java     | 485 ---------------
 .../hosted/image/sources/SourceManager.java   |  15 +-
 18 files changed, 728 insertions(+), 833 deletions(-)
 delete mode 100644 substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProviderBase.java

diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ArrayTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ArrayTypeEntry.java
index 70346824800c..6c5e27ba2041 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ArrayTypeEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ArrayTypeEntry.java
@@ -31,9 +31,9 @@ public class ArrayTypeEntry extends StructureTypeEntry {
     private final LoaderEntry loader;
 
     public ArrayTypeEntry(String typeName, int size, long classOffset, long typeSignature,
-                          long compressedTypeSignature, long layoutTypeSignature, long compressedLayoutTypeSignature,
-                          TypeEntry elementType, LoaderEntry loader) {
-        super(typeName, size, classOffset, typeSignature, compressedTypeSignature, layoutTypeSignature, compressedLayoutTypeSignature);
+                    long compressedTypeSignature, long layoutTypeSignature,
+                    TypeEntry elementType, LoaderEntry loader) {
+        super(typeName, size, classOffset, typeSignature, compressedTypeSignature, layoutTypeSignature);
         this.elementType = elementType;
         this.loader = loader;
     }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
index ade8b9f76843..75855ac056f7 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
@@ -26,12 +26,12 @@
 
 package com.oracle.objectfile.debugentry;
 
-import com.oracle.objectfile.debugentry.range.Range;
-
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 
+import com.oracle.objectfile.debugentry.range.Range;
+
 /**
  * Track debug info associated with a Java class.
  */
@@ -70,9 +70,9 @@ public class ClassEntry extends StructureTypeEntry {
     private final List<DirEntry> dirs;
 
     public ClassEntry(String typeName, int size, long classOffset, long typeSignature,
-                      long compressedTypeSignature, long layoutTypeSignature, long compressedLayoutTypeSignature,
-                      ClassEntry superClass, FileEntry fileEntry, LoaderEntry loader) {
-        super(typeName, size, classOffset, typeSignature, compressedTypeSignature, layoutTypeSignature, compressedLayoutTypeSignature);
+                    long compressedTypeSignature, long layoutTypeSignature,
+                    ClassEntry superClass, FileEntry fileEntry, LoaderEntry loader) {
+        super(typeName, size, classOffset, typeSignature, compressedTypeSignature, layoutTypeSignature);
         this.superClass = superClass;
         this.fileEntry = fileEntry;
         this.loader = loader;
@@ -89,18 +89,18 @@ public void collectFilesAndDirs() {
         fileSet.add(fileEntry);
 
         // add all files of declared methods
-        fileSet.addAll(methods.parallelStream().map(MethodEntry::getFileEntry).toList());
+        fileSet.addAll(methods.stream().map(MethodEntry::getFileEntry).toList());
 
         // add all files required for compilations
-        // no need to add the primary range as this is the same as the corresponding method declaration file
+        // no need to add the primary range as this is the same as the corresponding method
+        // declaration file
         fileSet.addAll(compiledMethods.parallelStream()
-                .flatMap(CompiledMethodEntry::topDownRangeStream)
-                .map(Range::getFileEntry)
-                .toList()
-        );
+                        .flatMap(CompiledMethodEntry::topDownRangeStream)
+                        .map(Range::getFileEntry)
+                        .toList());
 
         // add all files of fields
-        fileSet.addAll(getFields().parallelStream().map(FieldEntry::getFileEntry).toList());
+        fileSet.addAll(getFields().stream().map(FieldEntry::getFileEntry).toList());
 
         // fill file list from set
         fileSet.forEach(this::addFile);
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java
index 72d2fb58a731..656715a4a938 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java
@@ -32,8 +32,6 @@
 
 import com.oracle.objectfile.debuginfo.DebugInfoProvider;
 
-import jdk.graal.compiler.debug.DebugContext;
-
 /**
  * An abstract class which indexes the information presented by the DebugInfoProvider in an
  * organization suitable for use by subclasses targeting a specific binary format.
@@ -91,10 +89,8 @@ public abstract class DebugInfoBase {
 
     private final List<ClassEntry> instanceClassesWithCompilation = new ArrayList<>();
 
-
     private final List<PrimitiveTypeEntry> primitiveTypes = new ArrayList<>();
 
-
     private final List<ArrayTypeEntry> arrayTypes = new ArrayList<>();
     /**
      * Handle on type entry for header structure.
@@ -168,19 +164,19 @@ public abstract class DebugInfoBase {
      * A prefix used to label indirect types used to ensure gdb performs oop reference --> raw
      * address translation
      */
-    public static final String INDIRECT_PREFIX = "_z_.";
+    public static final String COMPRESSED_PREFIX = "_z_.";
     /*
      * A prefix used for type signature generation to generate unique type signatures for type
      * layout type units
      */
     public static final String LAYOUT_PREFIX = "_layout_.";
+
     /*
      * The name of the type for header field hub which needs special case processing to remove tag
      * bits
      */
     public static final String HUB_TYPE_NAME = "java.lang.Class";
 
-
     public DebugInfoBase(ByteOrder byteOrder) {
         this.byteOrder = byteOrder;
         this.useHeapBase = true;
@@ -296,7 +292,6 @@ public ClassEntry lookupObjectClass() {
         return objectClass;
     }
 
-
     /* Accessors to query the debug info model. */
     public ByteOrder getByteOrder() {
         return byteOrder;
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/EnumClassEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/EnumClassEntry.java
index 898d62d6c4af..0e8e82c430e3 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/EnumClassEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/EnumClassEntry.java
@@ -28,9 +28,9 @@
 
 public class EnumClassEntry extends ClassEntry {
     public EnumClassEntry(String typeName, int size, long classOffset, long typeSignature,
-                          long compressedTypeSignature, long layoutTypeSignature, long compressedLayoutTypeSignature,
-                          ClassEntry superClass, FileEntry fileEntry, LoaderEntry loader) {
-        super(typeName, size, classOffset, typeSignature, compressedTypeSignature, layoutTypeSignature, compressedLayoutTypeSignature, superClass, fileEntry, loader);
+                    long compressedTypeSignature, long layoutTypeSignature,
+                    ClassEntry superClass, FileEntry fileEntry, LoaderEntry loader) {
+        super(typeName, size, classOffset, typeSignature, compressedTypeSignature, layoutTypeSignature, superClass, fileEntry, loader);
     }
 
     @Override
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignTypeEntry.java
index 953a843dedc5..07812d99fc9f 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignTypeEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignTypeEntry.java
@@ -38,10 +38,10 @@ public class ForeignTypeEntry extends ClassEntry {
     private final boolean isFloat;
 
     public ForeignTypeEntry(String typeName, int size, long classOffset, long typeSignature,
-                            long layoutTypeSignature, ClassEntry superClass, FileEntry fileEntry, LoaderEntry loader,
-                            String typedefName, ForeignTypeEntry parent, TypeEntry pointerTo, boolean isWord,
-                            boolean isStruct, boolean isPointer, boolean isInteger, boolean isSigned, boolean isFloat) {
-        super(typeName, size, classOffset, typeSignature, typeSignature, layoutTypeSignature, layoutTypeSignature, superClass, fileEntry, loader);
+                    long layoutTypeSignature, ClassEntry superClass, FileEntry fileEntry, LoaderEntry loader,
+                    String typedefName, ForeignTypeEntry parent, TypeEntry pointerTo, boolean isWord,
+                    boolean isStruct, boolean isPointer, boolean isInteger, boolean isSigned, boolean isFloat) {
+        super(typeName, size, classOffset, typeSignature, typeSignature, layoutTypeSignature, superClass, fileEntry, loader);
         this.typedefName = typedefName;
         this.parent = parent;
         this.pointerTo = pointerTo;
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/HeaderTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/HeaderTypeEntry.java
index 165f0de77f7b..0ec06e4aa799 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/HeaderTypeEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/HeaderTypeEntry.java
@@ -29,7 +29,7 @@
 public class HeaderTypeEntry extends StructureTypeEntry {
 
     public HeaderTypeEntry(String typeName, int size, long typeSignature) {
-        super(typeName, size, -1, typeSignature, typeSignature, typeSignature, typeSignature);
+        super(typeName, size, -1, typeSignature, typeSignature, typeSignature);
     }
 
     @Override
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/InterfaceClassEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/InterfaceClassEntry.java
index 5e1a684f8baf..88616655d9f4 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/InterfaceClassEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/InterfaceClassEntry.java
@@ -34,9 +34,9 @@ public class InterfaceClassEntry extends ClassEntry {
     private final List<ClassEntry> implementors;
 
     public InterfaceClassEntry(String typeName, int size, long classOffset, long typeSignature,
-                               long compressedTypeSignature, long layoutTypeSignature, long compressedLayoutTypeSignature,
-                               ClassEntry superClass, FileEntry fileEntry, LoaderEntry loader) {
-        super(typeName, size, classOffset, typeSignature, compressedTypeSignature, layoutTypeSignature, compressedLayoutTypeSignature, superClass, fileEntry, loader);
+                    long compressedTypeSignature, long layoutTypeSignature,
+                    ClassEntry superClass, FileEntry fileEntry, LoaderEntry loader) {
+        super(typeName, size, classOffset, typeSignature, compressedTypeSignature, layoutTypeSignature, superClass, fileEntry, loader);
         this.implementors = new ArrayList<>();
     }
 
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 6cd673f8bc2d..76f71fe2e68b 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
@@ -28,6 +28,8 @@
 
 import java.util.List;
 
+import jdk.vm.ci.meta.JavaKind;
+
 public class MethodEntry extends MemberEntry {
     private final LocalEntry thisParam;
     private final List<LocalEntry> paramInfos;
@@ -45,9 +47,9 @@ public class MethodEntry extends MemberEntry {
 
     @SuppressWarnings("this-escape")
     public MethodEntry(FileEntry fileEntry, int line, String methodName, StructureTypeEntry ownerType,
-                       TypeEntry valueType, int modifiers, List<LocalEntry> paramInfos, LocalEntry thisParam,
-                       String symbolName, boolean isDeopt, boolean isOverride, boolean isConstructor, int vtableOffset,
-                       int firstLocalSlot, List<LocalEntry> locals) {
+                    TypeEntry valueType, int modifiers, List<LocalEntry> paramInfos, LocalEntry thisParam,
+                    String symbolName, boolean isDeopt, boolean isOverride, boolean isConstructor, int vtableOffset,
+                    int firstLocalSlot, List<LocalEntry> locals) {
         super(fileEntry, line, methodName, ownerType, valueType, modifiers);
         this.paramInfos = paramInfos;
         this.thisParam = thisParam;
@@ -100,7 +102,7 @@ public LocalEntry getThisParam() {
         return thisParam;
     }
 
-    public LocalEntry getLocalEntry(String name, int slot, TypeEntry type) {
+    public LocalEntry lookupLocalEntry(String name, int slot, TypeEntry type, JavaKind kind, int line) {
         if (slot < 0) {
             return null;
         }
@@ -122,6 +124,14 @@ public LocalEntry getLocalEntry(String name, int slot, TypeEntry type) {
                     return local;
                 }
             }
+
+            LocalEntry local = new LocalEntry(name, type, kind, slot, line);
+            synchronized (locals) {
+                if (!locals.contains(local)) {
+                    locals.add(local);
+                }
+            }
+            return local;
         }
 
         return null;
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java
index f73296e37d54..c0456db2ce96 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java
@@ -49,7 +49,7 @@ public abstract class StructureTypeEntry extends TypeEntry {
     protected long layoutTypeSignature;
 
     public StructureTypeEntry(String typeName, int size, long classOffset, long typeSignature,
-                              long compressedTypeSignature, long layoutTypeSignature, long compressedLayoutTypeSignature) {
+                    long compressedTypeSignature, long layoutTypeSignature) {
         super(typeName, size, classOffset, typeSignature, compressedTypeSignature);
         this.layoutTypeSignature = layoutTypeSignature;
 
@@ -100,4 +100,8 @@ String memberModifiers(int modifiers) {
 
         return builder.toString();
     }
+
+    public String getModifiersString(int modifiers) {
+        return memberModifiers(modifiers);
+    }
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java
index 50b91f99f823..4353c5793254 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java
@@ -41,6 +41,7 @@ public interface DebugInfoProvider {
     void installDebugInfo();
 
     boolean useHeapBase();
+
     boolean isRuntimeCompilation();
 
     /**
@@ -73,11 +74,11 @@ public interface DebugInfoProvider {
     StringTable getStringTable();
 
     List<TypeEntry> typeEntries();
+
     List<CompiledMethodEntry> compiledMethodEntries();
 
     String cachePath();
 
-
     enum FrameSizeChangeType {
         EXTEND,
         CONTRACT;
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 b9fa9f2eaef6..6a466192ea91 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
@@ -758,7 +758,7 @@ private int writeForeignLayoutTypeUnit(DebugContext context, ForeignTypeEntry fo
         int lengthPos = pos;
 
         /* Only write a TU preamble if we will write a new layout type. */
-        if (foreignTypeEntry.isWord() || foreignTypeEntry.isIntegral() || foreignTypeEntry.isFloat() || foreignTypeEntry.isStruct()) {
+        if (foreignTypeEntry.isWord() || foreignTypeEntry.isInteger() || foreignTypeEntry.isFloat() || foreignTypeEntry.isStruct()) {
             pos = writeTUPreamble(context, foreignTypeEntry.getLayoutTypeSignature(), loaderId, buffer, pos);
         }
 
@@ -787,13 +787,7 @@ private int writeForeignLayoutTypeUnit(DebugContext context, ForeignTypeEntry fo
                 }
             }
             log(context, "  [0x%08x] foreign pointer type %s referent 0x%x (%s)", pos, foreignTypeEntry.getTypeName(), targetType.getTypeSignature(), targetType.getTypeName());
-            /*
-             * Setting the layout type to the type we point to reuses an available type unit, so we
-             * do not have to write are separate type unit.
-             *
-             * As we do not write anything, we can just return the initial position.
-             */
-            foreignTypeEntry.setLayoutTypeSignature(targetType.getTypeSignature());
+            // As we do not write anything, we can just return the initial position.
             return p;
         }
 
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CompilationResultFrameTree.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CompilationResultFrameTree.java
index c31ee7208676..4c7b3af55c48 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CompilationResultFrameTree.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CompilationResultFrameTree.java
@@ -496,16 +496,12 @@ public CallNode build(CompilationResult compilationResult) {
                         SourceMappingWrapper wrapper = SourceMappingWrapper.create(sourceMapping, maxDepth);
                         if (wrapper != null) {
                             if (wrapper.getStartOffset() > targetCodeSize - 1) {
-                                if (debug.isLogEnabled(DebugContext.DETAILED_LEVEL)) {
-                                    debug.log(" Discard SourceMapping outside code-range %s", SourceMappingWrapper.getSourceMappingString(sourceMapping));
-                                }
+                                debug.log(DebugContext.DETAILED_LEVEL, " Discard SourceMapping outside code-range %s", SourceMappingWrapper.getSourceMappingString(sourceMapping));
                                 continue;
                             }
                             sourcePosData.add(wrapper);
                         } else {
-                            if (debug.isLogEnabled(DebugContext.DETAILED_LEVEL)) {
-                                debug.log(" Discard SourceMapping %s", SourceMappingWrapper.getSourceMappingString(sourceMapping));
-                            }
+                            debug.log(DebugContext.DETAILED_LEVEL, " Discard SourceMapping %s", SourceMappingWrapper.getSourceMappingString(sourceMapping));
                         }
                     }
                 }
@@ -697,7 +693,7 @@ private CallNode visitFrame(SourcePositionSupplier sourcePos, BytecodePosition f
                 }
 
                 boolean hasEqualCaller = FrameNode.hasEqualCaller(root.frame, frame);
-                if (debug.isLogEnabled() && !hasEqualCaller) {
+                if (!hasEqualCaller) {
                     debug.log("Bottom frame mismatch for %s", sourcePos);
                 }
                 if (callee == null && hasEqualCaller) {
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 e6ab435875e1..6f6001756686 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
@@ -1,5 +1,18 @@
 package com.oracle.svm.core.debug;
 
+import java.lang.reflect.Modifier;
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Stream;
+
+import org.graalvm.collections.Pair;
+import org.graalvm.word.WordBase;
+
 import com.oracle.objectfile.debugentry.ClassEntry;
 import com.oracle.objectfile.debugentry.CompiledMethodEntry;
 import com.oracle.objectfile.debugentry.DirEntry;
@@ -18,6 +31,7 @@
 import com.oracle.objectfile.debugentry.range.PrimaryRange;
 import com.oracle.objectfile.debugentry.range.Range;
 import com.oracle.objectfile.debuginfo.DebugInfoProvider;
+import com.oracle.svm.core.SubstrateOptions;
 import com.oracle.svm.core.UniqueShortNameProvider;
 import com.oracle.svm.core.code.CompilationResultFrameTree;
 import com.oracle.svm.core.config.ConfigurationValues;
@@ -33,6 +47,8 @@
 import com.oracle.svm.core.heap.ReferenceAccess;
 import com.oracle.svm.core.meta.SharedMethod;
 import com.oracle.svm.core.meta.SharedType;
+
+import jdk.graal.compiler.api.replacements.Fold;
 import jdk.graal.compiler.code.CompilationResult;
 import jdk.graal.compiler.core.common.CompilationIdentifier;
 import jdk.graal.compiler.core.target.Backend;
@@ -61,25 +77,64 @@
 import jdk.vm.ci.meta.ResolvedJavaType;
 import jdk.vm.ci.meta.Signature;
 import jdk.vm.ci.meta.Value;
-import org.graalvm.collections.Pair;
-import org.graalvm.word.WordBase;
-
-import java.lang.reflect.Modifier;
-import java.nio.file.FileSystems;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.stream.Stream;
-
 
+/**
+ * A shared base for providing debug info that can be processed by any debug info format specified
+ * in ObjectFile.
+ *
+ * <p>
+ * Debug Info is provided as Debug Entries, with each entry containing Debug Info for a logical
+ * unit, i.e. Types, ClassLoaders, Methods, Fields, Compilations as well as the underlying file
+ * system . Debug Entries model the hosted universe to provide a normalized view on debug info data
+ * for all debug info implementations.
+ * </p>
+ *
+ * <ul>
+ * The Debug Info contains the following Debug Entries up as follows:
+ * <li>DirEntry: Represents a parent directory for one or more FileEntries, based on the debug
+ * sources directory. One root directory entry contains the path to the debug sources.</li>
+ * <li>FileEntry: Represents a single source file, which may be used by the debugger to provide
+ * source code. During native-image build, the SourceManager safes all the processed source files
+ * and provides the debug sources directory.</li>
+ * <li>LoaderEntry: Represents a class loader entry. Built-in class loaders and image classloaders
+ * are not stored as LoaderEntries but implicitly inferred for types with no LoaderEntry.</li>
+ * <li>TypeEntry: Represents one shared type. For native image build time debug info there exists
+ * one TypeEntry per type in the HostedUniverse. TypeEntries are divided into following categories:
+ * <ul>
+ * <li>PrimitiveTypeEntry: Represents a primitive java type.</li>
+ * <li>HeaderTypeEntry: A special TypeEntry that represents the object header information in the
+ * native image heap, as sort of a super type to Object.</li>
+ * <li>ArrayTypeEntry: Represents an array type.</li>
+ * <li>ForeignTypeEntry: Represents a type that is not a java class, e.g. CStruct types, CPointer
+ * types, ... .</li>
+ * <li>EnumClassEntry: Represents an enumeration class.</li>
+ * <li>InterfaceClassEntry: Represents an interface class, and stores references to all
+ * implementors.</li>
+ * <li>ClassEntry: Represents any other java class that is not already covered by other type entries
+ * (Instance classes).</li>
+ * </ul>
+ * </li>
+ * <li>MethodEntry: Represents a method declaration and holds a list of all parameters and locals
+ * that are used within the method.</li>
+ * <li>CompiledMethodEntry: Represents a compilation. Is composed of ranges, i.e. frame states and
+ * location information of params and locals (where variables are stored). A CompiledMethodEntry
+ * always has a PrimaryRange that spans the whole compilation, which is further composed of:
+ * <ul>
+ * <li>LeafRange: A leaf in the compilation tree.</li>
+ * <li>CallRange: A CallNode in the compilation tree. Represents inlined calls and is therefore
+ * itself composed of ranges.</li>
+ * </ul>
+ * </li>
+ * </ul>
+ */
 public abstract class SharedDebugInfoProvider implements DebugInfoProvider {
 
     protected final RuntimeConfiguration runtimeConfiguration;
     protected final MetaAccessProvider metaAccess;
     protected final UniqueShortNameProvider nameProvider;
+
+    protected final DebugContext debug;
+
     protected final boolean useHeapBase;
     protected final int compressionShift;
     protected final int referenceSize;
@@ -99,13 +154,12 @@ public abstract class SharedDebugInfoProvider implements DebugInfoProvider {
     private final ConcurrentHashMap<CompilationIdentifier, CompiledMethodEntry> compiledMethodIndex = new ConcurrentHashMap<>();
     protected final StringTable stringTable = new StringTable();
 
-    /* Ensure we have a null string and  in the string section. */
+    /* Ensure we have a null string and in the string section. */
     protected final String uniqueNullStringEntry = stringTable.uniqueDebugString("");
     protected final String cachePathEntry;
 
     private HeaderTypeEntry headerTypeEntry;
 
-
     /*
      * A prefix used to label indirect types used to ensure gdb performs oop reference --> raw
      * address translation
@@ -117,15 +171,15 @@ public abstract class SharedDebugInfoProvider implements DebugInfoProvider {
      */
     public static final String LAYOUT_PREFIX = "_layout_.";
 
-
     public static final String CLASS_CONSTANT_SUFFIX = ".class";
 
     static final Path EMPTY_PATH = Paths.get("");
-    
-    public SharedDebugInfoProvider(RuntimeConfiguration runtimeConfiguration, MetaAccessProvider metaAccess, UniqueShortNameProvider nameProvider, Path cachePath) {
+
+    public SharedDebugInfoProvider(DebugContext debug, RuntimeConfiguration runtimeConfiguration, MetaAccessProvider metaAccess, UniqueShortNameProvider nameProvider, Path cachePath) {
         this.runtimeConfiguration = runtimeConfiguration;
         this.metaAccess = metaAccess;
         this.nameProvider = nameProvider;
+        this.debug = debug.isLogEnabled() ? debug : DebugContext.disabled(null);
         this.cachePathEntry = stringTable.uniqueDebugString(cachePath.toString());
         this.hubType = (SharedType) metaAccess.lookupJavaType(Class.class);
         this.wordBaseType = (SharedType) metaAccess.lookupJavaType(WordBase.class);
@@ -140,7 +194,9 @@ public SharedDebugInfoProvider(RuntimeConfiguration runtimeConfiguration, MetaAc
     }
 
     protected abstract Stream<SharedType> typeInfo();
+
     protected abstract Stream<Pair<SharedMethod, CompilationResult>> codeInfo();
+
     protected abstract Stream<Object> dataInfo();
 
     @Override
@@ -204,87 +260,118 @@ public List<CompiledMethodEntry> compiledMethodEntries() {
         return compiledMethodIndex.values().stream().toList();
     }
 
-    /* TODO create debug info providers for debugging sections */
-
     /* Functions for installing debug info into the index maps. */
     @Override
     public void installDebugInfo() {
-        // create and index an empty dir with index 0 for null paths.
-        lookupDirEntry(EMPTY_PATH);
+        // we can only meaningfully provide logging if debug info is produced sequentially
+        Stream<SharedType> typeStream = debug.isLogEnabled() ? typeInfo() : typeInfo().parallel();
+        Stream<Pair<SharedMethod, CompilationResult>> codeStream = debug.isLogEnabled() ? codeInfo() : codeInfo().parallel();
+        Stream<Object> dataStream = debug.isLogEnabled() ? dataInfo() : dataInfo().parallel();
+
+        try (DebugContext.Scope s = debug.scope("DebugInfoProvider")) {
+            // create and index an empty dir with index 0 for null paths.
+            lookupDirEntry(EMPTY_PATH);
+
+            // handle types, compilations and data
+            // code info needs to be handled first as it contains source file infos of compilations
+            // which are collected in the class entry
+            codeStream.forEach(pair -> handleCodeInfo(pair.getLeft(), pair.getRight()));
+            typeStream.forEach(this::handleTypeInfo);
+            dataStream.forEach(this::handleDataInfo);
+
+            // create the header type
+            handleTypeInfo(null);
+        } catch (Throwable e) {
+            throw debug.handle(e);
+        }
+    }
 
-        // handle types, compilations and data
-        typeInfo().parallel().forEach(this::handleTypeInfo);
-        codeInfo().parallel().forEach(pair -> handleCodeInfo(pair.getLeft(), pair.getRight()));
-        dataInfo().parallel().forEach(this::handleDataInfo);
+    protected void handleDataInfo(Object data) {
+    }
 
-        // create the header type
-        lookupHeaderTypeEntry();
+    private void handleTypeInfo(SharedType type) {
+        TypeEntry typeEntry = lookupTypeEntry(type);
 
         // collect all files and directories referenced by each classEntry
-        typeIndex.values().stream().parallel().forEach(typeEntry -> {
-            if (typeEntry instanceof ClassEntry classEntry) {
-                classEntry.collectFilesAndDirs();
-            }
-        });
+        if (typeEntry instanceof ClassEntry classEntry) {
+            classEntry.collectFilesAndDirs();
+        }
     }
 
-    private void handleDataInfo(Object data) {
-
-    }
-    private void handleTypeInfo(SharedType type) {
-        lookupTypeEntry(type);
-    }
     private void handleCodeInfo(SharedMethod method, CompilationResult compilation) {
+        // First make sure the underlying MethodEntry exists
         MethodEntry methodEntry = lookupMethodEntry(method);
+        // Then process the compilation for frame states from infopoints/sourcemappings
         lookupCompiledMethodEntry(methodEntry, method, compilation);
     }
 
+    @Fold
+    static boolean omitInline() {
+        return SubstrateOptions.OmitInlinedMethodDebugLineInfo.getValue();
+    }
+
+    @Fold
+    static int debugCodeInfoMaxDepth() {
+        return SubstrateOptions.DebugCodeInfoMaxDepth.getValue();
+    }
+
+    @Fold
+    static boolean debugCodeInfoUseSourceMappings() {
+        return SubstrateOptions.DebugCodeInfoUseSourceMappings.getValue();
+    }
+
     protected CompiledMethodEntry installCompilationInfo(MethodEntry methodEntry, SharedMethod method, CompilationResult compilation) {
-        methodEntry.setInRange();
-
-        int primaryLine = methodEntry.getLine();
-        int frameSize = compilation.getTotalFrameSize();
-        List<FrameSizeChangeEntry> frameSizeChanges = getFrameSizeChanges(compilation);
-        ClassEntry ownerType = methodEntry.getOwnerType();
-        PrimaryRange primaryRange = Range.createPrimary(methodEntry, 0, compilation.getTargetCodeSize(), primaryLine, getCodeOffset(method));
-        CompiledMethodEntry compiledMethodEntry = new CompiledMethodEntry(primaryRange, frameSizeChanges, frameSize, ownerType);
-        if (compiledMethodIndex.putIfAbsent(compilation.getCompilationId(), compiledMethodEntry) == null) {
-            // can we still provide locals if we have no file name?
-            if (methodEntry.getFileName().isEmpty()) {
-                return compiledMethodEntry;
-            }
+        try (DebugContext.Scope s = debug.scope("DebugInfoCompilation")) {
+            debug.log("test");
+
+            methodEntry.setInRange();
+
+            int primaryLine = methodEntry.getLine();
+            int frameSize = compilation.getTotalFrameSize();
+            List<FrameSizeChangeEntry> frameSizeChanges = getFrameSizeChanges(compilation);
+            ClassEntry ownerType = methodEntry.getOwnerType();
+
+            PrimaryRange primaryRange = Range.createPrimary(methodEntry, 0, compilation.getTargetCodeSize(), primaryLine, getCodeOffset(method));
+            debug.log(DebugContext.INFO_LEVEL, "PrimaryRange %s.%s %s %s:%d [0x%x, 0x%x]", ownerType.getTypeName(), methodEntry.getMethodName(), primaryRange.getFileEntry().getPathName(),
+                            primaryRange.getFileName(), primaryLine, primaryRange.getLo(), primaryRange.getHi());
+
+            CompiledMethodEntry compiledMethodEntry = new CompiledMethodEntry(primaryRange, frameSizeChanges, frameSize, ownerType);
+            if (compiledMethodIndex.putIfAbsent(compilation.getCompilationId(), compiledMethodEntry) == null) {
+                // can we still provide locals if we have no file name?
+                if (methodEntry.getFileName().isEmpty()) {
+                    return compiledMethodEntry;
+                }
 
-            boolean omitInline = false; // SubstrateOptions.OmitInlinedMethodDebugLineInfo.getValue();
-            int maxDepth = Integer.MAX_VALUE; // SubstrateOptions.DebugCodeInfoMaxDepth.getValue();
-            boolean useSourceMappings = false; // SubstrateOptions.DebugCodeInfoUseSourceMappings.getValue();
-//            if (omitInline) {
-//                if (!SubstrateOptions.DebugCodeInfoMaxDepth.hasBeenSet()) {
-//                    /* TopLevelVisitor will not go deeper than level 2 */
-//                    maxDepth = 2;
-//                }
-//                if (!SubstrateOptions.DebugCodeInfoUseSourceMappings.hasBeenSet()) {
-//                    /* Skip expensive CompilationResultFrameTree building with SourceMappings */
-//                    useSourceMappings = false;
-//                }
-//            }
-            final CompilationResultFrameTree.CallNode root = new CompilationResultFrameTree.Builder(DebugContext.forCurrentThread(), compilation.getTargetCodeSize(), maxDepth, useSourceMappings, true).build(compilation);
-            if (root == null) {
-                return compiledMethodEntry;
+                boolean omitInline = omitInline();
+                int maxDepth = debugCodeInfoMaxDepth();
+                boolean useSourceMappings = debugCodeInfoUseSourceMappings();
+                if (omitInline) {
+                    /* TopLevelVisitor will not go deeper than level 2 */
+                    maxDepth = 2;
+                }
+                final CompilationResultFrameTree.CallNode root = new CompilationResultFrameTree.Builder(debug, compilation.getTargetCodeSize(), maxDepth, useSourceMappings,
+                                true)
+                                .build(compilation);
+                if (root == null) {
+                    return compiledMethodEntry;
+                }
+                final List<Range> subRanges = new ArrayList<>();
+                final CompilationResultFrameTree.Visitor visitor = omitInline ? new TopLevelVisitor(subRanges, frameSize, primaryRange) : new MultiLevelVisitor(subRanges, frameSize, primaryRange);
+                // arguments passed by visitor to apply are
+                // NativeImageDebugLocationInfo caller location info
+                // CallNode nodeToEmbed parent call node to convert to entry code leaf
+                // NativeImageDebugLocationInfo leaf into which current leaf may be merged
+                root.visitChildren(visitor, primaryRange, null, null);
+                // try to add a location record for offset zero
+                updateInitialLocation(primaryRange, subRanges, compilation, method);
+
+                ownerType.addCompiledMethod(compiledMethodEntry);
             }
-            final List<Range> subRanges = new ArrayList<>();
-            final CompilationResultFrameTree.Visitor visitor = (omitInline ? new TopLevelVisitor(subRanges, frameSize, primaryRange) : new MultiLevelVisitor(subRanges, frameSize, primaryRange));
-            // arguments passed by visitor to apply are
-            // NativeImageDebugLocationInfo caller location info
-            // CallNode nodeToEmbed parent call node to convert to entry code leaf
-            // NativeImageDebugLocationInfo leaf into which current leaf may be merged
-            root.visitChildren(visitor, primaryRange, null, null);
-            // try to add a location record for offset zero
-            updateInitialLocation(primaryRange, subRanges, compilation, method);
-
-            ownerType.addCompiledMethod(compiledMethodEntry);
-        }
 
-        return compiledMethodEntry;
+            return compiledMethodEntry;
+        } catch (Throwable e) {
+            throw debug.handle(e);
+        }
     }
 
     public List<FrameSizeChangeEntry> getFrameSizeChanges(CompilationResult compilation) {
@@ -313,52 +400,67 @@ public List<FrameSizeChangeEntry> getFrameSizeChanges(CompilationResult compilat
     protected abstract long getCodeOffset(SharedMethod method);
 
     protected MethodEntry installMethodEntry(SharedMethod method) {
-        FileEntry fileEntry = lookupFileEntry(method);
-
-        LineNumberTable lineNumberTable = method.getLineNumberTable();
-        int line = lineNumberTable == null ? 0 : lineNumberTable.getLineNumber(0);
-
-        String methodName = getMethodName(method); // stringTable.uniqueDebugString(getMethodName(method));
-        StructureTypeEntry ownerType = (StructureTypeEntry) lookupTypeEntry((SharedType) method.getDeclaringClass());
-        assert ownerType instanceof ClassEntry;
-        TypeEntry valueType = lookupTypeEntry((SharedType) method.getSignature().getReturnType(null));
-        int modifiers = getModifiers(method);
+        try (DebugContext.Scope s = debug.scope("DebugInfoMethod")) {
+            debug.log("test");
+            FileEntry fileEntry = lookupFileEntry(method);
 
-        // check the local variable table for parameters
-        // if the params are not in the table, we create synthetic ones from the method signature
-        List<LocalEntry> paramInfos = getParamEntries(method, line);
-        int firstLocalSlot = paramInfos.isEmpty() ? 0 : paramInfos.getLast().slot() + paramInfos.getLast().kind().getSlotCount();
-        LocalEntry thisParam = Modifier.isStatic(modifiers) ? null : paramInfos.removeFirst();
-
-        // look for locals in the methods local variable table
-        List<LocalEntry> locals = getLocalEntries(method, firstLocalSlot);
-
-        String symbolName = getSymbolName(method); //stringTable.uniqueDebugString(getSymbolName(method));
-        int vTableOffset = getVTableOffset(method);
-
-        boolean isDeopt = isDeopt(method, methodName);
-        boolean isOverride = isOverride(method);
-        boolean isConstructor = isConstructor(method);
-
-        return methodIndex.computeIfAbsent(method, m -> new MethodEntry(fileEntry, line, methodName, ownerType,
-                valueType, modifiers, paramInfos, thisParam, symbolName, isDeopt, isOverride, isConstructor,
-                vTableOffset, firstLocalSlot, locals));
+            LineNumberTable lineNumberTable = method.getLineNumberTable();
+            int line = lineNumberTable == null ? 0 : lineNumberTable.getLineNumber(0);
+
+            String methodName = getMethodName(method); // stringTable.uniqueDebugString(getMethodName(method));
+            StructureTypeEntry ownerType = (StructureTypeEntry) lookupTypeEntry((SharedType) method.getDeclaringClass());
+            assert ownerType instanceof ClassEntry;
+            TypeEntry valueType = lookupTypeEntry((SharedType) method.getSignature().getReturnType(null));
+            int modifiers = getModifiers(method);
+
+            // check the local variable table for parameters
+            // if the params are not in the table, we create synthetic ones from the method
+            // signature
+            List<LocalEntry> paramInfos = getParamEntries(method, line);
+            int firstLocalSlot = paramInfos.isEmpty() ? 0 : paramInfos.getLast().slot() + paramInfos.getLast().kind().getSlotCount();
+            LocalEntry thisParam = Modifier.isStatic(modifiers) ? null : paramInfos.removeFirst();
+
+            // look for locals in the methods local variable table
+            List<LocalEntry> locals = getLocalEntries(method, firstLocalSlot);
+
+            String symbolName = getSymbolName(method); // stringTable.uniqueDebugString(getSymbolName(method));
+            int vTableOffset = getVTableOffset(method);
+
+            boolean isDeopt = isDeopt(method, methodName);
+            boolean isOverride = isOverride(method);
+            boolean isConstructor = isConstructor(method);
+
+            return methodIndex.computeIfAbsent(method, m -> new MethodEntry(fileEntry, line, methodName, ownerType,
+                            valueType, modifiers, paramInfos, thisParam, symbolName, isDeopt, isOverride, isConstructor,
+                            vTableOffset, firstLocalSlot, locals));
+        } catch (Throwable e) {
+            throw debug.handle(e);
+        }
     }
 
     private TypeEntry installTypeEntry(SharedType type) {
-        TypeEntry typeEntry = createTypeEntry(type);
-        if (typeIndex.putIfAbsent(type, typeEntry) == null) {
-            /* Add class constant name to string table. */
-            if (typeEntry.getClassOffset() != -1) {
-                // stringTable.uniqueDebugString(typeEntry.getTypeName() + CLASS_CONSTANT_SUFFIX);
+        try (DebugContext.Scope s = debug.scope("DebugInfoType")) {
+            debug.log(DebugContext.INFO_LEVEL, "Register type %s ", type.getName());
+
+            TypeEntry typeEntry = createTypeEntry(type);
+            if (typeIndex.putIfAbsent(type, typeEntry) == null) {
+                /* Add class constant name to string table. */
+                if (typeEntry.getClassOffset() != -1) {
+                    // stringTable.uniqueDebugString(typeEntry.getTypeName() +
+                    // CLASS_CONSTANT_SUFFIX);
+                }
+                // typeEntry was added to the type index, now we need to process the type
+                debug.log(DebugContext.INFO_LEVEL, "Process type %s ", type.getName());
+                processTypeEntry(type, typeEntry);
             }
-            // typeEntry was added to the type index, now we need to process the type
-            processTypeEntry(type, typeEntry);
+            return typeEntry;
+        } catch (Throwable e) {
+            throw debug.handle(e);
         }
-        return typeEntry;
     }
 
     protected abstract TypeEntry createTypeEntry(SharedType type);
+
     protected abstract void processTypeEntry(SharedType type, TypeEntry typeEntry);
 
     protected void installHeaderTypeEntry() {
@@ -374,23 +476,27 @@ protected void installHeaderTypeEntry() {
         headerTypeEntry.addField(createSyntheticFieldEntry("hub", headerTypeEntry, hubType, hubOffset, referenceSize));
         if (ol.isIdentityHashFieldInObjectHeader()) {
             int idHashSize = ol.sizeInBytes(JavaKind.Int);
-            headerTypeEntry.addField(createSyntheticFieldEntry("idHash", headerTypeEntry, (SharedType) metaAccess.lookupJavaType(JavaKind.Int.toJavaClass()), ol.getObjectHeaderIdentityHashOffset(), idHashSize));
+            headerTypeEntry.addField(createSyntheticFieldEntry("idHash", headerTypeEntry, (SharedType) metaAccess.lookupJavaType(JavaKind.Int.toJavaClass()), ol.getObjectHeaderIdentityHashOffset(),
+                            idHashSize));
         }
     }
-    
+
     protected FieldEntry createSyntheticFieldEntry(String name, StructureTypeEntry ownerType, SharedType type, int offset, int size) {
-        //stringTable.uniqueDebugString(name);
+        // stringTable.uniqueDebugString(name);
         TypeEntry typeEntry = lookupTypeEntry(type);
+        debug.log("typename %s adding synthetic (public) field %s type %s size %d at offset 0x%x%n",
+                        ownerType.getTypeName(), name, typeEntry.getTypeName(), size, offset);
         return new FieldEntry(null, name, ownerType, typeEntry, size, offset, false, Modifier.PUBLIC);
     }
 
     protected FieldEntry createFieldEntry(FileEntry fileEntry, String name, StructureTypeEntry ownerType, SharedType type, int offset, int size, boolean isEmbedded, int modifier) {
-        //stringTable.uniqueDebugString(name);
+        // stringTable.uniqueDebugString(name);
         TypeEntry typeEntry = lookupTypeEntry(type);
+        debug.log("typename %s adding %s field %s type %s%s size %d at offset 0x%x%n",
+                        ownerType.getTypeName(), ownerType.getModifiersString(modifier), name, typeEntry.getTypeName(), (isEmbedded ? "(embedded)" : ""), size, offset);
         return new FieldEntry(fileEntry, name, ownerType, typeEntry, size, offset, isEmbedded, modifier);
     }
 
-
     public long getTypeSignature(String typeName) {
         return Digest.digestAsUUID(typeName).getLeastSignificantBits();
     }
@@ -422,7 +528,7 @@ public List<LocalEntry> getLocalEntries(SharedMethod method, int firstLocalSlot)
             // check if this is a local (slot is after last param slot)
             if (local != null && local.getSlot() >= firstLocalSlot) {
                 // we have a local with a known name, type and slot
-                String name = local.getName(); //stringTable.uniqueDebugString(l.getName());
+                String name = local.getName(); // stringTable.uniqueDebugString(l.getName());
                 SharedType type = (SharedType) local.getType().resolve(ownerType);
                 int slot = local.getSlot();
                 JavaKind storageKind = type.getStorageKind();
@@ -431,8 +537,8 @@ public List<LocalEntry> getLocalEntries(SharedMethod method, int firstLocalSlot)
                 TypeEntry typeEntry = lookupTypeEntry(type);
 
                 Optional<LocalEntry> existingEntry = localEntries.stream()
-                        .filter(le -> le.slot() == slot && le.type() == typeEntry && le.name().equals(name))
-                        .findFirst();
+                                .filter(le -> le.slot() == slot && le.type() == typeEntry && le.name().equals(name))
+                                .findFirst();
 
                 LocalEntry localEntry = new LocalEntry(name, typeEntry, storageKind, slot, line);
                 if (existingEntry.isEmpty()) {
@@ -456,17 +562,21 @@ public List<LocalEntry> getParamEntries(SharedMethod method, int line) {
         SharedType ownerType = (SharedType) method.getDeclaringClass();
         if (!method.isStatic()) {
             JavaKind kind = ownerType.getJavaKind();
-            JavaKind storageKind = ownerType.getStorageKind(); // isForeignWordType(ownerType, ownerType) ? JavaKind.Long : kind;
+            JavaKind storageKind = ownerType.getStorageKind(); // isForeignWordType(ownerType,
+                                                               // ownerType) ? JavaKind.Long : kind;
             assert kind == JavaKind.Object : "must be an object";
             paramInfos.add(new LocalEntry("this", lookupTypeEntry(ownerType), storageKind, slot, line));
             slot += kind.getSlotCount();
         }
         for (int i = 0; i < parameterCount; i++) {
             Local local = table == null ? null : table.getLocal(slot, 0);
-            String name = local != null ? local.getName() : "__" + i; // stringTable.uniqueDebugString(local != null ? local.getName() : "__" + i);
+            String name = local != null ? local.getName() : "__" + i; // stringTable.uniqueDebugString(local
+                                                                      // != null ? local.getName() :
+                                                                      // "__" + i);
             SharedType paramType = (SharedType) signature.getParameterType(i, null);
             JavaKind kind = paramType.getJavaKind();
-            JavaKind storageKind = paramType.getStorageKind(); // isForeignWordType(paramType, ownerType) ? JavaKind.Long : kind;
+            JavaKind storageKind = paramType.getStorageKind(); // isForeignWordType(paramType,
+                                                               // ownerType) ? JavaKind.Long : kind;
             paramInfos.add(new LocalEntry(name, lookupTypeEntry(paramType), storageKind, slot, line));
             slot += kind.getSlotCount();
         }
@@ -488,12 +598,10 @@ public boolean isDeopt(SharedMethod method, String methodName) {
         return methodName.endsWith(StableMethodNameFormatter.MULTI_METHOD_KEY_SEPARATOR);
     }
 
-
     public boolean isOverride(SharedMethod method) {
         return false;
     }
 
-
     public boolean isConstructor(SharedMethod method) {
         return method.isConstructor();
     }
@@ -502,7 +610,6 @@ public boolean isVirtual(SharedMethod method) {
         return false;
     }
 
-
     public int getVTableOffset(SharedMethod method) {
         return isVirtual(method) ? method.getVTableIndex() : -1;
     }
@@ -526,8 +633,8 @@ public MethodEntry lookupMethodEntry(SharedMethod method) {
             methodEntry.getOwnerType().addMethod(methodEntry);
         }
         return methodEntry;
-    }
 
+    }
 
     public CompiledMethodEntry lookupCompiledMethodEntry(MethodEntry methodEntry, SharedMethod method, CompilationResult compilation) {
         if (method == null) {
@@ -540,7 +647,6 @@ public CompiledMethodEntry lookupCompiledMethodEntry(MethodEntry methodEntry, Sh
         return compiledMethodEntry;
     }
 
-
     public TypeEntry lookupTypeEntry(SharedType type) {
         if (type == null) {
             // this must be the header type entry, as it is the only one with no underlying
@@ -553,7 +659,6 @@ public TypeEntry lookupTypeEntry(SharedType type) {
         return typeEntry;
     }
 
-
     public LoaderEntry lookupLoaderEntry(SharedType type) {
         if (type.isArray()) {
             type = (SharedType) type.getElementalType();
@@ -569,6 +674,8 @@ public LoaderEntry lookupLoaderEntry(String loaderName) {
     }
 
     public FileEntry lookupFileEntry(ResolvedJavaType type) {
+        // conjure up an appropriate, unique file name to keep tools happy
+        // even though we cannot find a corresponding source
         return lookupFileEntry(fullFilePathFromClassName(type));
     }
 
@@ -585,7 +692,7 @@ public FileEntry lookupFileEntry(Path fullFilePath) {
             return null;
         }
 
-        String fileName = fullFilePath.getFileName().toString(); //stringTable.uniqueDebugString(fullFilePath.getFileName().toString());
+        String fileName = fullFilePath.getFileName().toString(); // stringTable.uniqueDebugString(fullFilePath.getFileName().toString());
         Path dirPath = fullFilePath.getParent();
 
         DirEntry dirEntry = lookupDirEntry(dirPath);
@@ -605,7 +712,6 @@ public DirEntry lookupDirEntry(Path dirPath) {
         return dirIndex.computeIfAbsent(dirPath, DirEntry::new);
     }
 
-
     /* Other helper functions. */
     protected static ObjectLayout getObjectLayout() {
         return ConfigurationValues.getObjectLayout();
@@ -628,7 +734,6 @@ protected static Path fullFilePathFromClassName(ResolvedJavaType type) {
         return FileSystems.getDefault().getPath("", elements);
     }
 
-
     /**
      * Identify a Java type which is being used to model a foreign memory word or pointer type.
      *
@@ -651,7 +756,6 @@ protected boolean isForeignWordType(SharedType type) {
         return wordBaseType.isAssignableFrom(type);
     }
 
-
     private int findMarkOffset(SubstrateBackend.SubstrateMarkId markId, CompilationResult compilation) {
         for (CompilationResult.CodeMark mark : compilation.getMarks()) {
             if (mark.id.equals(markId)) {
@@ -672,7 +776,7 @@ private void updateInitialLocation(PrimaryRange primary, List<Range> locationInf
             // this is not a normal compiled method so give up
             return;
         }
-        // if there are any location info records then the first one will be for
+        // If there are any location info records then the first one will be for
         // a nop which follows the stack decrement, stack range check and pushes
         // of arguments into the stack frame.
         //
@@ -703,6 +807,7 @@ private void updateInitialLocation(PrimaryRange primary, List<Range> locationInf
         }
         // create a synthetic location record including details of passed arguments
         ParamLocationProducer locProducer = new ParamLocationProducer(method);
+        debug.log(DebugContext.DETAILED_LEVEL, "Add synthetic Location Info : %s (0, %d)", method.getName(), firstLocationOffset - 1);
         MethodEntry methodEntry = lookupMethodEntry(method);
         Range locationInfo = Range.createSubrange(primary, methodEntry, 0, firstLocationOffset, methodEntry.getLine(), primary, true, true);
 
@@ -713,6 +818,8 @@ private void updateInitialLocation(PrimaryRange primary, List<Range> locationInf
         // splitting at the extent point with the stack offsets adjusted in the new info
         if (locProducer.usesStack() && firstLocationOffset > stackDecrement) {
             Range splitLocationInfo = locationInfo.split(stackDecrement, compilation.getTotalFrameSize(), PRE_EXTEND_FRAME_SIZE);
+            debug.log(DebugContext.DETAILED_LEVEL, "Split synthetic Location Info : %s (0, %d) (%d, %d)", method.getName(),
+                            locationInfo.getLoOffset() - 1, locationInfo.getLoOffset(), locationInfo.getHiOffset() - 1);
             locationInfos.addFirst(splitLocationInfo);
         }
         locationInfos.addFirst(locationInfo);
@@ -720,21 +827,26 @@ private void updateInitialLocation(PrimaryRange primary, List<Range> locationInf
 
     private List<LocalValueEntry> initSyntheticInfoList(ParamLocationProducer locProducer, MethodEntry methodEntry) {
         ArrayList<LocalValueEntry> localValueInfos = new ArrayList<>();
+        // Create synthetic this param info
         if (methodEntry.getThisParam() != null) {
             JavaValue value = locProducer.thisLocation();
             LocalEntry thisParam = methodEntry.getThisParam();
+            debug.log(DebugContext.DETAILED_LEVEL, "local[0] %s type %s slot %d", thisParam.name(), thisParam.type().getTypeName(), thisParam.slot());
+            debug.log(DebugContext.DETAILED_LEVEL, "  =>  %s kind %s", value, thisParam.kind());
             localValueInfos.add(createLocalValueEntry(value, PRE_EXTEND_FRAME_SIZE, thisParam));
         }
+        // Iterate over all params and create synthetic param info for each
         int paramIdx = 0;
         for (LocalEntry param : methodEntry.getParams()) {
             JavaValue value = locProducer.paramLocation(paramIdx);
+            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 kind %s", value, param.kind());
             localValueInfos.add(createLocalValueEntry(value, PRE_EXTEND_FRAME_SIZE, param));
             paramIdx++;
         }
         return localValueInfos;
     }
 
-
     /**
      * Size in bytes of the frame at call entry before any stack extend. Essentially this accounts
      * for any automatically pushed return address whose presence depends upon the architecture.
@@ -810,7 +922,6 @@ public boolean usesStack() {
         }
     }
 
-
     // indices for arguments passed to SingleLevelVisitor::apply
     protected static final int CALLER_INFO = 0;
     protected static final int PARENT_NODE_TO_EMBED = 1;
@@ -831,6 +942,8 @@ private abstract class SingleLevelVisitor implements CompilationResultFrameTree.
 
         @Override
         public void apply(CompilationResultFrameTree.FrameNode node, Object... args) {
+            // Visits all nodes at this level and handles call nodes depth first (by default do
+            // nothing, just add the call nodes range info).
             if (node instanceof CompilationResultFrameTree.CallNode && skipPos(node.frame)) {
                 node.visitChildren(this, args);
             } else {
@@ -852,13 +965,21 @@ public void apply(CompilationResultFrameTree.FrameNode node, Object... args) {
                         // update last leaf and add new leaf to local info list
                         args[LAST_LEAF_INFO] = locationInfo;
                         locationInfos.add(locationInfo);
+                    } else {
+                        debug.log(DebugContext.DETAILED_LEVEL, "Merge leaf Location Info : %s depth %d (%d, %d) into (%d, %d)", lastLeaf.getMethodName(), lastLeaf.getDepth(), lastLeaf.getLoOffset(),
+                                        lastLeaf.getHiOffset() - 1, locationInfo.getLoOffset(), locationInfo.getHiOffset() - 1);
                     }
                 }
             }
         }
 
-        protected abstract void handleCallNode(CompilationResultFrameTree.CallNode callNode, CallRange locationInfo);
-        protected abstract void handleNodeToEmbed(CompilationResultFrameTree.CallNode nodeToEmbed, CompilationResultFrameTree.FrameNode node, CallRange callerInfo, Object... args);
+        protected void handleCallNode(CompilationResultFrameTree.CallNode callNode, CallRange locationInfo) {
+            // do nothing by default
+        }
+
+        protected void handleNodeToEmbed(CompilationResultFrameTree.CallNode nodeToEmbed, CompilationResultFrameTree.FrameNode node, CallRange callerInfo, Object... args) {
+            // do nothing by default
+        }
 
         public Range process(CompilationResultFrameTree.FrameNode node, CallRange callerInfo) {
             BytecodePosition pos;
@@ -887,6 +1008,9 @@ public Range process(CompilationResultFrameTree.FrameNode node, CallRange caller
 
             Range locationInfo = Range.createSubrange(primary, methodEntry, node.getStartPos(), node.getEndPos() + 1, line, callerInfo, isLeaf);
 
+            debug.log(DebugContext.DETAILED_LEVEL, "Create %s Location Info : %s depth %d (%d, %d)", isLeaf ? "leaf" : "call", method.getName(), locationInfo.getDepth(), locationInfo.getLoOffset(),
+                            locationInfo.getHiOffset() - 1);
+
             locationInfo.setLocalValueInfo(initLocalInfoList(pos, methodEntry, frameSize));
 
             return locationInfo;
@@ -897,29 +1021,87 @@ protected List<LocalValueEntry> initLocalInfoList(BytecodePosition pos, MethodEn
         List<LocalValueEntry> localInfos = new ArrayList<>();
 
         if (pos instanceof BytecodeFrame frame && frame.numLocals > 0) {
-            // deal with any inconsistencies in the layout of the frame locals
+            /*
+             * For each MethodEntry, initially local variables are loaded from the local variable
+             * table The local variable table is used here to get some additional information about
+             * locals and double-check the expected value kind
+             *
+             * A local variable that is not yet known to the method, will be added to the method
+             * with a synthesized name, the type according to the JavaKind (Object for all
+             * classes/array types) and the line of the current bytecode position
+             */
             LocalVariableTable lvt = pos.getMethod().getLocalVariableTable();
+            LineNumberTable lnt = pos.getMethod().getLineNumberTable();
+            int line = lnt == null ? 0 : lnt.getLineNumber(pos.getBCI());
+
+            // the owner type to resolve the local types against
+            SharedType ownerType = (SharedType) pos.getMethod().getDeclaringClass();
+
+            for (int slot = 0; slot < frame.numLocals; slot++) {
+                // Read locals from frame by slot - this might be an Illegal value
+                JavaValue value = frame.getLocalValue(slot);
+                JavaKind storageKind = frame.getLocalValueKind(slot);
+
+                if (value == Value.ILLEGAL) {
+                    /*
+                     * If we have an illegal value, also the storage kind must be Illegal. We don't
+                     * have any value, so we have to continue with the next slot.
+                     */
+                    assert storageKind == JavaKind.Illegal;
+                    continue;
+                }
 
-            if (lvt != null) {
-                SharedType ownerType = (SharedType) pos.getMethod().getDeclaringClass();
-                for (Local local : lvt.getLocalsAt(pos.getBCI())) {
-                    if (local != null) {
-                        // we have a local with a known name, type and slot
-                        String name = local.getName(); //stringTable.uniqueDebugString(l.getName());
-                        int slot = local.getSlot();
-                        SharedType type = (SharedType) local.getType().resolve(ownerType);
-                        TypeEntry typeEntry = lookupTypeEntry(type);
-
-                        JavaKind kind = type.getJavaKind();
-                        JavaValue value = (slot < frame.numLocals ? frame.getLocalValue(slot) : Value.ILLEGAL);
-                        JavaKind storageKind = (slot < frame.numLocals ? frame.getLocalValueKind(slot) : JavaKind.Illegal);
-
-                        if ((storageKind == kind) || isIntegralKindPromotion(storageKind, kind) ||
-                                (isForeignWordType(type, ownerType) && kind == JavaKind.Object && storageKind == JavaKind.Long)) {
-                            LocalEntry localEntry = method.getLocalEntry(name, slot, typeEntry);
-                            localInfos.add(createLocalValueEntry(value, frameSize, localEntry));
-                        }
-                    }
+                /*
+                 * We might not have a local variable table at all, which means we can only use the
+                 * frame local value. Even if there is a local variable table, there might not be a
+                 * local at this slot in the local variable table.
+                 */
+                Local local = lvt == null ? null : lvt.getLocal(slot, pos.getBCI());
+
+                String name;
+                SharedType type;
+                if (local == null) {
+                    /*
+                     * We don't have a corresponding local in the local variable table. Collect some
+                     * usable information for this local from the frame local kind.
+                     */
+                    name = storageKind.getTypeChar() + "_" + slot;
+                    Class<?> clazz = storageKind.isObject() ? Object.class : storageKind.toJavaClass();
+                    type = (SharedType) metaAccess.lookupJavaType(clazz);
+                } else {
+                    /*
+                     * Use the information from the local variable table. This allows us to match
+                     * the local variables with the ones we already read for the method entry. In
+                     * this case the information from the frame local kind is just used to
+                     * double-check the type kind from the local variable table.
+                     */
+                    name = local.getName();
+                    type = (SharedType) local.getType().resolve(ownerType);
+                }
+
+                TypeEntry typeEntry = lookupTypeEntry(type);
+                JavaKind kind = type.getJavaKind();
+
+                debug.log(DebugContext.DETAILED_LEVEL, "local %s type %s slot %d", name, typeEntry.getTypeName(), slot);
+                debug.log(DebugContext.DETAILED_LEVEL, "  =>  %s kind %s", value, storageKind);
+
+                // Double-check the kind from the frame local value with the kind from the local
+                // variable table.
+                if (storageKind == kind || isIntegralKindPromotion(storageKind, kind) || (isForeignWordType(type, ownerType) && kind == JavaKind.Object && storageKind == JavaKind.Long)) {
+                    /*
+                     * Lookup a LocalEntry from the MethodEntry. If the LocalEntry was already read
+                     * upfront from the local variable table, the LocalEntry already exists.
+                     */
+                    LocalEntry localEntry = method.lookupLocalEntry(name, slot, typeEntry, kind, line);
+                    localInfos.add(createLocalValueEntry(value, frameSize, localEntry));
+                } else {
+                    debug.log(DebugContext.DETAILED_LEVEL, "  value kind incompatible with var kind %s!", kind);
+                }
+
+                // No need to check the next slot if the current value needs two slots (next slot
+                // contains an illegal value which is skipped anyway).
+                if (storageKind.needsTwoSlots()) {
+                    slot++;
                 }
             }
         }
@@ -963,30 +1145,22 @@ private LocalValueEntry createLocalValueEntry(JavaValue value, int frameSize, Lo
         return new LocalValueEntry(regIndex, stackSlot, heapOffset, constant, localKind, local);
     }
 
-
     public abstract long objectOffset(JavaConstant constant);
 
     private static boolean isIntegralKindPromotion(JavaKind promoted, JavaKind original) {
         return (promoted == JavaKind.Int &&
-                (original == JavaKind.Boolean || original == JavaKind.Byte || original == JavaKind.Short || original == JavaKind.Char));
+                        (original == JavaKind.Boolean || original == JavaKind.Byte || original == JavaKind.Short || original == JavaKind.Char));
     }
 
+    // Top level visitor is just a single level visitor that starts at the primary range/root node
     private class TopLevelVisitor extends SingleLevelVisitor {
         TopLevelVisitor(List<Range> locationInfos, int frameSize, PrimaryRange primary) {
             super(locationInfos, frameSize, primary);
         }
-
-        @Override
-        protected void handleCallNode(CompilationResultFrameTree.CallNode callNode, CallRange locationInfo) {
-            // nothing to do here
-        }
-
-        @Override
-        protected void handleNodeToEmbed(CompilationResultFrameTree.CallNode nodeToEmbed, CompilationResultFrameTree.FrameNode node, CallRange callerInfo, Object... args) {
-            // nothing to do here
-        }
     }
 
+    // Multi level visitor starts at the primary range and defines behavior for stepping into call
+    // nodes
     public class MultiLevelVisitor extends SingleLevelVisitor {
         MultiLevelVisitor(List<Range> locationInfos, int frameSize, PrimaryRange primary) {
             super(locationInfos, frameSize, primary);
@@ -1041,17 +1215,18 @@ private static boolean hasChildren(CompilationResultFrameTree.CallNode callNode)
     }
 
     /**
-     * Create a location info record for the initial range associated with a parent call node
-     * whose position and start are defined by that call node and whose end is determined by the
-     * first child of the call node.
+     * Create a location info record for the initial range associated with a parent call node whose
+     * position and start are defined by that call node and whose end is determined by the first
+     * child of the call node.
      *
-     * @param parentToEmbed a parent call node which has already been processed to create the
-     *            caller location info
+     * @param parentToEmbed a parent call node which has already been processed to create the caller
+     *            location info
      * @param firstChild the first child of the call node
      * @param callerLocation the location info created to represent the range for the call
      * @return a location info to be embedded as the first child range of the caller location.
      */
-    private Range createEmbeddedParentLocationInfo(PrimaryRange primary, CompilationResultFrameTree.CallNode parentToEmbed, CompilationResultFrameTree.FrameNode firstChild, CallRange callerLocation, int frameSize) {
+    private Range createEmbeddedParentLocationInfo(PrimaryRange primary, CompilationResultFrameTree.CallNode parentToEmbed, CompilationResultFrameTree.FrameNode firstChild, CallRange callerLocation,
+                    int frameSize) {
         BytecodePosition pos = parentToEmbed.frame;
         int startPos = parentToEmbed.getStartPos();
         int endPos = (firstChild != null ? firstChild.getStartPos() : parentToEmbed.getEndPos() + 1);
@@ -1062,11 +1237,14 @@ private Range createEmbeddedParentLocationInfo(PrimaryRange primary, Compilation
         LineNumberTable lineNumberTable = method.getLineNumberTable();
         int line = lineNumberTable == null ? -1 : lineNumberTable.getLineNumber(pos.getBCI());
 
-        Range newRange = Range.createSubrange(primary, methodEntry, startPos, endPos, line, callerLocation, true);
+        Range locationInfo = Range.createSubrange(primary, methodEntry, startPos, endPos, line, callerLocation, true);
+
+        debug.log(DebugContext.DETAILED_LEVEL, "Embed leaf Location Info : %s depth %d (%d, %d)", locationInfo.getMethodName(), locationInfo.getDepth(), locationInfo.getLoOffset(),
+                        locationInfo.getHiOffset() - 1);
 
-        newRange.setLocalValueInfo(initLocalInfoList(pos, methodEntry, frameSize));
+        locationInfo.setLocalValueInfo(initLocalInfoList(pos, methodEntry, frameSize));
 
-        return newRange;
+        return locationInfo;
     }
 
     private static boolean isBadLeaf(CompilationResultFrameTree.FrameNode node, CallRange callerLocation) {
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
index 94c5c169ccfc..e0f2924990aa 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
@@ -1,5 +1,21 @@
 package com.oracle.svm.core.debug;
 
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+
+import org.graalvm.nativeimage.ImageSingletons;
+import org.graalvm.nativeimage.UnmanagedMemory;
+import org.graalvm.nativeimage.c.struct.RawField;
+import org.graalvm.nativeimage.c.struct.RawStructure;
+import org.graalvm.nativeimage.c.struct.SizeOf;
+import org.graalvm.nativeimage.c.type.CCharPointer;
+import org.graalvm.nativeimage.impl.UnmanagedMemorySupport;
+import org.graalvm.word.Pointer;
+
 import com.oracle.objectfile.BasicNobitsSectionImpl;
 import com.oracle.objectfile.ObjectFile;
 import com.oracle.objectfile.SectionName;
@@ -13,30 +29,15 @@
 import com.oracle.svm.core.os.VirtualMemoryProvider;
 import com.oracle.svm.core.thread.VMOperation;
 import com.oracle.svm.core.util.VMError;
+
 import jdk.graal.compiler.code.CompilationResult;
 import jdk.graal.compiler.core.common.NumUtil;
 import jdk.graal.compiler.debug.DebugContext;
 import jdk.vm.ci.meta.MetaAccessProvider;
-import org.graalvm.nativeimage.ImageSingletons;
-import org.graalvm.nativeimage.UnmanagedMemory;
-import org.graalvm.nativeimage.c.struct.RawField;
-import org.graalvm.nativeimage.c.struct.RawStructure;
-import org.graalvm.nativeimage.c.struct.SizeOf;
-import org.graalvm.nativeimage.c.type.CCharPointer;
-import org.graalvm.nativeimage.impl.UnmanagedMemorySupport;
-import org.graalvm.word.Pointer;
-
-import java.io.IOException;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.nio.file.Paths;
-import java.nio.file.StandardOpenOption;
-import java.util.ArrayList;
 
 public class SubstrateDebugInfoInstaller implements InstalledCodeObserver {
 
     private final SubstrateDebugInfoProvider substrateDebugInfoProvider;
-    private final DebugContext debugContext;
     private final ObjectFile objectFile;
     private final ArrayList<ObjectFile.Element> sortedObjectFileElements;
     private final int debugInfoSize;
@@ -61,9 +62,9 @@ public InstalledCodeObserver create(DebugContext debugContext, SharedMethod meth
         }
     }
 
-    private SubstrateDebugInfoInstaller(DebugContext debugContext, SharedMethod method, CompilationResult compilation, MetaAccessProvider metaAccess, RuntimeConfiguration runtimeConfiguration, Pointer code, int codeSize) {
-        this.debugContext = debugContext;
-        substrateDebugInfoProvider = new SubstrateDebugInfoProvider(method, compilation, runtimeConfiguration, metaAccess, code.rawValue());
+    private SubstrateDebugInfoInstaller(DebugContext debugContext, SharedMethod method, CompilationResult compilation, MetaAccessProvider metaAccess, RuntimeConfiguration runtimeConfiguration,
+                    Pointer code, int codeSize) {
+        substrateDebugInfoProvider = new SubstrateDebugInfoProvider(debugContext, method, compilation, runtimeConfiguration, metaAccess, code.rawValue());
 
         int pageSize = NumUtil.safeToInt(ImageSingletons.lookup(VirtualMemoryProvider.class).getGranularity().rawValue());
         objectFile = ObjectFile.createRuntimeDebugInfo(pageSize);
@@ -73,15 +74,15 @@ private SubstrateDebugInfoInstaller(DebugContext debugContext, SharedMethod meth
         debugInfoSize = objectFile.bake(sortedObjectFileElements);
 
         if (debugContext.isLogEnabled()) {
-            dumpObjectFile();
+            dumpObjectFile(debugContext);
         }
     }
 
-    private void dumpObjectFile() {
+    private void dumpObjectFile(DebugContext debugContext) {
         StringBuilder sb = new StringBuilder(substrateDebugInfoProvider.getCompilationName()).append(".debug");
         try (FileChannel dumpFile = FileChannel.open(Paths.get(sb.toString()),
-                StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING,
-                StandardOpenOption.CREATE)) {
+                        StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING,
+                        StandardOpenOption.CREATE)) {
             ByteBuffer buffer = dumpFile.map(FileChannel.MapMode.READ_WRITE, 0, debugInfoSize);
             objectFile.writeBuffer(sortedObjectFileElements, buffer);
         } catch (IOException e) {
@@ -197,12 +198,12 @@ private NonmovableArray<Byte> writeDebugInfoData() {
 
     private static String toString(Handle handle) {
         return "DebugInfoHandle(handle = 0x" + Long.toHexString(handle.getRawHandle().rawValue()) +
-                ", address = 0x" +
-                Long.toHexString(NonmovableArrays.addressOf(handle.getDebugInfoData(), 0).rawValue()) +
-                ", size = " +
-                NonmovableArrays.lengthOf(handle.getDebugInfoData()) +
-                ", handleState = " +
-                handle.getState() +
-                ")";
+                        ", address = 0x" +
+                        Long.toHexString(NonmovableArrays.addressOf(handle.getDebugInfoData(), 0).rawValue()) +
+                        ", size = " +
+                        NonmovableArrays.lengthOf(handle.getDebugInfoData()) +
+                        ", handleState = " +
+                        handle.getState() +
+                        ")";
     }
 }
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
index e375e3fbdf0a..2b04eff75881 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
@@ -24,6 +24,13 @@
  */
 package com.oracle.svm.core.debug;
 
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
+import java.util.stream.Stream;
+
+import org.graalvm.collections.Pair;
+import org.graalvm.nativeimage.ImageSingletons;
+
 import com.oracle.objectfile.debugentry.ArrayTypeEntry;
 import com.oracle.objectfile.debugentry.ClassEntry;
 import com.oracle.objectfile.debugentry.FileEntry;
@@ -35,18 +42,13 @@
 import com.oracle.svm.core.graal.meta.RuntimeConfiguration;
 import com.oracle.svm.core.meta.SharedMethod;
 import com.oracle.svm.core.meta.SharedType;
+
 import jdk.graal.compiler.code.CompilationResult;
+import jdk.graal.compiler.debug.DebugContext;
 import jdk.vm.ci.meta.JavaConstant;
 import jdk.vm.ci.meta.JavaKind;
 import jdk.vm.ci.meta.MetaAccessProvider;
 import jdk.vm.ci.meta.ResolvedJavaType;
-import org.graalvm.collections.Pair;
-import org.graalvm.nativeimage.ImageSingletons;
-
-import java.nio.file.FileSystems;
-import java.nio.file.Path;
-import java.util.stream.Stream;
-
 
 public class SubstrateDebugInfoProvider extends SharedDebugInfoProvider {
 
@@ -54,9 +56,9 @@ public class SubstrateDebugInfoProvider extends SharedDebugInfoProvider {
     private final CompilationResult compilation;
     private final long codeAddress;
 
-
-    public SubstrateDebugInfoProvider(SharedMethod method, CompilationResult compilation, RuntimeConfiguration runtimeConfiguration, MetaAccessProvider metaAccess, long codeAddress) {
-        super(runtimeConfiguration, metaAccess, ImageSingletons.lookup(SubstrateBFDNameProvider.class), SubstrateOptions.getRuntimeSourceDestDir());
+    public SubstrateDebugInfoProvider(DebugContext debug, SharedMethod method, CompilationResult compilation, RuntimeConfiguration runtimeConfiguration, MetaAccessProvider metaAccess,
+                    long codeAddress) {
+        super(debug, runtimeConfiguration, metaAccess, ImageSingletons.lookup(SubstrateBFDNameProvider.class), SubstrateOptions.getRuntimeSourceDestDir());
         this.method = method;
         this.compilation = compilation;
         this.codeAddress = codeAddress;
@@ -129,10 +131,10 @@ private int getTypeSize(SharedType type) {
             return 0;
         }
     }
-    
+
     @Override
     protected TypeEntry createTypeEntry(SharedType type) {
-        String typeName = type.toJavaName(); //stringTable.uniqueDebugString(idType.toJavaName());
+        String typeName = type.toJavaName(); // stringTable.uniqueDebugString(idType.toJavaName());
         LoaderEntry loaderEntry = lookupLoaderEntry(type);
         int size = getTypeSize(type);
         long classOffset = -1;
@@ -146,11 +148,10 @@ protected TypeEntry createTypeEntry(SharedType type) {
         } else {
             // otherwise we have a structured type
             long layoutTypeSignature = getTypeSignature(LAYOUT_PREFIX + typeName + loaderName);
-            long compressedLayoutTypeSignature = useHeapBase ? getTypeSignature(INDIRECT_PREFIX + LAYOUT_PREFIX + typeName + loaderName) : layoutTypeSignature;
             if (type.isArray()) {
                 TypeEntry elementTypeEntry = lookupTypeEntry((SharedType) type.getComponentType());
                 return new ArrayTypeEntry(typeName, size, classOffset, typeSignature, compressedTypeSignature,
-                        layoutTypeSignature, compressedLayoutTypeSignature, elementTypeEntry, loaderEntry);
+                                layoutTypeSignature, elementTypeEntry, loaderEntry);
             } else {
                 // otherwise this is a class entry
                 ClassEntry superClass = type.getSuperclass() == null ? null : (ClassEntry) lookupTypeEntry((SharedType) type.getSuperclass());
@@ -158,10 +159,10 @@ protected TypeEntry createTypeEntry(SharedType type) {
                 if (isForeignWordType(type)) {
                     // we just need the correct type signatures here
                     return new ClassEntry(typeName, size, classOffset, typeSignature, typeSignature, layoutTypeSignature,
-                            layoutTypeSignature, superClass, fileEntry, loaderEntry);
+                                    superClass, fileEntry, loaderEntry);
                 } else {
                     return new ClassEntry(typeName, size, classOffset, typeSignature, compressedTypeSignature,
-                            layoutTypeSignature, compressedLayoutTypeSignature, superClass, fileEntry, loaderEntry);
+                                    layoutTypeSignature, superClass, fileEntry, loaderEntry);
                 }
             }
         }
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 3c6e4a94367f..ef3dba0bc163 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
@@ -25,15 +25,26 @@
  */
 package com.oracle.svm.hosted.image;
 
+import static com.oracle.svm.hosted.c.info.AccessorInfo.AccessorKind.ADDRESS;
+import static com.oracle.svm.hosted.c.info.AccessorInfo.AccessorKind.GETTER;
+import static com.oracle.svm.hosted.c.info.AccessorInfo.AccessorKind.SETTER;
+
 import java.lang.reflect.Modifier;
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Comparator;
+import java.util.List;
 import java.util.Set;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+import org.graalvm.collections.Pair;
+import org.graalvm.nativeimage.ImageSingletons;
+import org.graalvm.nativeimage.c.struct.CPointerTo;
+import org.graalvm.nativeimage.c.struct.RawPointerTo;
+
+import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider;
 import com.oracle.graal.pointsto.infrastructure.WrappedJavaMethod;
 import com.oracle.graal.pointsto.infrastructure.WrappedJavaType;
 import com.oracle.objectfile.debugentry.ArrayTypeEntry;
@@ -45,6 +56,7 @@
 import com.oracle.objectfile.debugentry.ForeignTypeEntry;
 import com.oracle.objectfile.debugentry.InterfaceClassEntry;
 import com.oracle.objectfile.debugentry.LoaderEntry;
+import com.oracle.objectfile.debugentry.LocalEntry;
 import com.oracle.objectfile.debugentry.MethodEntry;
 import com.oracle.objectfile.debugentry.PrimitiveTypeEntry;
 import com.oracle.objectfile.debugentry.StructureTypeEntry;
@@ -55,6 +67,7 @@
 import com.oracle.svm.core.UniqueShortNameProvider;
 import com.oracle.svm.core.debug.SharedDebugInfoProvider;
 import com.oracle.svm.core.graal.meta.RuntimeConfiguration;
+import com.oracle.svm.core.image.ImageHeapPartition;
 import com.oracle.svm.core.meta.SharedMethod;
 import com.oracle.svm.core.meta.SharedType;
 import com.oracle.svm.core.util.VMError;
@@ -62,6 +75,8 @@
 import com.oracle.svm.hosted.c.info.AccessorInfo;
 import com.oracle.svm.hosted.c.info.ElementInfo;
 import com.oracle.svm.hosted.c.info.PointerToInfo;
+import com.oracle.svm.hosted.c.info.PropertyInfo;
+import com.oracle.svm.hosted.c.info.RawStructureInfo;
 import com.oracle.svm.hosted.c.info.SizableInfo;
 import com.oracle.svm.hosted.c.info.StructFieldInfo;
 import com.oracle.svm.hosted.c.info.StructInfo;
@@ -77,6 +92,8 @@
 import com.oracle.svm.hosted.substitute.InjectedFieldsType;
 import com.oracle.svm.hosted.substitute.SubstitutionMethod;
 import com.oracle.svm.hosted.substitute.SubstitutionType;
+import com.oracle.svm.util.ClassUtil;
+
 import jdk.graal.compiler.code.CompilationResult;
 import jdk.graal.compiler.debug.DebugContext;
 import jdk.vm.ci.meta.JavaConstant;
@@ -84,14 +101,6 @@
 import jdk.vm.ci.meta.ResolvedJavaField;
 import jdk.vm.ci.meta.ResolvedJavaMethod;
 import jdk.vm.ci.meta.ResolvedJavaType;
-import org.graalvm.collections.Pair;
-import org.graalvm.nativeimage.ImageSingletons;
-import org.graalvm.nativeimage.c.struct.CPointerTo;
-import org.graalvm.nativeimage.c.struct.RawPointerTo;
-
-import static com.oracle.svm.hosted.c.info.AccessorInfo.AccessorKind.ADDRESS;
-import static com.oracle.svm.hosted.c.info.AccessorInfo.AccessorKind.GETTER;
-import static com.oracle.svm.hosted.c.info.AccessorInfo.AccessorKind.SETTER;
 
 /**
  * Implementation of the DebugInfoProvider API interface that allows type, code and heap data info
@@ -102,20 +111,16 @@ class NativeImageDebugInfoProvider extends SharedDebugInfoProvider {
     protected final NativeImageCodeCache codeCache;
     protected final NativeLibraries nativeLibs;
 
-
     protected final int primitiveStartOffset;
     protected final int referenceStartOffset;
-
-    private final DebugContext debugContext;
     private final Set<HostedMethod> allOverrides;
 
-    NativeImageDebugInfoProvider(DebugContext debugContext, NativeImageCodeCache codeCache, NativeImageHeap heap, NativeLibraries nativeLibs, HostedMetaAccess metaAccess,
+    NativeImageDebugInfoProvider(DebugContext debug, NativeImageCodeCache codeCache, NativeImageHeap heap, NativeLibraries nativeLibs, HostedMetaAccess metaAccess,
                     RuntimeConfiguration runtimeConfiguration) {
-        super(runtimeConfiguration, metaAccess, UniqueShortNameProvider.singleton(), SubstrateOptions.getDebugInfoSourceCacheRoot());
+        super(debug, runtimeConfiguration, metaAccess, UniqueShortNameProvider.singleton(), SubstrateOptions.getDebugInfoSourceCacheRoot());
         this.heap = heap;
         this.codeCache = codeCache;
         this.nativeLibs = nativeLibs;
-        this.debugContext = debugContext;
 
         /* Offsets need to be adjusted relative to the heap base plus partition-specific offset. */
         NativeImageHeap.ObjectInfo primitiveFields = heap.getObjectInfo(StaticFieldsSupport.getStaticPrimitiveFields());
@@ -133,8 +138,8 @@ class NativeImageDebugInfoProvider extends SharedDebugInfoProvider {
 
     private static ResolvedJavaType getOriginal(ResolvedJavaType type) {
         /*
-         * unwrap then traverse through substitutions to the original. We don't want to
-         * get the original type of LambdaSubstitutionType to keep the stable name
+         * unwrap then traverse through substitutions to the original. We don't want to get the
+         * original type of LambdaSubstitutionType to keep the stable name
          */
         while (type instanceof WrappedJavaType wrappedJavaType) {
             type = wrappedJavaType.getWrapped();
@@ -175,6 +180,27 @@ private static ResolvedJavaMethod getAnnotatedOrOriginal(ResolvedJavaMethod meth
         return method;
     }
 
+    @Override
+    protected void handleDataInfo(Object data) {
+        // log ObjectInfo data
+        if (debug.isLogEnabled(DebugContext.INFO_LEVEL) && data instanceof NativeImageHeap.ObjectInfo objectInfo) {
+            try (DebugContext.Scope s = debug.scope("DebugDataInfo")) {
+                long offset = objectInfo.getOffset();
+                long size = objectInfo.getSize();
+                String typeName = objectInfo.getClazz().toJavaName();
+                ImageHeapPartition partition = objectInfo.getPartition();
+                String partitionName = partition.getName() + "{" + partition.getSize() + "}@" + partition.getStartOffset();
+                String provenance = objectInfo.toString();
+
+                debug.log(DebugContext.INFO_LEVEL, "Data: offset 0x%x size 0x%x type %s partition %s provenance %s ", offset, size, typeName, partitionName, provenance);
+            } catch (Throwable e) {
+                throw debug.handle(e);
+            }
+        } else {
+            super.handleDataInfo(data);
+        }
+    }
+
     private static int elementSize(ElementInfo elementInfo) {
         if (!(elementInfo instanceof SizableInfo) || elementInfo instanceof StructInfo structInfo && structInfo.isIncomplete()) {
             return 0;
@@ -182,6 +208,18 @@ private static int elementSize(ElementInfo elementInfo) {
         return ((SizableInfo) elementInfo).getSizeInBytes();
     }
 
+    private static String elementName(ElementInfo elementInfo) {
+        if (elementInfo == null) {
+            return "";
+        } else {
+            return elementInfo.getName();
+        }
+    }
+
+    private static SizableInfo.ElementKind elementKind(SizableInfo sizableInfo) {
+        return sizableInfo.getKind();
+    }
+
     private static String typedefName(ElementInfo elementInfo) {
         String name = null;
         if (elementInfo != null) {
@@ -202,7 +240,6 @@ private boolean isForeignPointerType(HostedType type) {
         return nativeLibs.isPointerBase(type.getWrapped());
     }
 
-
     /*
      * Foreign pointer types have associated element info which describes the target type. The
      * following helpers support querying of and access to this element info.
@@ -343,10 +380,35 @@ protected void processTypeEntry(SharedType type, TypeEntry typeEntry) {
     private void processMethods(HostedType type, ClassEntry classEntry) {
         for (HostedMethod method : type.getAllDeclaredMethods()) {
             MethodEntry methodEntry = lookupMethodEntry(method);
+            debug.log("typename %s adding %s method %s %s(%s)%n",
+                            classEntry.getTypeName(), methodEntry.getModifiersString(), methodEntry.getValueType().getTypeName(), methodEntry.getMethodName(),
+                            formatParams(methodEntry.getThisParam(), methodEntry.getParams()));
             classEntry.addMethod(methodEntry);
         }
     }
 
+    private static String formatParams(LocalEntry thisParam, List<LocalEntry> paramInfo) {
+        if (paramInfo.isEmpty()) {
+            return "";
+        }
+        StringBuilder builder = new StringBuilder();
+        if (thisParam != null) {
+            builder.append(thisParam.type().getTypeName());
+            builder.append(' ');
+            builder.append(thisParam.name());
+        }
+        for (LocalEntry param : paramInfo) {
+            if (!builder.isEmpty()) {
+                builder.append(", ");
+            }
+            builder.append(param.type().getTypeName());
+            builder.append(' ');
+            builder.append(param.name());
+        }
+
+        return builder.toString();
+    }
+
     @Override
     public String getMethodName(SharedMethod method) {
         String name;
@@ -386,9 +448,11 @@ private void processInterfaces(HostedType type, ClassEntry classEntry) {
         for (HostedType interfaceType : type.getInterfaces()) {
             TypeEntry entry = lookupTypeEntry(interfaceType);
             if (entry instanceof InterfaceClassEntry interfaceClassEntry) {
+                debug.log("typename %s adding interface %s%n", classEntry.getTypeName(), interfaceType.toJavaName());
                 interfaceClassEntry.addImplementor(classEntry);
             } else {
-                // don't model the interface relationship when the Java interface actually identifies a
+                // don't model the interface relationship when the Java interface actually
+                // identifies a
                 // foreign type
                 assert entry instanceof ForeignTypeEntry && classEntry instanceof ForeignTypeEntry;
             }
@@ -408,13 +472,14 @@ private void processForeignTypeFields(HostedType type, ForeignTypeEntry foreignT
         ElementInfo elementInfo = nativeLibs.findElementInfo(type);
         if (elementInfo instanceof StructInfo) {
             elementInfo.getChildren().stream().filter(NativeImageDebugInfoProvider::isTypedField)
-                    .map(elt -> ((StructFieldInfo) elt))
-                    .sorted(Comparator.comparingInt(field -> field.getOffsetInfo().getProperty()))
-                    .forEach(field -> {
-                        HostedType fieldType = getFieldType(field);
-                        FieldEntry fieldEntry = createFieldEntry(foreignTypeEntry.getFileEntry(), field.getName(), foreignTypeEntry, fieldType, field.getOffsetInfo().getProperty(), field.getSizeInBytes(), fieldTypeIsEmbedded(field), 0);
-                        foreignTypeEntry.addField(fieldEntry);
-                    });
+                            .map(elt -> ((StructFieldInfo) elt))
+                            .sorted(Comparator.comparingInt(field -> field.getOffsetInfo().getProperty()))
+                            .forEach(field -> {
+                                HostedType fieldType = getFieldType(field);
+                                FieldEntry fieldEntry = createFieldEntry(foreignTypeEntry.getFileEntry(), field.getName(), foreignTypeEntry, fieldType, field.getOffsetInfo().getProperty(),
+                                                field.getSizeInBytes(), fieldTypeIsEmbedded(field), 0);
+                                foreignTypeEntry.addField(fieldEntry);
+                            });
         }
     }
 
@@ -438,8 +503,8 @@ private FieldEntry createFieldEntry(HostedField field, StructureTypeEntry ownerT
         int modifiers = field.getModifiers();
         int offset = field.getLocation();
         /*
-         * For static fields we need to add in the appropriate partition base but only if we
-         * have a real offset
+         * For static fields we need to add in the appropriate partition base but only if we have a
+         * real offset
          */
         if (Modifier.isStatic(modifiers) && offset >= 0) {
             if (storageKind.isPrimitive()) {
@@ -457,7 +522,7 @@ protected TypeEntry createTypeEntry(SharedType type) {
         assert type instanceof HostedType;
         HostedType hostedType = (HostedType) type;
 
-        String typeName = hostedType.toJavaName(); //stringTable.uniqueDebugString(idType.toJavaName());
+        String typeName = hostedType.toJavaName(); // stringTable.uniqueDebugString(idType.toJavaName());
         int size = getTypeSize(hostedType);
         long classOffset = getClassOffset(hostedType);
         LoaderEntry loaderEntry = lookupLoaderEntry(hostedType);
@@ -467,26 +532,27 @@ protected TypeEntry createTypeEntry(SharedType type) {
 
         if (hostedType.isPrimitive()) {
             JavaKind kind = hostedType.getStorageKind();
+            debug.log("typename %s (%d bits)%n", typeName, kind == JavaKind.Void ? 0 : kind.getBitCount());
             return new PrimitiveTypeEntry(typeName, size, classOffset, typeSignature, kind);
         } else {
             // otherwise we have a structured type
             long layoutTypeSignature = getTypeSignature(LAYOUT_PREFIX + typeName + loaderName);
-            long compressedLayoutTypeSignature = useHeapBase ? getTypeSignature(INDIRECT_PREFIX + LAYOUT_PREFIX + typeName + loaderName) : layoutTypeSignature;
             if (hostedType.isArray()) {
                 TypeEntry elementTypeEntry = lookupTypeEntry(hostedType.getComponentType());
-                // int baseSize = getObjectLayout().getArrayBaseOffset(hostedType.getComponentType().getStorageKind());
-                // int lengthOffset = getObjectLayout().getArrayLengthOffset();
+                debug.log("typename %s element type %s base size %d length offset %d%n", typeName, elementTypeEntry.getTypeName(),
+                                getObjectLayout().getArrayBaseOffset(hostedType.getComponentType().getStorageKind()), getObjectLayout().getArrayLengthOffset());
                 return new ArrayTypeEntry(typeName, size, classOffset, typeSignature, compressedTypeSignature,
-                        layoutTypeSignature, compressedLayoutTypeSignature, elementTypeEntry, loaderEntry);
+                                layoutTypeSignature, elementTypeEntry, loaderEntry);
             } else {
                 // otherwise this is a class entry
                 ClassEntry superClass = hostedType.getSuperclass() == null ? null : (ClassEntry) lookupTypeEntry(hostedType.getSuperclass());
+                debug.log("typename %s adding super %s%n", typeName, superClass != null ? superClass.getTypeName() : "");
                 FileEntry fileEntry = lookupFileEntry(hostedType);
                 if (isForeignWordType(hostedType)) {
                     ElementInfo elementInfo = nativeLibs.findElementInfo(hostedType);
                     SizableInfo.ElementKind elementKind = elementInfo instanceof SizableInfo ? ((SizableInfo) elementInfo).getKind() : null;
                     size = elementSize(elementInfo);
-                    String typedefName = typedefName(elementInfo); //stringTable.uniqueDebugString(typedefName(elementInfo));
+                    String typedefName = typedefName(elementInfo); // stringTable.uniqueDebugString(typedefName(elementInfo));
                     boolean isWord = !isForeignPointerType(hostedType);
                     boolean isStruct = elementInfo instanceof StructInfo;
                     boolean isPointer = elementKind == SizableInfo.ElementKind.POINTER;
@@ -507,7 +573,8 @@ protected TypeEntry createTypeEntry(SharedType type) {
 
                     TypeEntry pointerToEntry = null;
                     if (isPointer) {
-                        // any target type for the pointer will be defined by a CPointerTo or RawPointerTo
+                        // any target type for the pointer will be defined by a CPointerTo or
+                        // RawPointerTo
                         // annotation
                         CPointerTo cPointerTo = type.getAnnotation(CPointerTo.class);
                         if (cPointerTo != null) {
@@ -519,6 +586,12 @@ protected TypeEntry createTypeEntry(SharedType type) {
                             HostedType pointerTo = heap.hMetaAccess.lookupJavaType(rawPointerTo.value());
                             pointerToEntry = lookupTypeEntry(pointerTo);
                         }
+
+                        if (pointerToEntry != null) {
+                            debug.log("foreign type %s referent %s ", typeName, pointerToEntry.getTypeName());
+                        } else {
+                            debug.log("foreign type %s", typeName);
+                        }
                     }
 
                     if (!isStruct && !isWord && !isInteger && !isFloat) {
@@ -527,22 +600,30 @@ protected TypeEntry createTypeEntry(SharedType type) {
                             pointerToEntry = lookupTypeEntry(voidType);
                         }
                         if (pointerToEntry != null) {
+                            /*
+                             * Setting the layout type to the type we point to reuses an available
+                             * type unit, so we do not have to write are separate type unit.
+                             */
                             layoutTypeSignature = pointerToEntry.getTypeSignature();
                         }
                     }
 
+                    if (debug.isLogEnabled()) {
+                        logForeignTypeInfo(hostedType);
+                    }
+
                     return new ForeignTypeEntry(typeName, size, classOffset, typeSignature, layoutTypeSignature,
-                            superClass, fileEntry, loaderEntry, typedefName, parentEntry, pointerToEntry, isWord,
-                            isStruct, isPointer, isInteger, isSigned, isFloat);
+                                    superClass, fileEntry, loaderEntry, typedefName, parentEntry, pointerToEntry, isWord,
+                                    isStruct, isPointer, isInteger, isSigned, isFloat);
                 } else if (hostedType.isEnum()) {
                     return new EnumClassEntry(typeName, size, classOffset, typeSignature, compressedTypeSignature,
-                            layoutTypeSignature, compressedLayoutTypeSignature, superClass, fileEntry, loaderEntry);
+                                    layoutTypeSignature, superClass, fileEntry, loaderEntry);
                 } else if (hostedType.isInstanceClass()) {
                     return new ClassEntry(typeName, size, classOffset, typeSignature, compressedTypeSignature,
-                            layoutTypeSignature, compressedLayoutTypeSignature, superClass, fileEntry, loaderEntry);
+                                    layoutTypeSignature, superClass, fileEntry, loaderEntry);
                 } else if (hostedType.isInterface()) {
                     return new InterfaceClassEntry(typeName, size, classOffset, typeSignature, compressedTypeSignature,
-                            layoutTypeSignature, compressedLayoutTypeSignature, superClass, fileEntry, loaderEntry);
+                                    layoutTypeSignature, superClass, fileEntry, loaderEntry);
                 } else {
                     throw new RuntimeException("Unknown type kind " + hostedType.getName());
                 }
@@ -550,22 +631,144 @@ protected TypeEntry createTypeEntry(SharedType type) {
         }
     }
 
+    private void logForeignTypeInfo(HostedType hostedType) {
+        if (!isForeignPointerType(hostedType)) {
+            // non pointer type must be an interface because an instance needs to be pointed to
+            assert hostedType.isInterface();
+            // foreign word types never have element info
+            debug.log(DebugContext.VERBOSE_LEVEL, "Foreign word type %s", hostedType.toJavaName());
+        } else {
+            ElementInfo elementInfo = nativeLibs.findElementInfo(hostedType);
+            logForeignPointerType(hostedType, elementInfo);
+        }
+    }
+
+    private void logForeignPointerType(HostedType hostedType, ElementInfo elementInfo) {
+        if (elementInfo == null) {
+            // can happen for a generic (void*) pointer or a class
+            if (hostedType.isInterface()) {
+                debug.log(DebugContext.VERBOSE_LEVEL, "Foreign pointer type %s", hostedType.toJavaName());
+            } else {
+                debug.log(DebugContext.VERBOSE_LEVEL, "Foreign pointer type %s (class)", hostedType.toJavaName());
+            }
+        } else if (elementInfo instanceof PointerToInfo) {
+            logPointerToInfo(hostedType, (PointerToInfo) elementInfo);
+        } else if (elementInfo instanceof StructInfo) {
+            if (elementInfo instanceof RawStructureInfo) {
+                logRawStructureInfo(hostedType, (RawStructureInfo) elementInfo);
+            } else {
+                logStructInfo(hostedType, (StructInfo) elementInfo);
+            }
+        }
+    }
+
+    private void logPointerToInfo(HostedType hostedType, PointerToInfo pointerToInfo) {
+        debug.log(DebugContext.VERBOSE_LEVEL, "Foreign pointer type %s %s", hostedType.toJavaName(), elementKind(pointerToInfo));
+        assert hostedType.isInterface();
+        int size = elementSize(pointerToInfo);
+        boolean isUnsigned = pointerToInfo.isUnsigned();
+        String typedefName = pointerToInfo.getTypedefName();
+        debug.log("element size = %d", size);
+        debug.log("%s", (isUnsigned ? "<unsigned>" : "<signed>"));
+        if (typedefName != null) {
+            debug.log("typedefname = %s", typedefName);
+        }
+        dumpElementInfo(pointerToInfo);
+    }
+
+    private void logStructInfo(HostedType hostedType, StructInfo structInfo) {
+        debug.log(DebugContext.VERBOSE_LEVEL, "Foreign struct type %s %s", hostedType.toJavaName(), elementKind(structInfo));
+        assert hostedType.isInterface();
+        boolean isIncomplete = structInfo.isIncomplete();
+        if (isIncomplete) {
+            debug.log("<incomplete>");
+        } else {
+            debug.log("complete : element size = %d", elementSize(structInfo));
+        }
+        String typedefName = structInfo.getTypedefName();
+        if (typedefName != null) {
+            debug.log("    typedefName = %s", typedefName);
+        }
+        dumpElementInfo(structInfo);
+    }
+
+    private void logRawStructureInfo(HostedType hostedType, RawStructureInfo rawStructureInfo) {
+        debug.log(DebugContext.VERBOSE_LEVEL, "Foreign raw struct type %s %s", hostedType.toJavaName(), elementKind(rawStructureInfo));
+        assert hostedType.isInterface();
+        debug.log("element size = %d", elementSize(rawStructureInfo));
+        String typedefName = rawStructureInfo.getTypedefName();
+        if (typedefName != null) {
+            debug.log("    typedefName = %s", typedefName);
+        }
+        dumpElementInfo(rawStructureInfo);
+    }
+
+    private void dumpElementInfo(ElementInfo elementInfo) {
+        if (elementInfo != null) {
+            debug.log("Element Info {%n%s}", formatElementInfo(elementInfo));
+        } else {
+            debug.log("Element Info {}");
+        }
+    }
+
+    private static String formatElementInfo(ElementInfo elementInfo) {
+        StringBuilder stringBuilder = new StringBuilder();
+        formatElementInfo(elementInfo, stringBuilder, 0);
+        return stringBuilder.toString();
+    }
+
+    private static void formatElementInfo(ElementInfo elementInfo, StringBuilder stringBuilder, int indent) {
+        indentElementInfo(stringBuilder, indent);
+        formatSingleElement(elementInfo, stringBuilder);
+        List<ElementInfo> children = elementInfo.getChildren();
+        if (children == null || children.isEmpty()) {
+            stringBuilder.append("\n");
+        } else {
+            stringBuilder.append(" {\n");
+            for (ElementInfo child : children) {
+                formatElementInfo(child, stringBuilder, indent + 1);
+            }
+            indentElementInfo(stringBuilder, indent);
+            stringBuilder.append("}\n");
+        }
+    }
+
+    private static void formatSingleElement(ElementInfo elementInfo, StringBuilder stringBuilder) {
+        stringBuilder.append(ClassUtil.getUnqualifiedName(elementInfo.getClass()));
+        stringBuilder.append(" : ");
+        stringBuilder.append(elementName(elementInfo));
+        if (elementInfo instanceof PropertyInfo<?>) {
+            stringBuilder.append(" = ");
+            formatPropertyInfo((PropertyInfo<?>) elementInfo, stringBuilder);
+        }
+        if (elementInfo instanceof AccessorInfo) {
+            stringBuilder.append(" ");
+            stringBuilder.append(((AccessorInfo) elementInfo).getAccessorKind());
+        }
+    }
+
+    private static <T> void formatPropertyInfo(PropertyInfo<T> propertyInfo, StringBuilder stringBuilder) {
+        stringBuilder.append(propertyInfo.getProperty());
+    }
+
+    private static void indentElementInfo(StringBuilder stringBuilder, int indent) {
+        stringBuilder.append("  ".repeat(Math.max(0, indent + 1)));
+    }
+
     @Override
     public FileEntry lookupFileEntry(ResolvedJavaType type) {
-        if (type instanceof HostedType hostedType) {
-            // unwrap to the underlying class either the original or target class
-            // we want any substituted target if there is one. directly unwrapping will
-            // do what we want.
-            ResolvedJavaType javaType = hostedType.getWrapped().getWrapped();
-            Class<?> clazz = hostedType.getJavaClass();
-            SourceManager sourceManager = ImageSingletons.lookup(SourceManager.class);
-            Path filePath = sourceManager.findAndCacheSource(hostedType, clazz, debugContext);
+        Class<?> clazz = OriginalClassProvider.getJavaClass(type);
+        SourceManager sourceManager = ImageSingletons.lookup(SourceManager.class);
+        try (DebugContext.Scope s = debug.scope("DebugFileInfo", type)) {
+            Path filePath = sourceManager.findAndCacheSource(type, clazz, debug);
             if (filePath != null) {
                 return lookupFileEntry(filePath);
             }
+        } catch (Throwable e) {
+            throw debug.handle(e);
         }
-        // conjure up an appropriate, unique file name to keep tools happy
-        // even though we cannot find a corresponding source
+
+        // fallback to default file entry lookup
         return super.lookupFileEntry(type);
     }
 
@@ -586,7 +789,7 @@ private int getTypeSize(HostedType type) {
             }
             case HostedArrayClass hostedArrayClass -> {
                 /* Use the size of header common to all arrays of this type. */
-                return getObjectLayout().getArrayBaseOffset(type.getComponentType().getStorageKind());
+                return getObjectLayout().getArrayBaseOffset(hostedArrayClass.getComponentType().getStorageKind());
             }
             case HostedInterface hostedInterface -> {
                 /* Use the size of the header common to all implementors. */
@@ -594,7 +797,7 @@ private int getTypeSize(HostedType type) {
             }
             case HostedPrimitiveType hostedPrimitiveType -> {
                 /* Use the number of bytes needed to store the value. */
-                JavaKind javaKind = type.getStorageKind();
+                JavaKind javaKind = hostedPrimitiveType.getStorageKind();
                 return (javaKind == JavaKind.Void ? 0 : javaKind.getByteCount());
             }
             default -> {
@@ -605,8 +808,8 @@ private int getTypeSize(HostedType type) {
 
     private long getClassOffset(HostedType type) {
         /*
-         * Only query the heap for reachable types. These are guaranteed to have been seen by
-         * the analysis and to exist in the shadow heap.
+         * Only query the heap for reachable types. These are guaranteed to have been seen by the
+         * analysis and to exist in the shadow heap.
          */
         if (type.getWrapped().isReachable()) {
             NativeImageHeap.ObjectInfo objectInfo = heap.getObjectInfo(type.getHub());
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProviderBase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProviderBase.java
deleted file mode 100644
index fdafdf2d6493..000000000000
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProviderBase.java
+++ /dev/null
@@ -1,485 +0,0 @@
-/*
- * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-package com.oracle.svm.hosted.image;
-
-import static com.oracle.svm.hosted.c.info.AccessorInfo.AccessorKind.ADDRESS;
-import static com.oracle.svm.hosted.c.info.AccessorInfo.AccessorKind.GETTER;
-import static com.oracle.svm.hosted.c.info.AccessorInfo.AccessorKind.SETTER;
-
-import java.nio.file.FileSystems;
-import java.nio.file.Path;
-import java.util.HashMap;
-
-import org.graalvm.nativeimage.ImageSingletons;
-import org.graalvm.word.WordBase;
-
-import com.oracle.svm.core.StaticFieldsSupport;
-import com.oracle.svm.core.SubstrateOptions;
-import com.oracle.svm.core.config.ConfigurationValues;
-import com.oracle.svm.core.config.ObjectLayout;
-import com.oracle.svm.core.graal.code.SubstrateCallingConvention;
-import com.oracle.svm.core.graal.code.SubstrateCallingConventionKind;
-import com.oracle.svm.core.graal.code.SubstrateCallingConventionType;
-import com.oracle.svm.core.graal.meta.RuntimeConfiguration;
-import com.oracle.svm.core.graal.meta.SubstrateRegisterConfig;
-import com.oracle.svm.core.heap.Heap;
-import com.oracle.svm.core.heap.ObjectHeader;
-import com.oracle.svm.core.util.VMError;
-import com.oracle.svm.hosted.c.NativeLibraries;
-import com.oracle.svm.hosted.c.info.AccessorInfo;
-import com.oracle.svm.hosted.c.info.ElementInfo;
-import com.oracle.svm.hosted.c.info.SizableInfo;
-import com.oracle.svm.hosted.c.info.StructFieldInfo;
-import com.oracle.svm.hosted.c.info.StructInfo;
-import com.oracle.svm.hosted.meta.HostedField;
-import com.oracle.svm.hosted.meta.HostedMetaAccess;
-import com.oracle.svm.hosted.meta.HostedMethod;
-import com.oracle.svm.hosted.meta.HostedType;
-import com.oracle.svm.hosted.substitute.InjectedFieldsType;
-import com.oracle.svm.hosted.substitute.SubstitutionMethod;
-import com.oracle.svm.hosted.substitute.SubstitutionType;
-
-import jdk.graal.compiler.core.common.CompressEncoding;
-import jdk.graal.compiler.core.target.Backend;
-import jdk.vm.ci.code.RegisterConfig;
-import jdk.vm.ci.meta.JavaConstant;
-import jdk.vm.ci.meta.JavaKind;
-import jdk.vm.ci.meta.JavaType;
-import jdk.vm.ci.meta.ResolvedJavaMethod;
-import jdk.vm.ci.meta.ResolvedJavaType;
-
-/**
- * Abstract base class for implementation of DebugInfoProvider API providing a suite of useful
- * static and instance methods useful to provider implementations.
- */
-public abstract class NativeImageDebugInfoProviderBase {
-    protected final NativeImageHeap heap;
-    protected final NativeImageCodeCache codeCache;
-    protected final NativeLibraries nativeLibs;
-    protected final RuntimeConfiguration runtimeConfiguration;
-    protected final boolean useHeapBase;
-    protected final int compressionShift;
-    protected final int referenceSize;
-    protected final int pointerSize;
-    protected final int objectAlignment;
-    protected final int primitiveStartOffset;
-    protected final int referenceStartOffset;
-    protected final int reservedBitsMask;
-
-    protected final HostedType hubType;
-    protected final HostedType wordBaseType;
-    final HashMap<JavaKind, HostedType> javaKindToHostedType;
-    private final Path cachePath = SubstrateOptions.getDebugInfoSourceCacheRoot();
-
-    public NativeImageDebugInfoProviderBase(NativeImageCodeCache codeCache, NativeImageHeap heap, NativeLibraries nativeLibs, HostedMetaAccess metaAccess, RuntimeConfiguration runtimeConfiguration) {
-        this.heap = heap;
-        this.codeCache = codeCache;
-        this.nativeLibs = nativeLibs;
-        this.runtimeConfiguration = runtimeConfiguration;
-        this.hubType = metaAccess.lookupJavaType(Class.class);
-        this.wordBaseType = metaAccess.lookupJavaType(WordBase.class);
-        this.pointerSize = ConfigurationValues.getTarget().wordSize;
-        ObjectHeader objectHeader = Heap.getHeap().getObjectHeader();
-        NativeImageHeap.ObjectInfo primitiveFields = heap.getObjectInfo(StaticFieldsSupport.getStaticPrimitiveFields());
-        NativeImageHeap.ObjectInfo objectFields = heap.getObjectInfo(StaticFieldsSupport.getStaticObjectFields());
-        this.reservedBitsMask = objectHeader.getReservedBitsMask();
-        if (SubstrateOptions.SpawnIsolates.getValue()) {
-            CompressEncoding compressEncoding = ImageSingletons.lookup(CompressEncoding.class);
-            this.useHeapBase = compressEncoding.hasBase();
-            this.compressionShift = (compressEncoding.hasShift() ? compressEncoding.getShift() : 0);
-        } else {
-            this.useHeapBase = false;
-            this.compressionShift = 0;
-        }
-        this.referenceSize = getObjectLayout().getReferenceSize();
-        this.objectAlignment = getObjectLayout().getAlignment();
-        /* Offsets need to be adjusted relative to the heap base plus partition-specific offset. */
-        primitiveStartOffset = (int) primitiveFields.getOffset();
-        referenceStartOffset = (int) objectFields.getOffset();
-        javaKindToHostedType = initJavaKindToHostedTypes(metaAccess);
-    }
-
-    /*
-     * HostedType wraps an AnalysisType and both HostedType and AnalysisType punt calls to
-     * getSourceFilename to the wrapped class so for consistency we need to do type names and path
-     * lookup relative to the doubly unwrapped HostedType.
-     *
-     * However, note that the result of the unwrap on the AnalysisType may be a SubstitutionType
-     * which wraps both an original type and the annotated type that substitutes it. Unwrapping
-     * normally returns the AnnotatedType which we need to use to resolve the file name. However, we
-     * frequently (but not always) need to use the original to name the owning type to ensure that
-     * names found in method param and return types resolve correctly.
-     *
-     * Likewise, unwrapping of an AnalysisMethod or AnalysisField may encounter a SubstitutionMethod
-     * or SubstitutionField. It may also encounter a SubstitutionType when unwrapping the owner type
-     * is unwrapped. In those cases also we may need to use the substituted metadata rather than the
-     * substitution to ensure that names resolve correctly.
-     *
-     * The following static routines provide logic to perform unwrapping and, where necessary,
-     * traversal from the various substitution metadata instance to the corresponding substituted
-     * metadata instance.
-     */
-
-    protected static ResolvedJavaType getDeclaringClass(HostedType hostedType, boolean wantOriginal) {
-        // unwrap to the underlying class either the original or target class
-        if (wantOriginal) {
-            return getOriginal(hostedType);
-        }
-        // we want any substituted target if there is one. directly unwrapping will
-        // do what we want.
-        return hostedType.getWrapped().getWrapped();
-    }
-
-    protected static ResolvedJavaType getDeclaringClass(HostedMethod hostedMethod, boolean wantOriginal) {
-        if (wantOriginal) {
-            return getOriginal(hostedMethod.getDeclaringClass());
-        }
-        // we want a substituted target if there is one. if there is a substitution at the end of
-        // the method chain fetch the annotated target class
-        ResolvedJavaMethod javaMethod = NativeImageDebugInfoProviderBase.getAnnotatedOrOriginal(hostedMethod);
-        return javaMethod.getDeclaringClass();
-    }
-
-    @SuppressWarnings("unused")
-    protected static ResolvedJavaType getDeclaringClass(HostedField hostedField, boolean wantOriginal) {
-        /* for now fields are always reported as belonging to the original class */
-        return getOriginal(hostedField.getDeclaringClass());
-    }
-
-    protected static ResolvedJavaType getOriginal(HostedType hostedType) {
-        /*
-         * partially unwrap then traverse through substitutions to the original. We don't want to
-         * get the original type of LambdaSubstitutionType to keep the stable name
-         */
-        ResolvedJavaType javaType = hostedType.getWrapped().getWrapped();
-        if (javaType instanceof SubstitutionType) {
-            return ((SubstitutionType) javaType).getOriginal();
-        } else if (javaType instanceof InjectedFieldsType) {
-            return ((InjectedFieldsType) javaType).getOriginal();
-        }
-        return javaType;
-    }
-
-    private static ResolvedJavaMethod getAnnotatedOrOriginal(HostedMethod hostedMethod) {
-        ResolvedJavaMethod javaMethod = hostedMethod.getWrapped().getWrapped();
-        // This method is only used when identifying the modifiers or the declaring class
-        // of a HostedMethod. Normally the method unwraps to the underlying JVMCI method
-        // which is the one that provides bytecode to the compiler as well as, line numbers
-        // and local info. If we unwrap to a SubstitutionMethod then we use the annotated
-        // method, not the JVMCI method that the annotation refers to since that will be the
-        // one providing the bytecode etc used by the compiler. If we unwrap to any other,
-        // custom substitution method we simply use it rather than dereferencing to the
-        // original. The difference is that the annotated method's bytecode will be used to
-        // replace the original and the debugger needs to use it to identify the file and access
-        // permissions. A custom substitution may exist alongside the original, as is the case
-        // with some uses for reflection. So, we don't want to conflate the custom substituted
-        // method and the original. In this latter case the method code will be synthesized without
-        // reference to the bytecode of the original. Hence there is no associated file and the
-        // permissions need to be determined from the custom substitution method itself.
-
-        if (javaMethod instanceof SubstitutionMethod) {
-            SubstitutionMethod substitutionMethod = (SubstitutionMethod) javaMethod;
-            javaMethod = substitutionMethod.getAnnotated();
-        }
-        return javaMethod;
-    }
-
-    protected static int getOriginalModifiers(HostedMethod hostedMethod) {
-        return NativeImageDebugInfoProviderBase.getAnnotatedOrOriginal(hostedMethod).getModifiers();
-    }
-
-    /*
-     * GraalVM uses annotated interfaces to model foreign types. The following helpers support
-     * detection and categorization of these types, whether presented as a JavaType or HostedType.
-     */
-
-    /**
-     * Identify a Java type which is being used to model a foreign memory word or pointer type.
-     *
-     * @param type the type to be tested
-     * @param accessingType another type relative to which the first type may need to be resolved
-     * @return true if the type models a foreign memory word or pointer type
-     */
-    protected boolean isForeignWordType(JavaType type, ResolvedJavaType accessingType) {
-        HostedType resolvedJavaType = (HostedType) type.resolve(accessingType);
-        return isForeignWordType(resolvedJavaType);
-    }
-
-    /**
-     * Identify a hosted type which is being used to model a foreign memory word or pointer type.
-     *
-     * @param hostedType the type to be tested
-     * @return true if the type models a foreign memory word or pointer type
-     */
-    protected boolean isForeignWordType(HostedType hostedType) {
-        // unwrap because native libs operates on the analysis type universe
-        return nativeLibs.isWordBase(hostedType.getWrapped());
-    }
-
-    /**
-     * Identify a hosted type which is being used to model a foreign pointer type.
-     *
-     * @param hostedType the type to be tested
-     * @return true if the type models a foreign pointer type
-     */
-    protected boolean isForeignPointerType(HostedType hostedType) {
-        // unwrap because native libs operates on the analysis type universe
-        return nativeLibs.isPointerBase(hostedType.getWrapped());
-    }
-
-    /*
-     * Foreign pointer types have associated element info which describes the target type. The
-     * following helpers support querying of and access to this element info.
-     */
-
-    protected static boolean isTypedField(ElementInfo elementInfo) {
-        if (elementInfo instanceof StructFieldInfo) {
-            for (ElementInfo child : elementInfo.getChildren()) {
-                if (child instanceof AccessorInfo) {
-                    switch (((AccessorInfo) child).getAccessorKind()) {
-                        case GETTER:
-                        case SETTER:
-                        case ADDRESS:
-                            return true;
-                    }
-                }
-            }
-        }
-        return false;
-    }
-
-    protected HostedType getFieldType(StructFieldInfo field) {
-        // we should always have some sort of accessor, preferably a GETTER or a SETTER
-        // but possibly an ADDRESS accessor
-        for (ElementInfo elt : field.getChildren()) {
-            if (elt instanceof AccessorInfo) {
-                AccessorInfo accessorInfo = (AccessorInfo) elt;
-                if (accessorInfo.getAccessorKind() == GETTER) {
-                    return heap.hUniverse.lookup(accessorInfo.getReturnType());
-                }
-            }
-        }
-        for (ElementInfo elt : field.getChildren()) {
-            if (elt instanceof AccessorInfo) {
-                AccessorInfo accessorInfo = (AccessorInfo) elt;
-                if (accessorInfo.getAccessorKind() == SETTER) {
-                    return heap.hUniverse.lookup(accessorInfo.getParameterType(0));
-                }
-            }
-        }
-        for (ElementInfo elt : field.getChildren()) {
-            if (elt instanceof AccessorInfo) {
-                AccessorInfo accessorInfo = (AccessorInfo) elt;
-                if (accessorInfo.getAccessorKind() == ADDRESS) {
-                    return heap.hUniverse.lookup(accessorInfo.getReturnType());
-                }
-            }
-        }
-        assert false : "Field %s must have a GETTER, SETTER, ADDRESS or OFFSET accessor".formatted(field);
-        // treat it as a word?
-        // n.b. we want a hosted type not an analysis type
-        return heap.hUniverse.lookup(wordBaseType);
-    }
-
-    protected static boolean fieldTypeIsEmbedded(StructFieldInfo field) {
-        // we should always have some sort of accessor, preferably a GETTER or a SETTER
-        // but possibly an ADDRESS
-        for (ElementInfo elt : field.getChildren()) {
-            if (elt instanceof AccessorInfo) {
-                AccessorInfo accessorInfo = (AccessorInfo) elt;
-                if (accessorInfo.getAccessorKind() == GETTER) {
-                    return false;
-                }
-            }
-        }
-        for (ElementInfo elt : field.getChildren()) {
-            if (elt instanceof AccessorInfo) {
-                AccessorInfo accessorInfo = (AccessorInfo) elt;
-                if (accessorInfo.getAccessorKind() == SETTER) {
-                    return false;
-                }
-            }
-        }
-        for (ElementInfo elt : field.getChildren()) {
-            if (elt instanceof AccessorInfo) {
-                AccessorInfo accessorInfo = (AccessorInfo) elt;
-                if (accessorInfo.getAccessorKind() == ADDRESS) {
-                    return true;
-                }
-            }
-        }
-        throw VMError.shouldNotReachHere("Field %s must have a GETTER, SETTER, ADDRESS or OFFSET accessor".formatted(field));
-    }
-
-    protected static int elementSize(ElementInfo elementInfo) {
-        if (!(elementInfo instanceof SizableInfo) || elementInfo instanceof StructInfo structInfo && structInfo.isIncomplete()) {
-            return 0;
-        }
-        Integer size = ((SizableInfo) elementInfo).getSizeInBytes();
-        assert size != null;
-        return size;
-    }
-
-    protected static String elementName(ElementInfo elementInfo) {
-        if (elementInfo == null) {
-            return "";
-        } else {
-            return elementInfo.getName();
-        }
-    }
-
-    protected static SizableInfo.ElementKind elementKind(SizableInfo sizableInfo) {
-        return sizableInfo.getKind();
-    }
-
-    /*
-     * Debug info generation requires knowledge of a variety of parameters that determine object
-     * sizes and layouts and the organization of the code and code cache. The following helper
-     * methods provide access to this information.
-     */
-
-    static ObjectLayout getObjectLayout() {
-        return ConfigurationValues.getObjectLayout();
-    }
-
-    public boolean useHeapBase() {
-        return useHeapBase;
-    }
-
-    public int compressionShift() {
-        return compressionShift;
-    }
-
-    public int referenceSize() {
-        return referenceSize;
-    }
-
-    public int pointerSize() {
-        return pointerSize;
-    }
-
-    public int objectAlignment() {
-        return objectAlignment;
-    }
-
-    public int reservedBitsMask() {
-        return reservedBitsMask;
-    }
-
-    public int compiledCodeMax() {
-        return codeCache.getCodeCacheSize();
-    }
-
-    /**
-     * Return the offset into the initial heap at which the object identified by constant is located
-     * or -1 if the object is not present in the initial heap.
-     *
-     * @param constant must have JavaKind Object and must be non-null.
-     * @return the offset into the initial heap at which the object identified by constant is
-     *         located or -1 if the object is not present in the initial heap.
-     */
-    public long objectOffset(JavaConstant constant) {
-        assert constant.getJavaKind() == JavaKind.Object && !constant.isNull() : "invalid constant for object offset lookup";
-        NativeImageHeap.ObjectInfo objectInfo = heap.getConstantInfo(constant);
-        if (objectInfo != null) {
-            return objectInfo.getOffset();
-        }
-        return -1;
-    }
-
-    protected HostedType hostedTypeForKind(JavaKind kind) {
-        return javaKindToHostedType.get(kind);
-    }
-
-    private static HashMap<JavaKind, HostedType> initJavaKindToHostedTypes(HostedMetaAccess metaAccess) {
-        HashMap<JavaKind, HostedType> map = new HashMap<>();
-        for (JavaKind kind : JavaKind.values()) {
-            Class<?> clazz;
-            switch (kind) {
-                case Illegal:
-                    clazz = null;
-                    break;
-                case Object:
-                    clazz = java.lang.Object.class;
-                    break;
-                default:
-                    clazz = kind.toJavaClass();
-            }
-            HostedType javaType = clazz != null ? metaAccess.lookupJavaType(clazz) : null;
-            map.put(kind, javaType);
-        }
-        return map;
-    }
-
-    /**
-     * Retrieve details of the native calling convention for a top level compiled method, including
-     * details of which registers or stack slots are used to pass parameters.
-     * 
-     * @param method The method whose calling convention is required.
-     * @return The calling convention for the method.
-     */
-    protected SubstrateCallingConvention getCallingConvention(HostedMethod method) {
-        SubstrateCallingConventionKind callingConventionKind = method.getCallingConventionKind();
-        HostedType declaringClass = method.getDeclaringClass();
-        HostedType receiverType = method.isStatic() ? null : declaringClass;
-        var signature = method.getSignature();
-        final SubstrateCallingConventionType type;
-        if (callingConventionKind.isCustom()) {
-            type = method.getCustomCallingConventionType();
-        } else {
-            type = callingConventionKind.toType(false);
-        }
-        Backend backend = runtimeConfiguration.lookupBackend(method);
-        RegisterConfig registerConfig = backend.getCodeCache().getRegisterConfig();
-        assert registerConfig instanceof SubstrateRegisterConfig;
-        return (SubstrateCallingConvention) registerConfig.getCallingConvention(type, signature.getReturnType(), signature.toParameterTypes(receiverType), backend);
-    }
-
-    /*
-     * The following helpers aid construction of file paths for class source files.
-     */
-
-    protected static Path fullFilePathFromClassName(HostedType hostedInstanceClass) {
-        String[] elements = hostedInstanceClass.toJavaName().split("\\.");
-        int count = elements.length;
-        String name = elements[count - 1];
-        while (name.startsWith("$")) {
-            name = name.substring(1);
-        }
-        if (name.contains("$")) {
-            name = name.substring(0, name.indexOf('$'));
-        }
-        if (name.equals("")) {
-            name = "_nofile_";
-        }
-        elements[count - 1] = name + ".java";
-        return FileSystems.getDefault().getPath("", elements);
-    }
-
-    public Path getCachePath() {
-        return cachePath;
-    }
-}
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/sources/SourceManager.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/sources/SourceManager.java
index a07904a260f8..af7ba1565312 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/sources/SourceManager.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/sources/SourceManager.java
@@ -31,7 +31,6 @@
 import java.util.HashMap;
 
 import jdk.graal.compiler.debug.DebugContext;
-
 import jdk.vm.ci.meta.ResolvedJavaType;
 
 /**
@@ -72,10 +71,8 @@ public Path findAndCacheSource(ResolvedJavaType resolvedType, Class<?> clazz, De
                 path = locateSource(fileName, packageName, clazz);
                 if (path == null) {
                     // as a last ditch effort derive path from the Java class name
-                    if (debugContext.areScopesEnabled()) {
-                        debugContext.log(DebugContext.INFO_LEVEL, "Failed to find source file for class %s%n", resolvedType.toJavaName());
-                    }
-                    if (packageName.length() > 0) {
+                    debugContext.log(DebugContext.INFO_LEVEL, "Failed to find source file for class %s%n", resolvedType.toJavaName());
+                    if (!packageName.isEmpty()) {
                         path = Paths.get("", packageName.split("\\."));
                         path = path.resolve(fileName);
                     }
@@ -93,7 +90,7 @@ public Path findAndCacheSource(ResolvedJavaType resolvedType, Class<?> clazz, De
      * the source name embedded in the class file or the class name itself.
      *
      * @param resolvedType the resolved java type whose source file name is required
-     * @return the file name or null if it the class cannot be associated with a source file
+     * @return the file name or null if the class cannot be associated with a source file
      */
     private static String computeBaseName(ResolvedJavaType resolvedType) {
         if (resolvedType.isPrimitive()) {
@@ -146,10 +143,10 @@ private static String computePackageName(ResolvedJavaType javaType) {
      *
      * @param fileName the base file name for the source file
      * @param packageName the name of the package for the associated Java class
-     * @return a protoype name for the source file
+     * @return a prototype name for the source file
      */
     private static Path computePrototypeName(String fileName, String packageName) {
-        if (packageName.length() == 0) {
+        if (packageName.isEmpty()) {
             return Paths.get("", fileName);
         } else {
             return Paths.get("", packageName.split("\\.")).resolve(fileName);
@@ -160,7 +157,7 @@ private static Path computePrototypeName(String fileName, String packageName) {
      * A map from a Java type to an associated source paths which is known to have an up to date
      * entry in the relevant source file cache. This is used to memoize previous lookups.
      */
-    private static HashMap<ResolvedJavaType, Path> verifiedPaths = new HashMap<>();
+    private static final HashMap<ResolvedJavaType, Path> verifiedPaths = new HashMap<>();
 
     /**
      * An invalid path used as a marker to track failed lookups so we don't waste time looking up

From f9994577bc4890dc52ee0e08649ab561d9e2cca6 Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Mon, 9 Dec 2024 19:50:23 +0100
Subject: [PATCH 11/34] Remove some constraints from logging in dwarf sections,
 Split up Debug Entries for LocalValues and ForeignTypes, some refactoring

---
 .../debugentry/CompiledMethodEntry.java       |  29 +++-
 .../debugentry/ConstantValueEntry.java        |  45 +++++
 .../debugentry/ForeignFloatTypeEntry.java     |  35 ++++
 .../debugentry/ForeignIntegerTypeEntry.java   |  41 +++++
 .../debugentry/ForeignPointerTypeEntry.java   |  41 +++++
 .../debugentry/ForeignStructTypeEntry.java    |  48 ++++++
 .../debugentry/ForeignTypeEntry.java          |  60 +------
 .../debugentry/ForeignWordTypeEntry.java      |  41 +++++
 .../debugentry/LocalValueEntry.java           |  19 +--
 .../debugentry/RegisterValueEntry.java        |  37 ++++
 .../debugentry/StackValueEntry.java           |  36 ++++
 .../objectfile/debugentry/TypeEntry.java      |  10 +-
 .../objectfile/debugentry/range/Range.java    |  75 +++------
 .../elf/dwarf/DwarfARangesSectionImpl.java    |  44 ++---
 .../elf/dwarf/DwarfAbbrevSectionImpl.java     |   9 +-
 .../elf/dwarf/DwarfFrameSectionImpl.java      |   7 +-
 .../elf/dwarf/DwarfInfoSectionImpl.java       | 159 +++++++-----------
 .../elf/dwarf/DwarfLineSectionImpl.java       |  76 +++------
 .../elf/dwarf/DwarfLocSectionImpl.java        |  62 +++----
 .../elf/dwarf/DwarfRangesSectionImpl.java     |  26 +--
 .../elf/dwarf/DwarfSectionImpl.java           |  18 +-
 .../elf/dwarf/DwarfStrSectionImpl.java        |   3 +-
 .../core/debug/SharedDebugInfoProvider.java   |  48 +++---
 .../image/NativeImageDebugInfoProvider.java   |  98 ++++++-----
 24 files changed, 594 insertions(+), 473 deletions(-)
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ConstantValueEntry.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignFloatTypeEntry.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignIntegerTypeEntry.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignPointerTypeEntry.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignStructTypeEntry.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignWordTypeEntry.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/RegisterValueEntry.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StackValueEntry.java

diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/CompiledMethodEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/CompiledMethodEntry.java
index ef8e74d6f430..e567435e4830 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/CompiledMethodEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/CompiledMethodEntry.java
@@ -26,26 +26,26 @@
 
 package com.oracle.objectfile.debugentry;
 
-import com.oracle.objectfile.debugentry.range.PrimaryRange;
-import com.oracle.objectfile.debugentry.range.Range;
-
 import java.util.List;
 import java.util.stream.Stream;
 
+import com.oracle.objectfile.debugentry.range.PrimaryRange;
+import com.oracle.objectfile.debugentry.range.Range;
+
 /**
  * Tracks debug info associated with a top level compiled method.
  *
- * @param primary        The primary range detailed by this object.
- * @param classEntry     Details of the class owning this range.
+ * @param primary The primary range detailed by this object.
+ * @param classEntry Details of the class owning this range.
  * @param frameSizeInfos Details of compiled method frame size changes.
- * @param frameSize      Size of compiled method frame.
+ * @param frameSize Size of compiled method frame.
  */
 public record CompiledMethodEntry(PrimaryRange primary, List<FrameSizeChangeEntry> frameSizeInfos, int frameSize,
-                                  ClassEntry classEntry) {
+                ClassEntry classEntry) {
 
     /**
-     * Returns a stream that traverses all the callees of the method associated with this entry.
-     * The stream performs a depth-first pre-order traversal of the call tree.
+     * Returns a stream that traverses all the callees of the method associated with this entry. The
+     * stream performs a depth-first pre-order traversal of the call tree.
      *
      * @return the iterator
      */
@@ -64,4 +64,15 @@ public Stream<Range> topDownRangeStream() {
     public Stream<Range> leafRangeStream() {
         return topDownRangeStream().filter(Range::isLeaf);
     }
+
+    /**
+     * Returns a stream that traverses the callees of the method associated with this entry and
+     * returns only the call ranges. The stream performs a depth-first pre-order traversal of the
+     * call tree returning only ranges with callees.
+     *
+     * @return the iterator
+     */
+    public Stream<Range> callRangeStream() {
+        return topDownRangeStream().filter(range -> !range.isLeaf());
+    }
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ConstantValueEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ConstantValueEntry.java
new file mode 100644
index 000000000000..7cc67d3f59f0
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ConstantValueEntry.java
@@ -0,0 +1,45 @@
+package com.oracle.objectfile.debugentry;
+
+import java.util.Objects;
+
+import jdk.vm.ci.meta.JavaConstant;
+
+public class ConstantValueEntry extends LocalValueEntry {
+    private final long heapOffset;
+    private final JavaConstant constant;
+
+    public ConstantValueEntry(long heapOffset, JavaConstant constant, LocalEntry local) {
+        super(local);
+        this.heapOffset = heapOffset;
+        this.constant = constant;
+    }
+
+    @Override
+    public String toString() {
+        return "CONST:" + (constant != null ? constant.toValueString() : "null") + "[" + Long.toHexString(heapOffset) + "]";
+    }
+
+    public JavaConstant getConstant() {
+        return constant;
+    }
+
+    public long getHeapOffset() {
+        return heapOffset;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this)
+            return true;
+        if (obj == null || obj.getClass() != this.getClass())
+            return false;
+        var that = (ConstantValueEntry) obj;
+        return this.heapOffset == that.heapOffset &&
+                        Objects.equals(this.constant, that.constant);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(heapOffset, constant);
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignFloatTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignFloatTypeEntry.java
new file mode 100644
index 000000000000..8b6a198751b9
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignFloatTypeEntry.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.debugentry;
+
+public class ForeignFloatTypeEntry extends ForeignTypeEntry {
+
+    public ForeignFloatTypeEntry(String typeName, int size, long classOffset, long typeSignature,
+                    long layoutTypeSignature, ClassEntry superClass, FileEntry fileEntry, LoaderEntry loader) {
+        super(typeName, size, classOffset, typeSignature, layoutTypeSignature, superClass, fileEntry, loader);
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignIntegerTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignIntegerTypeEntry.java
new file mode 100644
index 000000000000..20e8b6868f9c
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignIntegerTypeEntry.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.debugentry;
+
+public class ForeignIntegerTypeEntry extends ForeignTypeEntry {
+    private final boolean isSigned;
+
+    public ForeignIntegerTypeEntry(String typeName, int size, long classOffset, long typeSignature,
+                    long layoutTypeSignature, ClassEntry superClass, FileEntry fileEntry, LoaderEntry loader, boolean isSigned) {
+        super(typeName, size, classOffset, typeSignature, layoutTypeSignature, superClass, fileEntry, loader);
+        this.isSigned = isSigned;
+    }
+
+    public boolean isSigned() {
+        return isSigned;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignPointerTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignPointerTypeEntry.java
new file mode 100644
index 000000000000..0b8f83d73268
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignPointerTypeEntry.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.debugentry;
+
+public class ForeignPointerTypeEntry extends ForeignTypeEntry {
+    private final TypeEntry pointerTo;
+
+    public ForeignPointerTypeEntry(String typeName, int size, long classOffset, long typeSignature,
+                    long layoutTypeSignature, ClassEntry superClass, FileEntry fileEntry, LoaderEntry loader, TypeEntry pointerTo) {
+        super(typeName, size, classOffset, typeSignature, layoutTypeSignature, superClass, fileEntry, loader);
+        this.pointerTo = pointerTo;
+    }
+
+    public TypeEntry getPointerTo() {
+        return pointerTo;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignStructTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignStructTypeEntry.java
new file mode 100644
index 000000000000..e14f62141c0b
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignStructTypeEntry.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.debugentry;
+
+public class ForeignStructTypeEntry extends ForeignTypeEntry {
+    private final String typedefName;
+    private final ForeignStructTypeEntry parent;
+
+    public ForeignStructTypeEntry(String typeName, int size, long classOffset, long typeSignature,
+                    long layoutTypeSignature, ClassEntry superClass, FileEntry fileEntry, LoaderEntry loader,
+                    String typedefName, ForeignStructTypeEntry parent) {
+        super(typeName, size, classOffset, typeSignature, layoutTypeSignature, superClass, fileEntry, loader);
+        this.typedefName = typedefName;
+        this.parent = parent;
+    }
+
+    public String getTypedefName() {
+        return typedefName;
+    }
+
+    public ForeignStructTypeEntry getParent() {
+        return parent;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignTypeEntry.java
index 07812d99fc9f..e95a2a7b5760 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignTypeEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignTypeEntry.java
@@ -26,31 +26,11 @@
 
 package com.oracle.objectfile.debugentry;
 
-public class ForeignTypeEntry extends ClassEntry {
-    private final String typedefName;
-    private final ForeignTypeEntry parent;
-    private final TypeEntry pointerTo;
-    private final boolean isWord;
-    private final boolean isStruct;
-    private final boolean isPointer;
-    private final boolean isInteger;
-    private final boolean isSigned;
-    private final boolean isFloat;
+public abstract class ForeignTypeEntry extends ClassEntry {
 
     public ForeignTypeEntry(String typeName, int size, long classOffset, long typeSignature,
-                    long layoutTypeSignature, ClassEntry superClass, FileEntry fileEntry, LoaderEntry loader,
-                    String typedefName, ForeignTypeEntry parent, TypeEntry pointerTo, boolean isWord,
-                    boolean isStruct, boolean isPointer, boolean isInteger, boolean isSigned, boolean isFloat) {
+                    long layoutTypeSignature, ClassEntry superClass, FileEntry fileEntry, LoaderEntry loader) {
         super(typeName, size, classOffset, typeSignature, typeSignature, layoutTypeSignature, superClass, fileEntry, loader);
-        this.typedefName = typedefName;
-        this.parent = parent;
-        this.pointerTo = pointerTo;
-        this.isWord = isWord;
-        this.isStruct = isStruct;
-        this.isPointer = isPointer;
-        this.isInteger = isInteger;
-        this.isSigned = isSigned;
-        this.isFloat = isFloat;
     }
 
     @Override
@@ -62,40 +42,4 @@ public boolean isForeign() {
     public boolean isInstance() {
         return false;
     }
-
-    public String getTypedefName() {
-        return typedefName;
-    }
-
-    public ForeignTypeEntry getParent() {
-        return parent;
-    }
-
-    public TypeEntry getPointerTo() {
-        return pointerTo;
-    }
-
-    public boolean isWord() {
-        return isWord;
-    }
-
-    public boolean isStruct() {
-        return isStruct;
-    }
-
-    public boolean isPointer() {
-        return isPointer;
-    }
-
-    public boolean isInteger() {
-        return isInteger;
-    }
-
-    public boolean isSigned() {
-        return isSigned;
-    }
-
-    public boolean isFloat() {
-        return isFloat;
-    }
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignWordTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignWordTypeEntry.java
new file mode 100644
index 000000000000..3039c24508e2
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignWordTypeEntry.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2023, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.debugentry;
+
+public class ForeignWordTypeEntry extends ForeignTypeEntry {
+    private final boolean isSigned;
+
+    public ForeignWordTypeEntry(String typeName, int size, long classOffset, long typeSignature,
+                    long layoutTypeSignature, ClassEntry superClass, FileEntry fileEntry, LoaderEntry loader, boolean isSigned) {
+        super(typeName, size, classOffset, typeSignature, layoutTypeSignature, superClass, fileEntry, loader);
+        this.isSigned = isSigned;
+    }
+
+    public boolean isSigned() {
+        return isSigned;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalValueEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalValueEntry.java
index e933e69b5abb..dafdf017f896 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalValueEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalValueEntry.java
@@ -1,18 +1,13 @@
 package com.oracle.objectfile.debugentry;
 
-import com.oracle.objectfile.debuginfo.DebugInfoProvider.LocalValueKind;
-import jdk.vm.ci.meta.JavaConstant;
+public abstract class LocalValueEntry {
+    private final LocalEntry local;
 
-public record LocalValueEntry(int regIndex, int stackSlot, long heapOffset, JavaConstant constant, LocalValueKind localKind, LocalEntry local) {
-
-    @Override
-    public String toString() {
-        return switch (localKind) {
-            case REGISTER -> "reg[" + regIndex + "]";
-            case STACK -> "stack[" + stackSlot + "]";
-            case CONSTANT -> "constant[" + (constant != null ? constant.toValueString() : "null") + "]";
-            default -> "-";
-        };
+    protected LocalValueEntry(LocalEntry local) {
+        this.local = local;
     }
 
+    public LocalEntry getLocal() {
+        return local;
+    }
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/RegisterValueEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/RegisterValueEntry.java
new file mode 100644
index 000000000000..4a7208e1e585
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/RegisterValueEntry.java
@@ -0,0 +1,37 @@
+package com.oracle.objectfile.debugentry;
+
+import java.util.Objects;
+
+public class RegisterValueEntry extends LocalValueEntry {
+
+    private final int regIndex;
+
+    public RegisterValueEntry(int regIndex, LocalEntry local) {
+        super(local);
+        this.regIndex = regIndex;
+    }
+
+    @Override
+    public String toString() {
+        return "REG:" + regIndex;
+    }
+
+    public int getRegIndex() {
+        return regIndex;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this)
+            return true;
+        if (obj == null || obj.getClass() != this.getClass())
+            return false;
+        var that = (RegisterValueEntry) obj;
+        return this.regIndex == that.regIndex;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(regIndex);
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StackValueEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StackValueEntry.java
new file mode 100644
index 000000000000..57b66c0ec90b
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StackValueEntry.java
@@ -0,0 +1,36 @@
+package com.oracle.objectfile.debugentry;
+
+import java.util.Objects;
+
+public class StackValueEntry extends LocalValueEntry {
+    private final int stackSlot;
+
+    public StackValueEntry(int stackSlot, LocalEntry local) {
+        super(local);
+        this.stackSlot = stackSlot;
+    }
+
+    @Override
+    public String toString() {
+        return "STACK:" + stackSlot;
+    }
+
+    public int getStackSlot() {
+        return stackSlot;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this)
+            return true;
+        if (obj == null || obj.getClass() != this.getClass())
+            return false;
+        var that = (StackValueEntry) obj;
+        return this.stackSlot == that.stackSlot;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(stackSlot);
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/TypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/TypeEntry.java
index 4796670d96f3..0c8c5a84bcd2 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/TypeEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/TypeEntry.java
@@ -55,7 +55,7 @@ public abstract class TypeEntry {
     private final int size;
 
     protected TypeEntry(String typeName, int size, long classOffset, long typeSignature,
-                        long typeSignatureForCompressed) {
+                    long typeSignatureForCompressed) {
         this.typeName = typeName;
         this.size = size;
         this.classOffset = classOffset;
@@ -138,9 +138,13 @@ public String toString() {
             case PrimitiveTypeEntry p -> "Primitive";
             case HeaderTypeEntry h -> "Header";
             case ArrayTypeEntry a -> "Array";
-            case InterfaceClassEntry i  -> "Interface";
+            case InterfaceClassEntry i -> "Interface";
             case EnumClassEntry e -> "Enum";
-            case ForeignTypeEntry f -> "Foreign";
+            case ForeignWordTypeEntry fw -> "ForeignWord";
+            case ForeignStructTypeEntry fs -> "ForeignStruct";
+            case ForeignPointerTypeEntry fp -> "ForeignPointer";
+            case ForeignIntegerTypeEntry fi -> "ForeignInteger";
+            case ForeignFloatTypeEntry ff -> "ForeignFloat";
             case ClassEntry c -> "Instance";
             default -> "";
         };
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java
index 3bb5dcb35936..7740088ea557 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java
@@ -26,23 +26,25 @@
 
 package com.oracle.objectfile.debugentry.range;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Stream;
+
+import com.oracle.objectfile.debugentry.ConstantValueEntry;
 import com.oracle.objectfile.debugentry.FileEntry;
 import com.oracle.objectfile.debugentry.LocalEntry;
 import com.oracle.objectfile.debugentry.LocalValueEntry;
 import com.oracle.objectfile.debugentry.MethodEntry;
+import com.oracle.objectfile.debugentry.StackValueEntry;
 import com.oracle.objectfile.debugentry.TypeEntry;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider;
+
 import jdk.vm.ci.meta.JavaConstant;
 import jdk.vm.ci.meta.JavaKind;
 import jdk.vm.ci.meta.PrimitiveConstant;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Stream;
-
 /**
  * Details of a specific address range in a compiled method either a primary range identifying a
  * whole compiled method or a sub-range identifying a sub-sequence of the compiled instructions that
@@ -165,14 +167,14 @@ public Range split(int stackDecrement, int frameSize, int preExtendFrameSize) {
         this.caller.insertCallee(newRange, 1);
 
         for (LocalValueEntry localInfo : this.localValueInfos) {
-            if (localInfo.localKind() == DebugInfoProvider.LocalValueKind.STACK) {
+            if (localInfo instanceof StackValueEntry stackValue) {
                 // need to redefine the value for this param using a stack slot value
                 // that allows for the stack being extended by framesize. however we
                 // also need to remove any adjustment that was made to allow for the
                 // difference between the caller SP and the pre-extend callee SP
                 // because of a stacked return address.
                 int adjustment = frameSize - preExtendFrameSize;
-                newRange.localValueInfos.add(new LocalValueEntry(localInfo.regIndex(), localInfo.stackSlot() + adjustment, localInfo.heapOffset(), localInfo.constant(), localInfo.localKind(), localInfo.local()));
+                newRange.localValueInfos.add(new StackValueEntry(stackValue.getStackSlot() + adjustment, stackValue.getLocal()));
             } else {
                 newRange.localValueInfos.add(localInfo);
             }
@@ -311,17 +313,8 @@ public Map<LocalEntry, List<Range>> getVarRangeMap() {
         HashMap<LocalEntry, List<Range>> varRangeMap = new HashMap<>();
         for (Range callee : getCallees()) {
             for (LocalValueEntry localValue : callee.localValueInfos) {
-                LocalEntry local = localValue.local();
-                if (local != null) {
-                    switch (localValue.localKind()) {
-                        case REGISTER:
-                        case STACK:
-                        case CONSTANT:
-                            varRangeMap.computeIfAbsent(local, l -> new ArrayList<>()).add(callee);
-                            break;
-                        case UNDEFINED:
-                            break;
-                    }
+                if (localValue != null && localValue.getLocal() != null) {
+                    varRangeMap.computeIfAbsent(localValue.getLocal(), l -> new ArrayList<>()).add(callee);
                 }
             }
         }
@@ -331,17 +324,14 @@ public Map<LocalEntry, List<Range>> getVarRangeMap() {
     public boolean hasLocalValues(LocalEntry local) {
         for (Range callee : getCallees()) {
             for (LocalValueEntry localValue : callee.localValueInfos) {
-                if (local == localValue.local()) {
-                    switch (localValue.localKind()) {
-                        case REGISTER:
-                        case STACK:
-                            return true;
-                        case CONSTANT:
-                            JavaConstant constant = localValue.constant();
-                            // can only handle primitive or null constants just now
-                            return constant instanceof PrimitiveConstant || constant.getJavaKind() == JavaKind.Object;
-                        case UNDEFINED:
-                            break;
+                if (localValue != null && localValue.getLocal() == local) {
+                    if (localValue instanceof ConstantValueEntry constantValueEntry) {
+                        JavaConstant constant = constantValueEntry.getConstant();
+                        // can only handle primitive or null constants just now
+                        return constant instanceof PrimitiveConstant || constant.getJavaKind() == JavaKind.Object;
+                    } else {
+                        // register or stack value
+                        return true;
                     }
                 }
             }
@@ -357,25 +347,13 @@ public List<LocalValueEntry> getLocalValues() {
         return Collections.unmodifiableList(localValueInfos);
     }
 
-    public int getLocalValueCount() {
-        return localValueInfos.size();
-    }
-
-    public LocalValueEntry getLocalValue(int i) {
-        return localValueInfos.get(i);
-    }
-
-    public LocalEntry getLocal(int i) {
-        return getLocalValue(i).local();
-    }
-
     public void setLocalValueInfo(List<LocalValueEntry> localValueInfos) {
         this.localValueInfos.addAll(localValueInfos);
     }
 
     public LocalValueEntry lookupValue(LocalEntry local) {
         for (LocalValueEntry localValue : localValueInfos) {
-            if (localValue.local() == local) {
+            if (localValue.getLocal() == local) {
                 return localValue;
             }
         }
@@ -396,14 +374,9 @@ public boolean tryMerge(Range that) {
         if (this.line != that.line) {
             return false;
         }
-        if (this.localValueInfos.size() != that.localValueInfos.size()) {
+        if (!this.localValueInfos.equals(that.localValueInfos)) {
             return false;
         }
-        for (int i = 0; i < this.localValueInfos.size(); i++) {
-            if (!this.getLocalValue(i).equals(that.getLocalValue(i))) {
-                return false;
-            }
-        }
         // merging just requires updating lo and hi range as everything else is equal
         this.hiOffset = that.hiOffset;
         caller.removeCallee(that);
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfARangesSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfARangesSectionImpl.java
index ca06b1514d3f..62c0e6bb5944 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfARangesSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfARangesSectionImpl.java
@@ -26,18 +26,12 @@
 
 package com.oracle.objectfile.elf.dwarf;
 
-import java.util.Map;
-
-import com.oracle.objectfile.debugentry.ClassEntry;
+import com.oracle.objectfile.debugentry.CompiledMethodEntry;
+import com.oracle.objectfile.debugentry.range.Range;
 import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName;
 import com.oracle.objectfile.elf.dwarf.constants.DwarfVersion;
-import jdk.graal.compiler.debug.DebugContext;
 
-import com.oracle.objectfile.LayoutDecision;
-import com.oracle.objectfile.LayoutDecisionMap;
-import com.oracle.objectfile.ObjectFile;
-import com.oracle.objectfile.debugentry.CompiledMethodEntry;
-import com.oracle.objectfile.debugentry.range.Range;
+import jdk.graal.compiler.debug.DebugContext;
 
 /**
  * Section generator for debug_aranges section.
@@ -90,11 +84,10 @@ public void createContent() {
          * Where N is the number of compiled methods.
          */
         assert !contentByteArrayCreated();
-        Cursor byteCount = new Cursor();
-        instanceClassStream().filter(ClassEntry::hasCompiledMethods).forEachOrdered(classEntry -> {
-            byteCount.add(entrySize(classEntry.compiledMethods().size()));
-        });
-        byte[] buffer = new byte[byteCount.get()];
+        int byteCount = instanceClassWithCompilationStream()
+                        .mapToInt(classEntry -> entrySize(classEntry.compiledMethods().size()))
+                        .sum();
+        byte[] buffer = new byte[byteCount];
         super.setContent(buffer);
     }
 
@@ -111,23 +104,6 @@ private static int entrySize(int methodCount) {
         return size;
     }
 
-    @Override
-    public byte[] getOrDecideContent(Map<ObjectFile.Element, LayoutDecisionMap> alreadyDecided, byte[] contentHint) {
-        ObjectFile.Element textElement = getElement().getOwner().elementForName(".text");
-        LayoutDecisionMap decisionMap = alreadyDecided.get(textElement);
-        if (decisionMap != null) {
-            Object valueObj = decisionMap.getDecidedValue(LayoutDecision.Kind.VADDR);
-            if (valueObj instanceof Number) {
-                /*
-                 * This may not be the final vaddr for the text segment but it will be close enough
-                 * to make debug easier i.e. to within a 4k page or two.
-                 */
-                debugTextBase = ((Number) valueObj).longValue();
-            }
-        }
-        return super.getOrDecideContent(alreadyDecided, contentHint);
-    }
-
     @Override
     public void writeContent(DebugContext context) {
         assert contentByteArrayCreated();
@@ -135,10 +111,10 @@ public void writeContent(DebugContext context) {
         int size = buffer.length;
         Cursor cursor = new Cursor();
 
-        enableLog(context, cursor.get());
+        enableLog(context);
 
         log(context, "  [0x%08x] DEBUG_ARANGES", cursor.get());
-        instanceClassStream().filter(ClassEntry::hasCompiledMethods).forEachOrdered(classEntry -> {
+        instanceClassWithCompilationStream().forEachOrdered(classEntry -> {
             int lengthPos = cursor.get();
             log(context, "  [0x%08x] class %s CU 0x%x", lengthPos, classEntry.getTypeName(), getCUIndex(classEntry));
             cursor.set(writeHeader(getCUIndex(classEntry), buffer, cursor.get()));
@@ -177,7 +153,7 @@ private int writeHeader(int cuIndex, byte[] buffer, int p) {
     int writeARange(DebugContext context, CompiledMethodEntry compiledMethod, byte[] buffer, int p) {
         int pos = p;
         Range primary = compiledMethod.primary();
-        log(context, "  [0x%08x] %016x %016x %s", pos, debugTextBase + primary.getLo(), primary.getHi() - primary.getLo(), primary.getFullMethodNameWithParams());
+        log(context, "  [0x%08x] %016x %016x %s", pos, primary.getLo(), primary.getHi() - primary.getLo(), primary.getFullMethodNameWithParams());
         pos = writeCodeOffset(primary.getLo(), buffer, pos);
         pos = writeLong(primary.getHi() - primary.getLo(), buffer, pos);
         return pos;
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java
index a0a465e97d83..dc75eb1fa6ff 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java
@@ -905,7 +905,7 @@ public void writeContent(DebugContext context) {
         int size = buffer.length;
         int pos = 0;
 
-        enableLog(context, pos);
+        enableLog(context);
 
         pos = writeAbbrevs(context, buffer, pos);
 
@@ -927,7 +927,6 @@ public int writeAbbrevs(DebugContext context, byte[] buffer, int p) {
         pos = writeParameterLocationAbbrevs(context, buffer, pos);
         pos = writeLocalLocationAbbrevs(context, buffer, pos);
 
-
         // Write Abbrevs that are only used for AOT debuginfo generation
         if (!dwarfSections.isRuntimeCompilation()) {
             pos = writeTypeUnitAbbrev(context, buffer, pos);
@@ -958,9 +957,9 @@ public int writeAbbrevs(DebugContext context, byte[] buffer, int p) {
 
             /*
              * if we address rebasing is required then we need to use compressed layout types
-             * supplied with a suitable data_location attribute and compressed pointer types to ensure
-             * that gdb converts offsets embedded in static or instance fields to raw pointers.
-             * Transformed addresses are typed using pointers to the underlying layout.
+             * supplied with a suitable data_location attribute and compressed pointer types to
+             * ensure that gdb converts offsets embedded in static or instance fields to raw
+             * pointers. Transformed addresses are typed using pointers to the underlying layout.
              *
              * if address rebasing is not required then a data_location attribute on the layout type
              * will ensure that address tag bits are removed.
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImpl.java
index 585d9a1bac6b..5f26a84e4150 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImpl.java
@@ -26,14 +26,15 @@
 
 package com.oracle.objectfile.elf.dwarf;
 
+import java.util.List;
+
 import com.oracle.objectfile.debugentry.CompiledMethodEntry;
 import com.oracle.objectfile.debugentry.FrameSizeChangeEntry;
 import com.oracle.objectfile.debugentry.range.Range;
 import com.oracle.objectfile.elf.dwarf.constants.DwarfFrameValue;
 import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName;
-import jdk.graal.compiler.debug.DebugContext;
 
-import java.util.List;
+import jdk.graal.compiler.debug.DebugContext;
 
 /**
  * Section generic generator for debug_frame section.
@@ -74,7 +75,7 @@ public void writeContent(DebugContext context) {
         int size = buffer.length;
         int pos = 0;
 
-        enableLog(context, pos);
+        enableLog(context);
 
         /*
          * There are entries for the prologue region where the stack is being built, the method body
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 6a466192ea91..ee66de24317b 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
@@ -27,28 +27,28 @@
 package com.oracle.objectfile.elf.dwarf;
 
 import java.lang.reflect.Modifier;
-import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
-import java.util.stream.Stream;
 
 import com.oracle.objectfile.debugentry.ArrayTypeEntry;
 import com.oracle.objectfile.debugentry.ClassEntry;
 import com.oracle.objectfile.debugentry.CompiledMethodEntry;
 import com.oracle.objectfile.debugentry.FieldEntry;
 import com.oracle.objectfile.debugentry.FileEntry;
+import com.oracle.objectfile.debugentry.ForeignFloatTypeEntry;
+import com.oracle.objectfile.debugentry.ForeignIntegerTypeEntry;
+import com.oracle.objectfile.debugentry.ForeignPointerTypeEntry;
+import com.oracle.objectfile.debugentry.ForeignStructTypeEntry;
 import com.oracle.objectfile.debugentry.ForeignTypeEntry;
+import com.oracle.objectfile.debugentry.ForeignWordTypeEntry;
 import com.oracle.objectfile.debugentry.HeaderTypeEntry;
 import com.oracle.objectfile.debugentry.InterfaceClassEntry;
 import com.oracle.objectfile.debugentry.LocalEntry;
-import com.oracle.objectfile.debugentry.LocalValueEntry;
 import com.oracle.objectfile.debugentry.MethodEntry;
 import com.oracle.objectfile.debugentry.PrimitiveTypeEntry;
 import com.oracle.objectfile.debugentry.StructureTypeEntry;
 import com.oracle.objectfile.debugentry.TypeEntry;
 import com.oracle.objectfile.debugentry.range.Range;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider;
 import com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.AbbrevCode;
 import com.oracle.objectfile.elf.dwarf.constants.DwarfAccess;
 import com.oracle.objectfile.elf.dwarf.constants.DwarfEncoding;
@@ -61,8 +61,6 @@
 import com.oracle.objectfile.elf.dwarf.constants.DwarfVersion;
 
 import jdk.graal.compiler.debug.DebugContext;
-import jdk.vm.ci.meta.JavaKind;
-import jdk.vm.ci.meta.PrimitiveConstant;
 
 /**
  * Section generator for debug_info section.
@@ -101,7 +99,7 @@ public void writeContent(DebugContext context) {
         int size = buffer.length;
         int pos = 0;
 
-        enableLog(context, pos);
+        enableLog(context);
         log(context, "  [0x%08x] DEBUG_INFO", pos);
         log(context, "  [0x%08x] size = 0x%08x", pos, size);
 
@@ -137,7 +135,7 @@ public int generateContent(DebugContext context, byte[] buffer) {
         int pos = 0;
         if (dwarfSections.isRuntimeCompilation()) {
             Cursor cursor = new Cursor(pos);
-            instanceClassStream().filter(ClassEntry::hasCompiledMethods).forEachOrdered(classEntry -> {
+            instanceClassWithCompilationStream().forEachOrdered(classEntry -> {
                 setCUIndex(classEntry, cursor.get());
                 cursor.set(writeInstanceClassInfo(context, classEntry, buffer, cursor.get()));
             });
@@ -358,8 +356,8 @@ private int writeStructField(DebugContext context, FieldEntry fieldEntry, byte[]
                 abbrevCode = AbbrevCode.ARRAY_ELEMENT_FIELD;
                 pos = writeEmbeddedArrayDataType(context, foreignValueType, valueSize, fieldSize / valueSize, buffer, pos);
             } else {
-                if (foreignValueType.isPointer()) {
-                    TypeEntry pointerTo = foreignValueType.getPointerTo();
+                if (foreignValueType instanceof ForeignPointerTypeEntry pointerTypeEntry) {
+                    TypeEntry pointerTo = pointerTypeEntry.getPointerTo();
                     assert pointerTo != null : "ADDRESS field pointer type must have a known target type";
                     // type the array using the referent of the pointer type
                     //
@@ -757,38 +755,33 @@ private int writeForeignLayoutTypeUnit(DebugContext context, ForeignTypeEntry fo
         String loaderId = foreignTypeEntry.getLoaderId();
         int lengthPos = pos;
 
-        /* Only write a TU preamble if we will write a new layout type. */
-        if (foreignTypeEntry.isWord() || foreignTypeEntry.isInteger() || foreignTypeEntry.isFloat() || foreignTypeEntry.isStruct()) {
-            pos = writeTUPreamble(context, foreignTypeEntry.getLayoutTypeSignature(), loaderId, buffer, pos);
+        if (foreignTypeEntry instanceof ForeignPointerTypeEntry pointerTypeEntry) {
+            TypeEntry pointerTo = pointerTypeEntry.getPointerTo();
+            log(context, "  [0x%08x] foreign pointer type %s referent 0x%x (%s)", pos, foreignTypeEntry.getTypeName(), pointerTo.getTypeSignature(), pointerTo.getTypeName());
+            // As we do not write anything, we can just return the initial position.
+            return p;
         }
 
+        /* Only write a TU preamble if we will write a new layout type. */
+        pos = writeTUPreamble(context, foreignTypeEntry.getLayoutTypeSignature(), loaderId, buffer, pos);
+
         int size = foreignTypeEntry.getSize();
-        if (foreignTypeEntry.isWord()) {
-            // define the type as a typedef for a signed or unsigned word i.e. we don't have a
-            // layout type
-            pos = writeForeignWordLayout(context, foreignTypeEntry, size, foreignTypeEntry.isSigned(), buffer, pos);
-        } else if (foreignTypeEntry.isInteger()) {
-            // use a suitably sized signed or unsigned integral type as the layout type
-            pos = writeForeignIntegerLayout(context, foreignTypeEntry, size, foreignTypeEntry.isSigned(), buffer, pos);
-        } else if (foreignTypeEntry.isFloat()) {
-            // use a suitably sized float type as the layout type
-            pos = writeForeignFloatLayout(context, foreignTypeEntry, size, buffer, pos);
-        } else if (foreignTypeEntry.isStruct()) {
-            // define this type using a structure layout
-            pos = writeForeignStructLayout(context, foreignTypeEntry, size, buffer, pos);
-        } else {
-            // this must be a pointer. if the target type is known use it to declare the pointer
-            // type, otherwise default to 'void *'
-            TypeEntry targetType = voidType();
-            if (foreignTypeEntry.isPointer()) {
-                TypeEntry pointerTo = foreignTypeEntry.getPointerTo();
-                if (pointerTo != null) {
-                    targetType = pointerTo;
-                }
+        switch (foreignTypeEntry) {
+            case ForeignWordTypeEntry wordTypeEntry ->
+                // define the type as a typedef for a signed or unsigned word i.e. we don't have a
+                // layout type
+                pos = writeForeignWordLayout(context, wordTypeEntry, size, buffer, pos);
+            case ForeignIntegerTypeEntry integerTypeEntry ->
+                // use a suitably sized signed or unsigned integer type as the layout type
+                pos = writeForeignIntegerLayout(context, integerTypeEntry, size, buffer, pos);
+            case ForeignFloatTypeEntry floatTypeEntry ->
+                // use a suitably sized float type as the layout type
+                pos = writeForeignFloatLayout(context, floatTypeEntry, size, buffer, pos);
+            case ForeignStructTypeEntry structTypeEntry ->
+                // define this type using a structure layout
+                pos = writeForeignStructLayout(context, structTypeEntry, size, buffer, pos);
+            default -> {
             }
-            log(context, "  [0x%08x] foreign pointer type %s referent 0x%x (%s)", pos, foreignTypeEntry.getTypeName(), targetType.getTypeSignature(), targetType.getTypeName());
-            // As we do not write anything, we can just return the initial position.
-            return p;
         }
 
         /*
@@ -1324,15 +1317,15 @@ private int writeInterfaceImplementor(DebugContext context, ClassEntry classEntr
         return pos;
     }
 
-    private int writeForeignStructLayout(DebugContext context, ForeignTypeEntry foreignTypeEntry, int size, byte[] buffer, int p) {
+    private int writeForeignStructLayout(DebugContext context, ForeignStructTypeEntry foreignStructTypeEntry, int size, byte[] buffer, int p) {
         int pos = p;
-        log(context, "  [0x%08x] foreign struct type for %s", pos, foreignTypeEntry.getTypeName());
+        log(context, "  [0x%08x] foreign struct type for %s", pos, foreignStructTypeEntry.getTypeName());
         AbbrevCode abbrevCode = AbbrevCode.FOREIGN_STRUCT;
         log(context, "  [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal());
         pos = writeAbbrevCode(abbrevCode, buffer, pos);
-        String typedefName = foreignTypeEntry.getTypedefName();
+        String typedefName = foreignStructTypeEntry.getTypedefName();
         if (typedefName == null) {
-            typedefName = "_" + foreignTypeEntry.getTypeName();
+            typedefName = "_" + foreignStructTypeEntry.getTypeName();
             verboseLog(context, "  [0x%08x]   using synthetic typedef name %s", pos, typedefName);
         }
         if (typedefName.startsWith("struct ")) {
@@ -1346,21 +1339,21 @@ private int writeForeignStructLayout(DebugContext context, ForeignTypeEntry fore
         log(context, "  [0x%08x]     byte_size  0x%x", pos, size);
         pos = writeAttrData1((byte) size, buffer, pos);
         // if we have a parent write a super attribute
-        ForeignTypeEntry parent = foreignTypeEntry.getParent();
+        ForeignStructTypeEntry parent = foreignStructTypeEntry.getParent();
         if (parent != null) {
             long typeSignature = parent.getLayoutTypeSignature();
             pos = writeSuperReference(context, typeSignature, parent.getTypedefName(), buffer, pos);
         }
-        pos = writeStructFields(context, foreignTypeEntry.getFields(), buffer, pos);
+        pos = writeStructFields(context, foreignStructTypeEntry.getFields(), buffer, pos);
         /*
          * Write a terminating null attribute.
          */
         return writeAttrNull(buffer, pos);
     }
 
-    private int writeForeignWordLayout(DebugContext context, ForeignTypeEntry foreignTypeEntry, int size, boolean isSigned, byte[] buffer, int p) {
+    private int writeForeignWordLayout(DebugContext context, ForeignWordTypeEntry foreignWordTypeEntry, int size, byte[] buffer, int p) {
         int pos = p;
-        log(context, "  [0x%08x] foreign primitive word type for %s", pos, foreignTypeEntry.getTypeName());
+        log(context, "  [0x%08x] foreign primitive word type for %s", pos, foreignWordTypeEntry.getTypeName());
         /* Record the location of this type entry. */
         AbbrevCode abbrevCode = AbbrevCode.PRIMITIVE_TYPE;
         log(context, "  [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal());
@@ -1373,17 +1366,17 @@ private int writeForeignWordLayout(DebugContext context, ForeignTypeEntry foreig
         log(context, "  [0x%08x]     bitCount  %d", pos, bitCount);
         pos = writeAttrData1(bitCount, buffer, pos);
         // treat the layout as a signed or unsigned word of the relevant size
-        DwarfEncoding encoding = (isSigned ? DwarfEncoding.DW_ATE_signed : DwarfEncoding.DW_ATE_unsigned);
+        DwarfEncoding encoding = (foreignWordTypeEntry.isSigned() ? DwarfEncoding.DW_ATE_signed : DwarfEncoding.DW_ATE_unsigned);
         log(context, "  [0x%08x]     encoding  0x%x", pos, encoding.value());
         pos = writeAttrEncoding(encoding, buffer, pos);
-        String name = uniqueDebugString(integralTypeName(byteSize, isSigned));
+        String name = uniqueDebugString(integralTypeName(byteSize, foreignWordTypeEntry.isSigned()));
         log(context, "  [0x%08x]     name  0x%x (%s)", pos, debugStringIndex(name), name);
         return writeStrSectionOffset(name, buffer, pos);
     }
 
-    private int writeForeignIntegerLayout(DebugContext context, ForeignTypeEntry foreignTypeEntry, int size, boolean isSigned, byte[] buffer, int p) {
+    private int writeForeignIntegerLayout(DebugContext context, ForeignIntegerTypeEntry foreignIntegerTypeEntry, int size, byte[] buffer, int p) {
         int pos = p;
-        log(context, "  [0x%08x] foreign primitive integral type for %s", pos, foreignTypeEntry.getTypeName());
+        log(context, "  [0x%08x] foreign primitive integral type for %s", pos, foreignIntegerTypeEntry.getTypeName());
         /* Record the location of this type entry. */
         AbbrevCode abbrevCode = AbbrevCode.PRIMITIVE_TYPE;
         log(context, "  [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal());
@@ -1396,17 +1389,17 @@ private int writeForeignIntegerLayout(DebugContext context, ForeignTypeEntry for
         log(context, "  [0x%08x]     bitCount  %d", pos, bitCount);
         pos = writeAttrData1(bitCount, buffer, pos);
         // treat the layout as a signed or unsigned word of the relevant size
-        DwarfEncoding encoding = (isSigned ? DwarfEncoding.DW_ATE_signed : DwarfEncoding.DW_ATE_unsigned);
+        DwarfEncoding encoding = (foreignIntegerTypeEntry.isSigned() ? DwarfEncoding.DW_ATE_signed : DwarfEncoding.DW_ATE_unsigned);
         log(context, "  [0x%08x]     encoding  0x%x", pos, encoding.value());
         pos = writeAttrEncoding(encoding, buffer, pos);
-        String name = uniqueDebugString(integralTypeName(byteSize, isSigned));
+        String name = uniqueDebugString(integralTypeName(byteSize, foreignIntegerTypeEntry.isSigned()));
         log(context, "  [0x%08x]     name  0x%x (%s)", pos, debugStringIndex(name), name);
         return writeStrSectionOffset(name, buffer, pos);
     }
 
-    private int writeForeignFloatLayout(DebugContext context, ForeignTypeEntry foreignTypeEntry, int size, byte[] buffer, int p) {
+    private int writeForeignFloatLayout(DebugContext context, ForeignFloatTypeEntry foreignFloatTypeEntry, int size, byte[] buffer, int p) {
         int pos = p;
-        log(context, "  [0x%08x] foreign primitive float type for %s", pos, foreignTypeEntry.getTypeName());
+        log(context, "  [0x%08x] foreign primitive float type for %s", pos, foreignFloatTypeEntry.getTypeName());
         /* Record the location of this type entry. */
         AbbrevCode abbrevCode = AbbrevCode.PRIMITIVE_TYPE;
         log(context, "  [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal());
@@ -1694,8 +1687,8 @@ private int writeEmbeddedArrayDataType(DebugContext context, ForeignTypeEntry fo
         pos = writeAttrData4(size, buffer, pos);
         String elementTypeName = foreignValueType.getTypeName();
         long elementTypeSignature;
-        if (foreignValueType.isPointer()) {
-            TypeEntry pointerTo = foreignValueType.getPointerTo();
+        if (foreignValueType instanceof ForeignPointerTypeEntry pointerTypeEntry) {
+            TypeEntry pointerTo = pointerTypeEntry.getPointerTo();
             assert pointerTo != null : "ADDRESS field pointer type must have a known target type";
             // type the array using the referent of the pointer type
             //
@@ -1773,9 +1766,8 @@ private int writeMethodLocation(DebugContext context, ClassEntry classEntry, Com
         int methodSpecOffset = getMethodDeclarationIndex(primary.getMethodEntry());
         log(context, "  [0x%08x]     specification  0x%x (%s)", pos, methodSpecOffset, methodKey);
         pos = writeInfoSectionOffset(methodSpecOffset, buffer, pos);
-        Map<LocalEntry, List<Range>> varRangeMap = primary.getVarRangeMap();
-        pos = writeMethodParameterLocations(context, classEntry, varRangeMap, primary, 2, buffer, pos);
-        pos = writeMethodLocalLocations(context, classEntry, varRangeMap, primary, 2, buffer, pos);
+        pos = writeMethodParameterLocations(context, classEntry, primary, 2, buffer, pos);
+        pos = writeMethodLocalLocations(context, classEntry, primary, 2, buffer, pos);
         if (primary.includesInlineRanges()) {
             /*
              * the method has inlined ranges so write concrete inlined method entries as its
@@ -1789,7 +1781,7 @@ private int writeMethodLocation(DebugContext context, ClassEntry classEntry, Com
         return writeAttrNull(buffer, pos);
     }
 
-    private int writeMethodParameterLocations(DebugContext context, ClassEntry classEntry, Map<LocalEntry, List<Range>> varRangeMap, Range range, int depth, byte[] buffer, int p) {
+    private int writeMethodParameterLocations(DebugContext context, ClassEntry classEntry, Range range, int depth, byte[] buffer, int p) {
         int pos = p;
         MethodEntry methodEntry;
         if (range.isPrimary()) {
@@ -1801,17 +1793,17 @@ private int writeMethodParameterLocations(DebugContext context, ClassEntry class
         if (!Modifier.isStatic(methodEntry.getModifiers())) {
             LocalEntry thisParamInfo = methodEntry.getThisParam();
             int refAddr = getMethodLocalIndex(classEntry, methodEntry, thisParamInfo);
-            pos = writeMethodLocalLocation(context, range, thisParamInfo, varRangeMap, refAddr, depth, true, buffer, pos);
+            pos = writeMethodLocalLocation(context, range, thisParamInfo, refAddr, depth, true, buffer, pos);
         }
         for (int i = 0; i < methodEntry.getParamCount(); i++) {
             LocalEntry paramInfo = methodEntry.getParam(i);
             int refAddr = getMethodLocalIndex(classEntry, methodEntry, paramInfo);
-            pos = writeMethodLocalLocation(context, range, paramInfo, varRangeMap, refAddr, depth, true, buffer, pos);
+            pos = writeMethodLocalLocation(context, range, paramInfo, refAddr, depth, true, buffer, pos);
         }
         return pos;
     }
 
-    private int writeMethodLocalLocations(DebugContext context, ClassEntry classEntry, Map<LocalEntry, List<Range>> varRangeMap, Range range, int depth, byte[] buffer, int p) {
+    private int writeMethodLocalLocations(DebugContext context, ClassEntry classEntry, Range range, int depth, byte[] buffer, int p) {
         int pos = p;
         MethodEntry methodEntry;
         if (range.isPrimary()) {
@@ -1823,32 +1815,16 @@ private int writeMethodLocalLocations(DebugContext context, ClassEntry classEntr
 
         for (LocalEntry local : methodEntry.getLocals()) {
             int refAddr = getMethodLocalIndex(classEntry, methodEntry, local);
-            pos = writeMethodLocalLocation(context, range, local, varRangeMap, refAddr, depth, false, buffer, pos);
+            pos = writeMethodLocalLocation(context, range, local, refAddr, depth, false, buffer, pos);
         }
         return pos;
     }
 
-    private int writeMethodLocalLocation(DebugContext context, Range range, LocalEntry localInfo, Map<LocalEntry, List<Range>> varRangeMap, int refAddr, int depth, boolean isParam, byte[] buffer,
+    private int writeMethodLocalLocation(DebugContext context, Range range, LocalEntry localInfo, int refAddr, int depth, boolean isParam, byte[] buffer,
                     int p) {
         int pos = p;
         log(context, "  [0x%08x] method %s location %s:%s", pos, (isParam ? "parameter" : "local"), localInfo.name(), localInfo.type().getTypeName());
 
-        boolean hasValues = false;
-        for (Range subrange : varRangeMap.getOrDefault(localInfo, new ArrayList<>())) {
-            LocalValueEntry value = subrange.lookupValue(localInfo);
-            if (value != null) {
-                log(context, "  [0x%08x]     local  %s:%s [0x%x, 0x%x] = %s", pos, value.local().name(), value.local().type().getTypeName(), subrange.getLo(), subrange.getHi(), formatValue(value));
-                DebugInfoProvider.LocalValueKind localKind = value.localKind();
-                // can only handle primitive or null constants just now
-                if (localKind == DebugInfoProvider.LocalValueKind.REGISTER || localKind == DebugInfoProvider.LocalValueKind.STACK || (
-                        localKind == DebugInfoProvider.LocalValueKind.CONSTANT && (value.constant() instanceof PrimitiveConstant || value.constant().getJavaKind() == JavaKind.Object)
-                        )) {
-                    hasValues = true;
-                    break;
-                }
-            }
-        }
-
         AbbrevCode abbrevCode;
         if (range.hasLocalValues(localInfo)) {
             abbrevCode = (isParam ? AbbrevCode.METHOD_PARAMETER_LOCATION_2 : AbbrevCode.METHOD_LOCAL_LOCATION_2);
@@ -1879,12 +1855,7 @@ private int generateConcreteInlinedMethods(DebugContext context, ClassEntry clas
         int pos = p;
         log(context, "  [0x%08x] concrete entries [0x%x,0x%x] %s", pos, primary.getLo(), primary.getHi(), primary.getFullMethodName());
         int depth = 0;
-        Stream<Range> iterator = compiledEntry.topDownRangeStream();
-        for (Range subrange : iterator.toList()) {
-            if (subrange.isLeaf()) {
-                // we only generate concrete methods for non-leaf entries
-                continue;
-            }
+        for (Range subrange : compiledEntry.callRangeStream().toList()) {
             // if we just stepped out of a child range write nulls for each step up
             while (depth > subrange.getDepth()) {
                 pos = writeAttrNull(buffer, pos);
@@ -1894,9 +1865,8 @@ private int generateConcreteInlinedMethods(DebugContext context, ClassEntry clas
             pos = writeInlineSubroutine(context, classEntry, subrange, depth + 2, buffer, pos);
             // increment depth to account for parameter and method locations
             depth++;
-            Map<LocalEntry, List<Range>> varRangeMap = subrange.getVarRangeMap();
-            pos = writeMethodParameterLocations(context, classEntry, varRangeMap, subrange, depth + 2, buffer, pos);
-            pos = writeMethodLocalLocations(context, classEntry, varRangeMap, subrange, depth + 2, buffer, pos);
+            pos = writeMethodParameterLocations(context, classEntry, subrange, depth + 2, buffer, pos);
+            pos = writeMethodLocalLocations(context, classEntry, subrange, depth + 2, buffer, pos);
         }
         // if we just stepped out of a child range write nulls for each step up
         while (depth > 0) {
@@ -1986,12 +1956,7 @@ private void addInlinedMethods(DebugContext context, CompiledMethodEntry compile
             return;
         }
         verboseLog(context, "  [0x%08x] collect abstract inlined methods %s", p, primary.getFullMethodName());
-        Stream<Range> iterator = compiledEntry.topDownRangeStream();
-        for (Range subrange : iterator.toList()) {
-            if (subrange.isLeaf()) {
-                // we only generate abstract inline methods for non-leaf entries
-                continue;
-            }
+        for (Range subrange : compiledEntry.callRangeStream().toList()) {
             // the subrange covers an inline call and references the caller method entry. its
             // child ranges all reference the same inlined called method. leaf children cover code
             // for
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 16cd374f4e9b..34d365183ffa 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
@@ -26,21 +26,15 @@
 
 package com.oracle.objectfile.elf.dwarf;
 
-import java.util.Iterator;
-import java.util.Map;
-
 import com.oracle.objectfile.debugentry.ClassEntry;
+import com.oracle.objectfile.debugentry.CompiledMethodEntry;
+import com.oracle.objectfile.debugentry.FileEntry;
+import com.oracle.objectfile.debugentry.range.Range;
 import com.oracle.objectfile.elf.dwarf.constants.DwarfLineOpcode;
 import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName;
 import com.oracle.objectfile.elf.dwarf.constants.DwarfVersion;
-import jdk.graal.compiler.debug.DebugContext;
 
-import com.oracle.objectfile.LayoutDecision;
-import com.oracle.objectfile.LayoutDecisionMap;
-import com.oracle.objectfile.ObjectFile;
-import com.oracle.objectfile.debugentry.CompiledMethodEntry;
-import com.oracle.objectfile.debugentry.FileEntry;
-import com.oracle.objectfile.debugentry.range.Range;
+import jdk.graal.compiler.debug.DebugContext;
 
 /**
  * Section generator for debug_line section.
@@ -83,7 +77,7 @@ public void createContent() {
          */
 
         Cursor byteCount = new Cursor();
-        instanceClassStream().filter(ClassEntry::hasCompiledMethods).forEachOrdered(classEntry -> {
+        instanceClassWithCompilationStream().forEachOrdered(classEntry -> {
             setLineIndex(classEntry, byteCount.get());
             int headerSize = headerSize();
             int dirTableSize = computeDirTableSize(classEntry);
@@ -186,23 +180,6 @@ private int computeLineNumberTableSize(ClassEntry classEntry) {
         return writeLineNumberTable(null, classEntry, null, 0);
     }
 
-    @Override
-    public byte[] getOrDecideContent(Map<ObjectFile.Element, LayoutDecisionMap> alreadyDecided, byte[] contentHint) {
-        ObjectFile.Element textElement = getElement().getOwner().elementForName(".text");
-        LayoutDecisionMap decisionMap = alreadyDecided.get(textElement);
-        if (decisionMap != null) {
-            Object valueObj = decisionMap.getDecidedValue(LayoutDecision.Kind.VADDR);
-            if (valueObj instanceof Number) {
-                /*
-                 * This may not be the final vaddr for the text segment but it will be close enough
-                 * to make debug easier i.e. to within a 4k page or two.
-                 */
-                debugTextBase = ((Number) valueObj).longValue();
-            }
-        }
-        return super.getOrDecideContent(alreadyDecided, contentHint);
-    }
-
     @Override
     public void writeContent(DebugContext context) {
         assert contentByteArrayCreated();
@@ -210,9 +187,9 @@ public void writeContent(DebugContext context) {
         byte[] buffer = getContent();
         Cursor cursor = new Cursor();
 
-        enableLog(context, cursor.get());
+        enableLog(context);
         log(context, "  [0x%08x] DEBUG_LINE", cursor.get());
-        instanceClassStream().filter(ClassEntry::hasCompiledMethods).forEachOrdered(classEntry -> {
+        instanceClassWithCompilationStream().forEachOrdered(classEntry -> {
             int pos = cursor.get();
             setLineIndex(classEntry, pos);
             int lengthPos = pos;
@@ -362,7 +339,7 @@ private int writeCompiledMethodLineInfo(DebugContext context, ClassEntry classEn
         // the compiled method might be a substitution and not in the file of the class entry
         FileEntry fileEntry = primaryRange.getFileEntry();
         if (fileEntry == null) {
-            log(context, "  [0x%08x] primary range [0x%08x, 0x%08x] skipped (no file) %s", pos, debugTextBase + primaryRange.getLo(), debugTextBase + primaryRange.getHi(),
+            log(context, "  [0x%08x] primary range [0x%08x, 0x%08x] skipped (no file) %s", pos, primaryRange.getLo(), primaryRange.getHi(),
                             primaryRange.getFullMethodNameWithParams());
             return pos;
         }
@@ -395,7 +372,7 @@ private int writeCompiledMethodLineInfo(DebugContext context, ClassEntry classEn
         /*
          * Set state for primary.
          */
-        log(context, "  [0x%08x] primary range [0x%08x, 0x%08x] %s %s:%d", pos, debugTextBase + primaryRange.getLo(), debugTextBase + primaryRange.getHi(),
+        log(context, "  [0x%08x] primary range [0x%08x, 0x%08x] %s %s:%d", pos, primaryRange.getLo(), primaryRange.getHi(),
                         primaryRange.getFullMethodNameWithParams(),
                         file, primaryRange.getLine());
 
@@ -419,14 +396,11 @@ private int writeCompiledMethodLineInfo(DebugContext context, ClassEntry classEn
         /*
          * Now write a row for each subrange lo and hi.
          */
-        Iterator<Range> iterator = compiledEntry.leafRangeStream().iterator();
-        if (prologueRange != null) {
-            // skip already processed range
-            Range first = iterator.next();
-            assert first == prologueRange;
-        }
-        while (iterator.hasNext()) {
-            Range subrange = iterator.next();
+
+        assert prologueRange == null || compiledEntry.leafRangeStream().findFirst().filter(first -> first == prologueRange).isPresent();
+
+        // skip already processed range
+        for (Range subrange : compiledEntry.leafRangeStream().skip(prologueRange != null ? 1 : 0).toList()) {
             assert subrange.getLo() >= primaryRange.getLo();
             assert subrange.getHi() <= primaryRange.getHi();
             FileEntry subFileEntry = subrange.getFileEntry();
@@ -439,7 +413,7 @@ private int writeCompiledMethodLineInfo(DebugContext context, ClassEntry classEn
             long subLine = subrange.getLine();
             long subAddressLo = subrange.getLo();
             long subAddressHi = subrange.getHi();
-            log(context, "  [0x%08x] sub range [0x%08x, 0x%08x] %s %s:%d", pos, debugTextBase + subAddressLo, debugTextBase + subAddressHi, subrange.getFullMethodNameWithParams(), subfile,
+            log(context, "  [0x%08x] sub range [0x%08x, 0x%08x] %s %s:%d", pos, subAddressLo, subAddressHi, subrange.getFullMethodNameWithParams(), subfile,
                             subLine);
             if (subLine < 0) {
                 /*
@@ -537,6 +511,7 @@ private int writeCompiledMethodLineInfo(DebugContext context, ClassEntry classEn
             line += lineDelta;
             address += addressDelta;
         }
+
         /*
          * Append a final end sequence just below the next primary range.
          */
@@ -566,14 +541,10 @@ private int writeLineNumberTable(DebugContext context, ClassEntry classEntry, by
     }
 
     private static Range prologueLeafRange(CompiledMethodEntry compiledEntry) {
-        Iterator<Range> iterator = compiledEntry.leafRangeStream().iterator();
-        if (iterator.hasNext()) {
-            Range range = iterator.next();
-            if (range.getLo() == compiledEntry.primary().getLo()) {
-                return range;
-            }
-        }
-        return null;
+        return compiledEntry.leafRangeStream()
+                        .findFirst()
+                        .filter(r -> r.getLo() == compiledEntry.primary().getLo())
+                        .orElse(null);
     }
 
     private int writeCopyOp(DebugContext context, byte[] buffer, int p) {
@@ -650,7 +621,7 @@ private int writeEndSequenceOp(DebugContext context, byte[] buffer, int p) {
         DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNE_end_sequence;
         int pos = p;
         verboseLog(context, "  [0x%08x] Extended opcode 1: End sequence", pos);
-        debugAddress = debugTextBase;
+        debugAddress = 0;
         debugLine = 1;
         debugCopyCount = 0;
         pos = writePrefixOpcode(buffer, pos);
@@ -664,8 +635,7 @@ private int writeEndSequenceOp(DebugContext context, byte[] buffer, int p) {
     private int writeSetAddressOp(DebugContext context, long arg, byte[] buffer, int p) {
         DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNE_set_address;
         int pos = p;
-        debugAddress = debugTextBase + (int) arg;
-        verboseLog(context, "  [0x%08x] Extended opcode 2: Set Address to 0x%08x", pos, debugAddress);
+        verboseLog(context, "  [0x%08x] Extended opcode 2: Set Address to 0x%08x", pos, arg);
         pos = writePrefixOpcode(buffer, pos);
         /*
          * Insert extended insn byte count as ULEB.
@@ -734,7 +704,7 @@ private int writeSpecialOpcode(DebugContext context, byte opcode, byte[] buffer,
         debugAddress += opcodeAddress(opcode);
         debugLine += opcodeLine(opcode);
         verboseLog(context, "  [0x%08x] Special Opcode %d: advance Address by %d to 0x%08x and Line by %d to %d",
-                p, opcodeId(opcode), opcodeAddress(opcode), debugAddress, opcodeLine(opcode), debugLine);
+                        p, opcodeId(opcode), opcodeAddress(opcode), debugAddress, opcodeLine(opcode), debugLine);
         return writeByte(opcode, buffer, p);
     }
 
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLocSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLocSectionImpl.java
index 4e722966a7f9..f7535209b148 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLocSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLocSectionImpl.java
@@ -28,7 +28,6 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -38,8 +37,11 @@
 import com.oracle.objectfile.LayoutDecisionMap;
 import com.oracle.objectfile.ObjectFile;
 import com.oracle.objectfile.debugentry.ClassEntry;
+import com.oracle.objectfile.debugentry.ConstantValueEntry;
 import com.oracle.objectfile.debugentry.LocalEntry;
 import com.oracle.objectfile.debugentry.LocalValueEntry;
+import com.oracle.objectfile.debugentry.RegisterValueEntry;
+import com.oracle.objectfile.debugentry.StackValueEntry;
 import com.oracle.objectfile.debugentry.range.Range;
 import com.oracle.objectfile.elf.ELFMachine;
 import com.oracle.objectfile.elf.ELFObjectFile;
@@ -70,15 +72,9 @@ public class DwarfLocSectionImpl extends DwarfSectionImpl {
      */
     private int dwarfStackRegister;
 
-    private static final LayoutDecision.Kind[] targetLayoutKinds = {
-                    LayoutDecision.Kind.CONTENT,
-                    LayoutDecision.Kind.SIZE,
-                    /* Add this so we can use the text section base address for debug. */
-                    LayoutDecision.Kind.VADDR};
-
     public DwarfLocSectionImpl(DwarfDebugInfo dwarfSections) {
         // debug_loc section depends on text section
-        super(dwarfSections, DwarfSectionName.DW_LOCLISTS_SECTION, DwarfSectionName.TEXT_SECTION, targetLayoutKinds);
+        super(dwarfSections, DwarfSectionName.DW_LOCLISTS_SECTION, DwarfSectionName.TEXT_SECTION);
         initDwarfRegMap();
     }
 
@@ -115,7 +111,7 @@ public void writeContent(DebugContext context) {
         int size = buffer.length;
         int pos = 0;
 
-        enableLog(context, pos);
+        enableLog(context);
         log(context, "  [0x%08x] DEBUG_LOC", pos);
         log(context, "  [0x%08x] size = 0x%08x", pos, size);
 
@@ -198,29 +194,17 @@ private static List<LocationListEntry> getLocationListEntries(ClassEntry classEn
             locationListEntries.addAll(getRangeLocationListEntries(primary, base));
             // location list entries for inlined calls
             if (!primary.isLeaf()) {
-                Iterator<Range> iterator = compiledEntry.topDownRangeStream().iterator();
-                while (iterator.hasNext()) {
-                    Range subrange = iterator.next();
-                    if (subrange.isLeaf()) {
-                        continue;
-                    }
-                    locationListEntries.addAll(getRangeLocationListEntries(subrange, base));
-                }
+                compiledEntry.callRangeStream().forEach(subrange -> locationListEntries.addAll(getRangeLocationListEntries(subrange, base)));
             }
         });
         return locationListEntries;
     }
 
     private static List<LocationListEntry> getRangeLocationListEntries(Range range, long base) {
-        List<LocationListEntry> locationListEntries = new ArrayList<>();
-
-        for (Map.Entry<LocalEntry, List<Range>> entry : range.getVarRangeMap().entrySet()) {
-            if (!entry.getValue().isEmpty()) {
-                locationListEntries.add(new LocationListEntry(range, base, entry.getKey(), entry.getValue()));
-            }
-        }
-
-        return locationListEntries;
+        return range.getVarRangeMap().entrySet().stream()
+                        .filter(entry -> !entry.getValue().isEmpty())
+                        .map(entry -> new LocationListEntry(range, base, entry.getKey(), entry.getValue()))
+                        .toList();
     }
 
     private int writeVarLocations(DebugContext context, LocalEntry local, long base, List<Range> rangeList, byte[] buffer, int p) {
@@ -237,25 +221,25 @@ private int writeVarLocations(DebugContext context, LocalEntry local, long base,
         for (LocalValueExtent extent : extents) {
             LocalValueEntry value = extent.value;
             assert (value != null);
-            log(context, "  [0x%08x]     local  %s:%s [0x%x, 0x%x] = %s", pos, value.local().name(), value.local().type().getTypeName(), extent.getLo(), extent.getHi(), formatValue(value));
+            log(context, "  [0x%08x]     local  %s:%s [0x%x, 0x%x] = %s", pos, local.name(), local.type().getTypeName(), extent.getLo(), extent.getHi(), value);
             pos = writeLocationListEntry(DwarfLocationListEntry.DW_LLE_offset_pair, buffer, pos);
             pos = writeULEB(extent.getLo() - base, buffer, pos);
             pos = writeULEB(extent.getHi() - base, buffer, pos);
-            switch (value.localKind()) {
-                case REGISTER:
-                    pos = writeRegisterLocation(context, value.regIndex(), buffer, pos);
+            switch (value) {
+                case RegisterValueEntry registerValueEntry:
+                    pos = writeRegisterLocation(context, registerValueEntry.getRegIndex(), buffer, pos);
                     break;
-                case STACK:
-                    pos = writeStackLocation(context, value.stackSlot(), buffer, pos);
+                case StackValueEntry stackValueEntry:
+                    pos = writeStackLocation(context, stackValueEntry.getStackSlot(), buffer, pos);
                     break;
-                case CONSTANT:
-                    JavaConstant constant = value.constant();
+                case ConstantValueEntry constantValueEntry:
+                    JavaConstant constant = constantValueEntry.getConstant();
                     if (constant instanceof PrimitiveConstant) {
-                        pos = writePrimitiveConstantLocation(context, value.constant(), buffer, pos);
+                        pos = writePrimitiveConstantLocation(context, constant, buffer, pos);
                     } else if (constant.isNull()) {
-                        pos = writeNullConstantLocation(context, value.constant(), buffer, pos);
+                        pos = writeNullConstantLocation(context, constant, buffer, pos);
                     } else {
-                        pos = writeObjectConstantLocation(context, value.constant(), value.heapOffset(), buffer, pos);
+                        pos = writeObjectConstantLocation(context, constant, constantValueEntry.getHeapOffset(), buffer, pos);
                     }
                     break;
                 default:
@@ -631,10 +615,6 @@ public enum DwarfRegEncodingAMD64 implements DwarfRegEncoding {
             this.graalEncoding = graalEncoding;
         }
 
-        public static int graalOrder(DwarfRegEncodingAMD64 e1, DwarfRegEncodingAMD64 e2) {
-            return Integer.compare(e1.graalEncoding, e2.graalEncoding);
-        }
-
         @Override
         public int getDwarfEncoding() {
             return dwarfEncoding;
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfRangesSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfRangesSectionImpl.java
index 2a7bba67ba40..9aeb0fa2918f 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfRangesSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfRangesSectionImpl.java
@@ -26,11 +26,6 @@
 
 package com.oracle.objectfile.elf.dwarf;
 
-import java.util.Map;
-
-import com.oracle.objectfile.LayoutDecision;
-import com.oracle.objectfile.LayoutDecisionMap;
-import com.oracle.objectfile.ObjectFile;
 import com.oracle.objectfile.debugentry.ClassEntry;
 import com.oracle.objectfile.elf.dwarf.constants.DwarfRangeListEntry;
 import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName;
@@ -55,23 +50,6 @@ public void createContent() {
         super.setContent(buffer);
     }
 
-    @Override
-    public byte[] getOrDecideContent(Map<ObjectFile.Element, LayoutDecisionMap> alreadyDecided, byte[] contentHint) {
-        ObjectFile.Element textElement = getElement().getOwner().elementForName(".text");
-        LayoutDecisionMap decisionMap = alreadyDecided.get(textElement);
-        if (decisionMap != null) {
-            Object valueObj = decisionMap.getDecidedValue(LayoutDecision.Kind.VADDR);
-            if (valueObj instanceof Number) {
-                /*
-                 * This may not be the final vaddr for the text segment but it will be close enough
-                 * to make debug easier i.e. to within a 4k page or two.
-                 */
-                debugTextBase = ((Number) valueObj).longValue();
-            }
-        }
-        return super.getOrDecideContent(alreadyDecided, contentHint);
-    }
-
     @Override
     public void writeContent(DebugContext context) {
         assert contentByteArrayCreated();
@@ -80,7 +58,7 @@ public void writeContent(DebugContext context) {
         int size = buffer.length;
         int pos = 0;
 
-        enableLog(context, pos);
+        enableLog(context);
         log(context, "  [0x%08x] DEBUG_RANGES", pos);
         log(context, "  [0x%08x] size = 0x%08x", pos, size);
 
@@ -119,7 +97,7 @@ private int writeRangeListsHeader(byte[] buffer, int p) {
     private int writeRangeLists(DebugContext context, byte[] buffer, int p) {
         Cursor entryCursor = new Cursor(p);
 
-        instanceClassStream().filter(ClassEntry::hasCompiledMethods).forEachOrdered(classEntry -> {
+        instanceClassWithCompilationStream().forEachOrdered(classEntry -> {
             int pos = entryCursor.get();
             setCodeRangesIndex(classEntry, pos);
             /* Write range list for a class */
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java
index f103fffe5f6e..ecc81cd22e2a 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java
@@ -43,7 +43,6 @@
 import com.oracle.objectfile.debugentry.CompiledMethodEntry;
 import com.oracle.objectfile.debugentry.HeaderTypeEntry;
 import com.oracle.objectfile.debugentry.LocalEntry;
-import com.oracle.objectfile.debugentry.LocalValueEntry;
 import com.oracle.objectfile.debugentry.MethodEntry;
 import com.oracle.objectfile.debugentry.PrimitiveTypeEntry;
 import com.oracle.objectfile.debugentry.StructureTypeEntry;
@@ -99,9 +98,7 @@ public int get() {
 
     protected final DwarfDebugInfo dwarfSections;
     protected boolean debug = false;
-    protected long debugTextBase = 0;
     protected long debugAddress = 0;
-    protected int debugBase = 0;
 
     private final ArrayList<Byte> contentBytes = new ArrayList<>();
 
@@ -189,7 +186,7 @@ private String debugSectionLogName() {
         return "dwarf" + getSectionName();
     }
 
-    protected void enableLog(DebugContext context, int pos) {
+    protected void enableLog(DebugContext context) {
         /*
          * Debug output is disabled during the first pass where we size the buffer. this is called
          * to enable it during the second pass where the buffer gets written, but only if the scope
@@ -197,10 +194,8 @@ protected void enableLog(DebugContext context, int pos) {
          */
         assert contentByteArrayCreated();
 
-        if (context.areScopesEnabled()) {
+        if (context.areScopesEnabled() && context.isLogEnabled()) {
             debug = true;
-            debugBase = pos;
-            debugAddress = debugTextBase;
         }
     }
 
@@ -669,15 +664,6 @@ private int writeHeapLocationOffset(long offset, byte[] buffer, int p) {
         return writeHeapOffset(offset, buffer, pos);
     }
 
-    protected static String formatValue(LocalValueEntry value) {
-        return switch (value.localKind()) {
-            case REGISTER -> "REG:" + value.regIndex();
-            case STACK -> "STACK:" + value.stackSlot();
-            case CONSTANT -> "CONST:" + value.constant() + "[" + Long.toHexString(value.heapOffset()) + "]";
-            default -> "-";
-        };
-    }
-
     /**
      * Identify the section after which this debug section needs to be ordered when sizing and
      * creating content.
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfStrSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfStrSectionImpl.java
index 5ddf7f8dc735..3272cef5f88c 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfStrSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfStrSectionImpl.java
@@ -28,6 +28,7 @@
 
 import com.oracle.objectfile.debugentry.StringEntry;
 import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName;
+
 import jdk.graal.compiler.debug.DebugContext;
 
 /**
@@ -61,7 +62,7 @@ public void writeContent(DebugContext context) {
         int size = buffer.length;
         int pos = 0;
 
-        enableLog(context, pos);
+        enableLog(context);
 
         verboseLog(context, " [0x%08x] DEBUG_STR", pos);
         for (StringEntry stringEntry : dwarfSections.getStringTable()) {
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 6f6001756686..d10235677d36 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
@@ -15,6 +15,7 @@
 
 import com.oracle.objectfile.debugentry.ClassEntry;
 import com.oracle.objectfile.debugentry.CompiledMethodEntry;
+import com.oracle.objectfile.debugentry.ConstantValueEntry;
 import com.oracle.objectfile.debugentry.DirEntry;
 import com.oracle.objectfile.debugentry.FieldEntry;
 import com.oracle.objectfile.debugentry.FileEntry;
@@ -24,6 +25,8 @@
 import com.oracle.objectfile.debugentry.LocalEntry;
 import com.oracle.objectfile.debugentry.LocalValueEntry;
 import com.oracle.objectfile.debugentry.MethodEntry;
+import com.oracle.objectfile.debugentry.RegisterValueEntry;
+import com.oracle.objectfile.debugentry.StackValueEntry;
 import com.oracle.objectfile.debugentry.StringTable;
 import com.oracle.objectfile.debugentry.StructureTypeEntry;
 import com.oracle.objectfile.debugentry.TypeEntry;
@@ -833,7 +836,10 @@ private List<LocalValueEntry> initSyntheticInfoList(ParamLocationProducer locPro
             LocalEntry thisParam = methodEntry.getThisParam();
             debug.log(DebugContext.DETAILED_LEVEL, "local[0] %s type %s slot %d", thisParam.name(), thisParam.type().getTypeName(), thisParam.slot());
             debug.log(DebugContext.DETAILED_LEVEL, "  =>  %s kind %s", value, thisParam.kind());
-            localValueInfos.add(createLocalValueEntry(value, PRE_EXTEND_FRAME_SIZE, thisParam));
+            LocalValueEntry localValueEntry = createLocalValueEntry(value, PRE_EXTEND_FRAME_SIZE, thisParam);
+            if (localValueEntry != null) {
+                localValueInfos.add(localValueEntry);
+            }
         }
         // Iterate over all params and create synthetic param info for each
         int paramIdx = 0;
@@ -841,7 +847,10 @@ private List<LocalValueEntry> initSyntheticInfoList(ParamLocationProducer locPro
             JavaValue value = locProducer.paramLocation(paramIdx);
             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 kind %s", value, param.kind());
-            localValueInfos.add(createLocalValueEntry(value, PRE_EXTEND_FRAME_SIZE, param));
+            LocalValueEntry localValueEntry = createLocalValueEntry(value, PRE_EXTEND_FRAME_SIZE, param);
+            if (localValueEntry != null) {
+                localValueInfos.add(localValueEntry);
+            }
             paramIdx++;
         }
         return localValueInfos;
@@ -1093,7 +1102,10 @@ protected List<LocalValueEntry> initLocalInfoList(BytecodePosition pos, MethodEn
                      * upfront from the local variable table, the LocalEntry already exists.
                      */
                     LocalEntry localEntry = method.lookupLocalEntry(name, slot, typeEntry, kind, line);
-                    localInfos.add(createLocalValueEntry(value, frameSize, localEntry));
+                    LocalValueEntry localValueEntry = createLocalValueEntry(value, frameSize, localEntry);
+                    if (localValueEntry != null) {
+                        localInfos.add(localValueEntry);
+                    }
                 } else {
                     debug.log(DebugContext.DETAILED_LEVEL, "  value kind incompatible with var kind %s!", kind);
                 }
@@ -1110,39 +1122,29 @@ protected List<LocalValueEntry> initLocalInfoList(BytecodePosition pos, MethodEn
     }
 
     private LocalValueEntry createLocalValueEntry(JavaValue value, int frameSize, LocalEntry local) {
-        LocalValueKind localKind;
-        int regIndex = -1;
-        int stackSlot = -1;
-        long heapOffset = -1;
-        JavaConstant constant = null;
-
         switch (value) {
             case RegisterValue registerValue -> {
-                localKind = LocalValueKind.REGISTER;
-                regIndex = registerValue.getRegister().number;
+                return new RegisterValueEntry(registerValue.getRegister().number, local);
             }
             case StackSlot stackValue -> {
-                localKind = LocalValueKind.STACK;
-                stackSlot = frameSize == 0 ? stackValue.getRawOffset() : stackValue.getOffset(frameSize);
+                int stackSlot = frameSize == 0 ? stackValue.getRawOffset() : stackValue.getOffset(frameSize);
+                return new StackValueEntry(stackSlot, local);
             }
             case JavaConstant constantValue -> {
                 if (constantValue instanceof PrimitiveConstant || constantValue.isNull()) {
-                    localKind = LocalValueKind.CONSTANT;
-                    constant = constantValue;
+                    return new ConstantValueEntry(-1, constantValue, local);
                 } else {
-                    heapOffset = objectOffset(constantValue);
+                    long heapOffset = objectOffset(constantValue);
                     if (heapOffset >= 0) {
-                        localKind = LocalValueKind.CONSTANT;
-                        constant = constantValue;
-                    } else {
-                        localKind = LocalValueKind.UNDEFINED;
+                        return new ConstantValueEntry(heapOffset, constantValue, local);
                     }
                 }
+                return null;
+            }
+            default -> {
+                return null;
             }
-            case null, default -> localKind = LocalValueKind.UNDEFINED;
         }
-
-        return new LocalValueEntry(regIndex, stackSlot, heapOffset, constant, localKind, local);
     }
 
     public abstract long objectOffset(JavaConstant constant);
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 ef3dba0bc163..5535a5f464a2 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
@@ -53,7 +53,12 @@
 import com.oracle.objectfile.debugentry.EnumClassEntry;
 import com.oracle.objectfile.debugentry.FieldEntry;
 import com.oracle.objectfile.debugentry.FileEntry;
+import com.oracle.objectfile.debugentry.ForeignFloatTypeEntry;
+import com.oracle.objectfile.debugentry.ForeignIntegerTypeEntry;
+import com.oracle.objectfile.debugentry.ForeignPointerTypeEntry;
+import com.oracle.objectfile.debugentry.ForeignStructTypeEntry;
 import com.oracle.objectfile.debugentry.ForeignTypeEntry;
+import com.oracle.objectfile.debugentry.ForeignWordTypeEntry;
 import com.oracle.objectfile.debugentry.InterfaceClassEntry;
 import com.oracle.objectfile.debugentry.LoaderEntry;
 import com.oracle.objectfile.debugentry.LocalEntry;
@@ -546,59 +551,72 @@ protected TypeEntry createTypeEntry(SharedType type) {
             } else {
                 // otherwise this is a class entry
                 ClassEntry superClass = hostedType.getSuperclass() == null ? null : (ClassEntry) lookupTypeEntry(hostedType.getSuperclass());
-                debug.log("typename %s adding super %s%n", typeName, superClass != null ? superClass.getTypeName() : "");
+
+                if (debug.isLogEnabled() && superClass != null) {
+                    debug.log("typename %s adding super %s%n", typeName, superClass.getTypeName());
+                }
+
                 FileEntry fileEntry = lookupFileEntry(hostedType);
                 if (isForeignWordType(hostedType)) {
+                    if (debug.isLogEnabled()) {
+                        logForeignTypeInfo(hostedType);
+                    }
+
                     ElementInfo elementInfo = nativeLibs.findElementInfo(hostedType);
                     SizableInfo.ElementKind elementKind = elementInfo instanceof SizableInfo ? ((SizableInfo) elementInfo).getKind() : null;
                     size = elementSize(elementInfo);
-                    String typedefName = typedefName(elementInfo); // stringTable.uniqueDebugString(typedefName(elementInfo));
-                    boolean isWord = !isForeignPointerType(hostedType);
-                    boolean isStruct = elementInfo instanceof StructInfo;
-                    boolean isPointer = elementKind == SizableInfo.ElementKind.POINTER;
-                    boolean isInteger = elementKind == SizableInfo.ElementKind.INTEGER;
-                    boolean isFloat = elementKind == SizableInfo.ElementKind.FLOAT;
-                    boolean isSigned = nativeLibs.isSigned(hostedType) || (isInteger && !((SizableInfo) elementInfo).isUnsigned());
-
-                    ForeignTypeEntry parentEntry = null;
-                    if (isStruct) {
+
+                    if (!isForeignPointerType(hostedType)) {
+                        boolean isSigned = nativeLibs.isSigned(hostedType);
+                        return new ForeignWordTypeEntry(typeName, size, classOffset, typeSignature, layoutTypeSignature,
+                                        superClass, fileEntry, loaderEntry, isSigned);
+                    } else if (elementInfo instanceof StructInfo) {
                         // look for the first interface that also has an associated StructInfo
+                        String typedefName = typedefName(elementInfo); // stringTable.uniqueDebugString(typedefName(elementInfo));
+                        ForeignStructTypeEntry parentEntry = null;
                         for (HostedInterface hostedInterface : hostedType.getInterfaces()) {
                             ElementInfo otherInfo = nativeLibs.findElementInfo(hostedInterface);
                             if (otherInfo instanceof StructInfo) {
-                                parentEntry = (ForeignTypeEntry) lookupTypeEntry(hostedInterface);
+                                parentEntry = (ForeignStructTypeEntry) lookupTypeEntry(hostedInterface);
                             }
                         }
-                    }
-
-                    TypeEntry pointerToEntry = null;
-                    if (isPointer) {
-                        // any target type for the pointer will be defined by a CPointerTo or
-                        // RawPointerTo
-                        // annotation
-                        CPointerTo cPointerTo = type.getAnnotation(CPointerTo.class);
-                        if (cPointerTo != null) {
-                            HostedType pointerTo = heap.hMetaAccess.lookupJavaType(cPointerTo.value());
-                            pointerToEntry = lookupTypeEntry(pointerTo);
-                        }
-                        RawPointerTo rawPointerTo = type.getAnnotation(RawPointerTo.class);
-                        if (rawPointerTo != null) {
-                            HostedType pointerTo = heap.hMetaAccess.lookupJavaType(rawPointerTo.value());
-                            pointerToEntry = lookupTypeEntry(pointerTo);
-                        }
+                        return new ForeignStructTypeEntry(typeName, size, classOffset, typeSignature, layoutTypeSignature, superClass, fileEntry, loaderEntry, typedefName, parentEntry);
+                    } else if (elementKind == SizableInfo.ElementKind.INTEGER) {
+                        boolean isSigned = nativeLibs.isSigned(hostedType) || !((SizableInfo) elementInfo).isUnsigned();
+                        return new ForeignIntegerTypeEntry(typeName, size, classOffset, typeSignature, layoutTypeSignature, superClass, fileEntry, loaderEntry, isSigned);
+                    } else if (elementKind == SizableInfo.ElementKind.FLOAT) {
+                        return new ForeignFloatTypeEntry(typeName, size, classOffset, typeSignature, layoutTypeSignature, superClass, fileEntry, loaderEntry);
+                    } else {
+                        // This must be a pointer. If the target type is known use it to declare the
+                        // pointer
+                        // type, otherwise default to 'void *'
+                        TypeEntry pointerToEntry = null;
+                        if (elementKind == SizableInfo.ElementKind.POINTER) {
+                            // any target type for the pointer will be defined by a CPointerTo or
+                            // RawPointerTo
+                            // annotation
+                            CPointerTo cPointerTo = type.getAnnotation(CPointerTo.class);
+                            if (cPointerTo != null) {
+                                HostedType pointerTo = heap.hMetaAccess.lookupJavaType(cPointerTo.value());
+                                pointerToEntry = lookupTypeEntry(pointerTo);
+                            }
+                            RawPointerTo rawPointerTo = type.getAnnotation(RawPointerTo.class);
+                            if (rawPointerTo != null) {
+                                HostedType pointerTo = heap.hMetaAccess.lookupJavaType(rawPointerTo.value());
+                                pointerToEntry = lookupTypeEntry(pointerTo);
+                            }
 
-                        if (pointerToEntry != null) {
-                            debug.log("foreign type %s referent %s ", typeName, pointerToEntry.getTypeName());
-                        } else {
-                            debug.log("foreign type %s", typeName);
+                            if (pointerToEntry != null) {
+                                debug.log("foreign type %s referent %s ", typeName, pointerToEntry.getTypeName());
+                            } else {
+                                debug.log("foreign type %s", typeName);
+                            }
                         }
-                    }
 
-                    if (!isStruct && !isWord && !isInteger && !isFloat) {
-                        // set pointer to type as alias for the layout type
                         if (pointerToEntry == null) {
                             pointerToEntry = lookupTypeEntry(voidType);
                         }
+
                         if (pointerToEntry != null) {
                             /*
                              * Setting the layout type to the type we point to reuses an available
@@ -606,15 +624,9 @@ protected TypeEntry createTypeEntry(SharedType type) {
                              */
                             layoutTypeSignature = pointerToEntry.getTypeSignature();
                         }
-                    }
 
-                    if (debug.isLogEnabled()) {
-                        logForeignTypeInfo(hostedType);
+                        return new ForeignPointerTypeEntry(typeName, size, classOffset, typeSignature, layoutTypeSignature, superClass, fileEntry, loaderEntry, pointerToEntry);
                     }
-
-                    return new ForeignTypeEntry(typeName, size, classOffset, typeSignature, layoutTypeSignature,
-                                    superClass, fileEntry, loaderEntry, typedefName, parentEntry, pointerToEntry, isWord,
-                                    isStruct, isPointer, isInteger, isSigned, isFloat);
                 } else if (hostedType.isEnum()) {
                     return new EnumClassEntry(typeName, size, classOffset, typeSignature, compressedTypeSignature,
                                     layoutTypeSignature, superClass, fileEntry, loaderEntry);

From bce21b16b4e6ee432ffba3c4d742e328aacb949e Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Tue, 10 Dec 2024 20:45:42 +0100
Subject: [PATCH 12/34] Cleanup, Refactoring and some Bugfixes

---
 substratevm/mx.substratevm/testhello.py       | 16 ++--
 .../debugentry/ConstantValueEntry.java        | 36 +------
 .../objectfile/debugentry/LocalEntry.java     |  4 +-
 .../debugentry/LocalValueEntry.java           | 11 +--
 .../objectfile/debugentry/MethodEntry.java    | 30 ++++--
 .../debugentry/RegisterValueEntry.java        | 30 +-----
 .../debugentry/StackValueEntry.java           | 29 +-----
 .../objectfile/debugentry/range/Range.java    | 52 ++++-------
 .../elf/dwarf/DwarfLocSectionImpl.java        |  8 +-
 .../core/debug/SharedDebugInfoProvider.java   | 93 ++++++++++---------
 10 files changed, 106 insertions(+), 203 deletions(-)

diff --git a/substratevm/mx.substratevm/testhello.py b/substratevm/mx.substratevm/testhello.py
index c81fc7c4e560..b38b7c3a8e40 100644
--- a/substratevm/mx.substratevm/testhello.py
+++ b/substratevm/mx.substratevm/testhello.py
@@ -147,25 +147,25 @@ def test():
 
     # check incoming parameters are bound to sensible values
     exec_string = execute("info args")
-    rexp = [fr"__0 = {digits_pattern}",
-            fr"__1 = 0x{hex_digits_pattern}"]
+    rexp = [fr"__int0 = {digits_pattern}",
+            fr"__long1 = 0x{hex_digits_pattern}"]
     checker = Checker(f"info args : {method_name}", rexp)
     checker.check(exec_string)
 
-    exec_string = execute("p __0")
+    exec_string = execute("p __int0")
     rexp = [fr"\${digits_pattern} = 1"]
-    checker = Checker("p __0", rexp)
+    checker = Checker("p __int0", rexp)
     checker.check(exec_string)
 
-    exec_string = execute("p __1")
+    exec_string = execute("p __long1")
     rexp = [fr"\${digits_pattern} = \(org\.graalvm\.nativeimage\.c\.type\.CCharPointerPointer\) 0x{hex_digits_pattern}"]
-    checker = Checker("p __1", rexp)
+    checker = Checker("p __long1", rexp)
     checker.check(exec_string)
 
-    exec_string = execute("p __1[0]")
+    exec_string = execute("p __long1[0]")
     rexp = [
         fr'\${digits_pattern} = \(org\.graalvm\.nativeimage\.c\.type\.CCharPointer\) 0x{hex_digits_pattern} "{wildcard_pattern}/hello_image"']
-    checker = Checker("p __1[0]", rexp)
+    checker = Checker("p __long1[0]", rexp)
     checker.check(exec_string)
 
     # set a break point at hello.Hello::main
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ConstantValueEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ConstantValueEntry.java
index 7cc67d3f59f0..edec954db932 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ConstantValueEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ConstantValueEntry.java
@@ -1,45 +1,11 @@
 package com.oracle.objectfile.debugentry;
 
-import java.util.Objects;
-
 import jdk.vm.ci.meta.JavaConstant;
 
-public class ConstantValueEntry extends LocalValueEntry {
-    private final long heapOffset;
-    private final JavaConstant constant;
-
-    public ConstantValueEntry(long heapOffset, JavaConstant constant, LocalEntry local) {
-        super(local);
-        this.heapOffset = heapOffset;
-        this.constant = constant;
-    }
+public record ConstantValueEntry(long heapOffset, JavaConstant constant) implements LocalValueEntry {
 
     @Override
     public String toString() {
         return "CONST:" + (constant != null ? constant.toValueString() : "null") + "[" + Long.toHexString(heapOffset) + "]";
     }
-
-    public JavaConstant getConstant() {
-        return constant;
-    }
-
-    public long getHeapOffset() {
-        return heapOffset;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj == this)
-            return true;
-        if (obj == null || obj.getClass() != this.getClass())
-            return false;
-        var that = (ConstantValueEntry) obj;
-        return this.heapOffset == that.heapOffset &&
-                        Objects.equals(this.constant, that.constant);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(heapOffset, constant);
-    }
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalEntry.java
index c698d3c0d1bf..a97eaf26d21a 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalEntry.java
@@ -1,8 +1,6 @@
 package com.oracle.objectfile.debugentry;
 
-import jdk.vm.ci.meta.JavaKind;
-
-public record LocalEntry(String name, TypeEntry type, JavaKind kind, int slot, int line) {
+public record LocalEntry(String name, TypeEntry type, int slot, int line) {
 
     @Override
     public String toString() {
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalValueEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalValueEntry.java
index dafdf017f896..4daf7802999c 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalValueEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalValueEntry.java
@@ -1,13 +1,4 @@
 package com.oracle.objectfile.debugentry;
 
-public abstract class LocalValueEntry {
-    private final LocalEntry local;
-
-    protected LocalValueEntry(LocalEntry local) {
-        this.local = local;
-    }
-
-    public LocalEntry getLocal() {
-        return local;
-    }
+public interface LocalValueEntry {
 }
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 76f71fe2e68b..c7966a7dabec 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
@@ -28,12 +28,10 @@
 
 import java.util.List;
 
-import jdk.vm.ci.meta.JavaKind;
-
 public class MethodEntry extends MemberEntry {
     private final LocalEntry thisParam;
     private final List<LocalEntry> paramInfos;
-    private final int firstLocalSlot;
+    private final int lastParamSlot;
     // local vars are accumulated as they are referenced sorted by slot, then name, then
     // type name. we don't currently deal handle references to locals with no slot.
     private final List<LocalEntry> locals;
@@ -49,7 +47,7 @@ public class MethodEntry extends MemberEntry {
     public MethodEntry(FileEntry fileEntry, int line, String methodName, StructureTypeEntry ownerType,
                     TypeEntry valueType, int modifiers, List<LocalEntry> paramInfos, LocalEntry thisParam,
                     String symbolName, boolean isDeopt, boolean isOverride, boolean isConstructor, int vtableOffset,
-                    int firstLocalSlot, List<LocalEntry> locals) {
+                    int lastParamSlot, List<LocalEntry> locals) {
         super(fileEntry, line, methodName, ownerType, valueType, modifiers);
         this.paramInfos = paramInfos;
         this.thisParam = thisParam;
@@ -58,7 +56,7 @@ public MethodEntry(FileEntry fileEntry, int line, String methodName, StructureTy
         this.isOverride = isOverride;
         this.isConstructor = isConstructor;
         this.vtableOffset = vtableOffset;
-        this.firstLocalSlot = firstLocalSlot;
+        this.lastParamSlot = lastParamSlot;
         this.locals = locals;
 
         this.isInRange = false;
@@ -102,12 +100,12 @@ public LocalEntry getThisParam() {
         return thisParam;
     }
 
-    public LocalEntry lookupLocalEntry(String name, int slot, TypeEntry type, JavaKind kind, int line) {
+    public LocalEntry lookupLocalEntry(String name, int slot, TypeEntry type, int line) {
         if (slot < 0) {
             return null;
         }
 
-        if (slot < firstLocalSlot) {
+        if (slot <= lastParamSlot) {
             if (thisParam != null) {
                 if (thisParam.slot() == slot && thisParam.name().equals(name) && thisParam.type() == type) {
                     return thisParam;
@@ -125,7 +123,7 @@ public LocalEntry lookupLocalEntry(String name, int slot, TypeEntry type, JavaKi
                 }
             }
 
-            LocalEntry local = new LocalEntry(name, type, kind, slot, line);
+            LocalEntry local = new LocalEntry(name, type, slot, line);
             synchronized (locals) {
                 if (!locals.contains(local)) {
                     locals.add(local);
@@ -134,6 +132,14 @@ public LocalEntry lookupLocalEntry(String name, int slot, TypeEntry type, JavaKi
             return local;
         }
 
+        /*
+         * The slot is within the range of the params, but none of the params exactly match. This
+         * might be some local value that is stored in a slot where we expect a param. We just
+         * ignore such values for now.
+         *
+         * This also applies to params that are inferred from frame values, as the types do not
+         * match most of the time.
+         */
         return null;
     }
 
@@ -141,6 +147,14 @@ public List<LocalEntry> getLocals() {
         return List.copyOf(locals);
     }
 
+    public int getLastParamSlot() {
+        return lastParamSlot;
+    }
+
+    public boolean isStatic() {
+        return thisParam == null;
+    }
+
     public boolean isDeopt() {
         return isDeopt;
     }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/RegisterValueEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/RegisterValueEntry.java
index 4a7208e1e585..e0fdc98780f9 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/RegisterValueEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/RegisterValueEntry.java
@@ -1,37 +1,9 @@
 package com.oracle.objectfile.debugentry;
 
-import java.util.Objects;
-
-public class RegisterValueEntry extends LocalValueEntry {
-
-    private final int regIndex;
-
-    public RegisterValueEntry(int regIndex, LocalEntry local) {
-        super(local);
-        this.regIndex = regIndex;
-    }
+public record RegisterValueEntry(int regIndex) implements LocalValueEntry {
 
     @Override
     public String toString() {
         return "REG:" + regIndex;
     }
-
-    public int getRegIndex() {
-        return regIndex;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj == this)
-            return true;
-        if (obj == null || obj.getClass() != this.getClass())
-            return false;
-        var that = (RegisterValueEntry) obj;
-        return this.regIndex == that.regIndex;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(regIndex);
-    }
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StackValueEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StackValueEntry.java
index 57b66c0ec90b..e7ae72bab599 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StackValueEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StackValueEntry.java
@@ -1,36 +1,9 @@
 package com.oracle.objectfile.debugentry;
 
-import java.util.Objects;
-
-public class StackValueEntry extends LocalValueEntry {
-    private final int stackSlot;
-
-    public StackValueEntry(int stackSlot, LocalEntry local) {
-        super(local);
-        this.stackSlot = stackSlot;
-    }
+public record StackValueEntry(int stackSlot) implements LocalValueEntry {
 
     @Override
     public String toString() {
         return "STACK:" + stackSlot;
     }
-
-    public int getStackSlot() {
-        return stackSlot;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj == this)
-            return true;
-        if (obj == null || obj.getClass() != this.getClass())
-            return false;
-        var that = (StackValueEntry) obj;
-        return this.stackSlot == that.stackSlot;
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(stackSlot);
-    }
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java
index 7740088ea557..053ed7f2e007 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java
@@ -27,7 +27,6 @@
 package com.oracle.objectfile.debugentry.range;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -96,7 +95,7 @@ public abstract class Range {
      * Values for the associated method's local and parameter variables that are available or,
      * alternatively, marked as invalid in this range.
      */
-    private final List<LocalValueEntry> localValueInfos = new ArrayList<>();
+    private final Map<LocalEntry, LocalValueEntry> localValueInfos = new HashMap<>();
 
     /**
      * Create a primary range representing the root of the subrange tree for a top level compiled
@@ -166,19 +165,20 @@ public Range split(int stackDecrement, int frameSize, int preExtendFrameSize) {
         this.caller.removeCallee(newRange);
         this.caller.insertCallee(newRange, 1);
 
-        for (LocalValueEntry localInfo : this.localValueInfos) {
-            if (localInfo instanceof StackValueEntry stackValue) {
+        for (var localInfo : this.localValueInfos.entrySet()) {
+            if (localInfo.getValue() instanceof StackValueEntry stackValue) {
                 // need to redefine the value for this param using a stack slot value
                 // that allows for the stack being extended by framesize. however we
                 // also need to remove any adjustment that was made to allow for the
                 // difference between the caller SP and the pre-extend callee SP
                 // because of a stacked return address.
                 int adjustment = frameSize - preExtendFrameSize;
-                newRange.localValueInfos.add(new StackValueEntry(stackValue.getStackSlot() + adjustment, stackValue.getLocal()));
+                newRange.localValueInfos.put(localInfo.getKey(), new StackValueEntry(stackValue.stackSlot() + adjustment));
             } else {
-                newRange.localValueInfos.add(localInfo);
+                newRange.localValueInfos.put(localInfo.getKey(), localInfo.getValue());
             }
         }
+
         return newRange;
     }
 
@@ -312,10 +312,8 @@ public int getDepth() {
     public Map<LocalEntry, List<Range>> getVarRangeMap() {
         HashMap<LocalEntry, List<Range>> varRangeMap = new HashMap<>();
         for (Range callee : getCallees()) {
-            for (LocalValueEntry localValue : callee.localValueInfos) {
-                if (localValue != null && localValue.getLocal() != null) {
-                    varRangeMap.computeIfAbsent(localValue.getLocal(), l -> new ArrayList<>()).add(callee);
-                }
+            for (LocalEntry local : callee.localValueInfos.keySet()) {
+                varRangeMap.computeIfAbsent(local, l -> new ArrayList<>()).add(callee);
             }
         }
         return varRangeMap;
@@ -323,16 +321,15 @@ public Map<LocalEntry, List<Range>> getVarRangeMap() {
 
     public boolean hasLocalValues(LocalEntry local) {
         for (Range callee : getCallees()) {
-            for (LocalValueEntry localValue : callee.localValueInfos) {
-                if (localValue != null && localValue.getLocal() == local) {
-                    if (localValue instanceof ConstantValueEntry constantValueEntry) {
-                        JavaConstant constant = constantValueEntry.getConstant();
-                        // can only handle primitive or null constants just now
-                        return constant instanceof PrimitiveConstant || constant.getJavaKind() == JavaKind.Object;
-                    } else {
-                        // register or stack value
-                        return true;
-                    }
+            LocalValueEntry localValue = callee.lookupValue(local);
+            if (localValue != null) {
+                if (localValue instanceof ConstantValueEntry constantValueEntry) {
+                    JavaConstant constant = constantValueEntry.constant();
+                    // can only handle primitive or null constants just now
+                    return constant instanceof PrimitiveConstant || constant.getJavaKind() == JavaKind.Object;
+                } else {
+                    // register or stack value
+                    return true;
                 }
             }
         }
@@ -343,21 +340,12 @@ public Range getCaller() {
         return caller;
     }
 
-    public List<LocalValueEntry> getLocalValues() {
-        return Collections.unmodifiableList(localValueInfos);
-    }
-
-    public void setLocalValueInfo(List<LocalValueEntry> localValueInfos) {
-        this.localValueInfos.addAll(localValueInfos);
+    public void setLocalValueInfo(Map<LocalEntry, LocalValueEntry> localValueInfos) {
+        this.localValueInfos.putAll(localValueInfos);
     }
 
     public LocalValueEntry lookupValue(LocalEntry local) {
-        for (LocalValueEntry localValue : localValueInfos) {
-            if (localValue.getLocal() == local) {
-                return localValue;
-            }
-        }
-        return null;
+        return localValueInfos.getOrDefault(local, null);
     }
 
     public boolean tryMerge(Range that) {
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLocSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLocSectionImpl.java
index f7535209b148..f34785e52799 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLocSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLocSectionImpl.java
@@ -227,19 +227,19 @@ private int writeVarLocations(DebugContext context, LocalEntry local, long base,
             pos = writeULEB(extent.getHi() - base, buffer, pos);
             switch (value) {
                 case RegisterValueEntry registerValueEntry:
-                    pos = writeRegisterLocation(context, registerValueEntry.getRegIndex(), buffer, pos);
+                    pos = writeRegisterLocation(context, registerValueEntry.regIndex(), buffer, pos);
                     break;
                 case StackValueEntry stackValueEntry:
-                    pos = writeStackLocation(context, stackValueEntry.getStackSlot(), buffer, pos);
+                    pos = writeStackLocation(context, stackValueEntry.stackSlot(), buffer, pos);
                     break;
                 case ConstantValueEntry constantValueEntry:
-                    JavaConstant constant = constantValueEntry.getConstant();
+                    JavaConstant constant = constantValueEntry.constant();
                     if (constant instanceof PrimitiveConstant) {
                         pos = writePrimitiveConstantLocation(context, constant, buffer, pos);
                     } else if (constant.isNull()) {
                         pos = writeNullConstantLocation(context, constant, buffer, pos);
                     } else {
-                        pos = writeObjectConstantLocation(context, constant, constantValueEntry.getHeapOffset(), buffer, pos);
+                        pos = writeObjectConstantLocation(context, constant, constantValueEntry.heapOffset(), buffer, pos);
                     }
                     break;
                 default:
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 d10235677d36..9399e8046af1 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
@@ -5,7 +5,9 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.Optional;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Stream;
@@ -325,8 +327,6 @@ static boolean debugCodeInfoUseSourceMappings() {
 
     protected CompiledMethodEntry installCompilationInfo(MethodEntry methodEntry, SharedMethod method, CompilationResult compilation) {
         try (DebugContext.Scope s = debug.scope("DebugInfoCompilation")) {
-            debug.log("test");
-
             methodEntry.setInRange();
 
             int primaryLine = methodEntry.getLine();
@@ -404,7 +404,6 @@ public List<FrameSizeChangeEntry> getFrameSizeChanges(CompilationResult compilat
 
     protected MethodEntry installMethodEntry(SharedMethod method) {
         try (DebugContext.Scope s = debug.scope("DebugInfoMethod")) {
-            debug.log("test");
             FileEntry fileEntry = lookupFileEntry(method);
 
             LineNumberTable lineNumberTable = method.getLineNumberTable();
@@ -420,11 +419,11 @@ protected MethodEntry installMethodEntry(SharedMethod method) {
             // if the params are not in the table, we create synthetic ones from the method
             // signature
             List<LocalEntry> paramInfos = getParamEntries(method, line);
-            int firstLocalSlot = paramInfos.isEmpty() ? 0 : paramInfos.getLast().slot() + paramInfos.getLast().kind().getSlotCount();
+            int lastParamSlot = paramInfos.isEmpty() ? -1 : paramInfos.getLast().slot();
             LocalEntry thisParam = Modifier.isStatic(modifiers) ? null : paramInfos.removeFirst();
 
             // look for locals in the methods local variable table
-            List<LocalEntry> locals = getLocalEntries(method, firstLocalSlot);
+            List<LocalEntry> locals = getLocalEntries(method, lastParamSlot);
 
             String symbolName = getSymbolName(method); // stringTable.uniqueDebugString(getSymbolName(method));
             int vTableOffset = getVTableOffset(method);
@@ -435,7 +434,7 @@ protected MethodEntry installMethodEntry(SharedMethod method) {
 
             return methodIndex.computeIfAbsent(method, m -> new MethodEntry(fileEntry, line, methodName, ownerType,
                             valueType, modifiers, paramInfos, thisParam, symbolName, isDeopt, isOverride, isConstructor,
-                            vTableOffset, firstLocalSlot, locals));
+                            vTableOffset, lastParamSlot, locals));
         } catch (Throwable e) {
             throw debug.handle(e);
         }
@@ -515,7 +514,7 @@ public String getMethodName(SharedMethod method) {
         return name;
     }
 
-    public List<LocalEntry> getLocalEntries(SharedMethod method, int firstLocalSlot) {
+    public List<LocalEntry> getLocalEntries(SharedMethod method, int lastParamSlot) {
         ArrayList<LocalEntry> localEntries = new ArrayList<>();
 
         LineNumberTable lnt = method.getLineNumberTable();
@@ -529,12 +528,11 @@ public List<LocalEntry> getLocalEntries(SharedMethod method, int firstLocalSlot)
         SharedType ownerType = (SharedType) method.getDeclaringClass();
         for (Local local : lvt.getLocals()) {
             // check if this is a local (slot is after last param slot)
-            if (local != null && local.getSlot() >= firstLocalSlot) {
+            if (local != null && local.getSlot() > lastParamSlot) {
                 // we have a local with a known name, type and slot
                 String name = local.getName(); // stringTable.uniqueDebugString(l.getName());
                 SharedType type = (SharedType) local.getType().resolve(ownerType);
                 int slot = local.getSlot();
-                JavaKind storageKind = type.getStorageKind();
                 int bciStart = local.getStartBCI();
                 int line = lnt == null ? 0 : lnt.getLineNumber(bciStart);
                 TypeEntry typeEntry = lookupTypeEntry(type);
@@ -543,7 +541,7 @@ public List<LocalEntry> getLocalEntries(SharedMethod method, int firstLocalSlot)
                                 .filter(le -> le.slot() == slot && le.type() == typeEntry && le.name().equals(name))
                                 .findFirst();
 
-                LocalEntry localEntry = new LocalEntry(name, typeEntry, storageKind, slot, line);
+                LocalEntry localEntry = new LocalEntry(name, typeEntry, slot, line);
                 if (existingEntry.isEmpty()) {
                     localEntries.add(localEntry);
                 } else if (existingEntry.get().line() > line) {
@@ -565,22 +563,19 @@ public List<LocalEntry> getParamEntries(SharedMethod method, int line) {
         SharedType ownerType = (SharedType) method.getDeclaringClass();
         if (!method.isStatic()) {
             JavaKind kind = ownerType.getJavaKind();
-            JavaKind storageKind = ownerType.getStorageKind(); // isForeignWordType(ownerType,
-                                                               // ownerType) ? JavaKind.Long : kind;
+            JavaKind storageKind = ownerType.getStorageKind();
             assert kind == JavaKind.Object : "must be an object";
-            paramInfos.add(new LocalEntry("this", lookupTypeEntry(ownerType), storageKind, slot, line));
+            paramInfos.add(new LocalEntry("this", lookupTypeEntry(ownerType), slot, line));
             slot += kind.getSlotCount();
         }
         for (int i = 0; i < parameterCount; i++) {
             Local local = table == null ? null : table.getLocal(slot, 0);
-            String name = local != null ? local.getName() : "__" + i; // stringTable.uniqueDebugString(local
-                                                                      // != null ? local.getName() :
-                                                                      // "__" + i);
             SharedType paramType = (SharedType) signature.getParameterType(i, null);
             JavaKind kind = paramType.getJavaKind();
-            JavaKind storageKind = paramType.getStorageKind(); // isForeignWordType(paramType,
-                                                               // ownerType) ? JavaKind.Long : kind;
-            paramInfos.add(new LocalEntry(name, lookupTypeEntry(paramType), storageKind, slot, line));
+            JavaKind storageKind = paramType.getStorageKind();
+            String name = local != null ? local.getName() : "__" + storageKind.getJavaName() + i;
+            // stringTable.uniqueDebugString(local != null ? local.getName() : "__" + i);
+            paramInfos.add(new LocalEntry(name, lookupTypeEntry(paramType), slot, line));
             slot += kind.getSlotCount();
         }
         return paramInfos;
@@ -828,17 +823,17 @@ private void updateInitialLocation(PrimaryRange primary, List<Range> locationInf
         locationInfos.addFirst(locationInfo);
     }
 
-    private List<LocalValueEntry> initSyntheticInfoList(ParamLocationProducer locProducer, MethodEntry methodEntry) {
-        ArrayList<LocalValueEntry> localValueInfos = new ArrayList<>();
+    private Map<LocalEntry, LocalValueEntry> initSyntheticInfoList(ParamLocationProducer locProducer, MethodEntry methodEntry) {
+        HashMap<LocalEntry, LocalValueEntry> localValueInfos = new HashMap<>();
         // Create synthetic this param info
         if (methodEntry.getThisParam() != null) {
             JavaValue value = locProducer.thisLocation();
             LocalEntry thisParam = methodEntry.getThisParam();
             debug.log(DebugContext.DETAILED_LEVEL, "local[0] %s type %s slot %d", thisParam.name(), thisParam.type().getTypeName(), thisParam.slot());
-            debug.log(DebugContext.DETAILED_LEVEL, "  =>  %s kind %s", value, thisParam.kind());
-            LocalValueEntry localValueEntry = createLocalValueEntry(value, PRE_EXTEND_FRAME_SIZE, thisParam);
+            debug.log(DebugContext.DETAILED_LEVEL, "  =>  %s", value);
+            LocalValueEntry localValueEntry = createLocalValueEntry(value, PRE_EXTEND_FRAME_SIZE);
             if (localValueEntry != null) {
-                localValueInfos.add(localValueEntry);
+                localValueInfos.put(thisParam, localValueEntry);
             }
         }
         // Iterate over all params and create synthetic param info for each
@@ -846,10 +841,10 @@ private List<LocalValueEntry> initSyntheticInfoList(ParamLocationProducer locPro
         for (LocalEntry param : methodEntry.getParams()) {
             JavaValue value = locProducer.paramLocation(paramIdx);
             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 kind %s", value, param.kind());
-            LocalValueEntry localValueEntry = createLocalValueEntry(value, PRE_EXTEND_FRAME_SIZE, param);
+            debug.log(DebugContext.DETAILED_LEVEL, "  =>  %s", value);
+            LocalValueEntry localValueEntry = createLocalValueEntry(value, PRE_EXTEND_FRAME_SIZE);
             if (localValueEntry != null) {
-                localValueInfos.add(localValueEntry);
+                localValueInfos.put(param, localValueEntry);
             }
             paramIdx++;
         }
@@ -1026,8 +1021,8 @@ public Range process(CompilationResultFrameTree.FrameNode node, CallRange caller
         }
     }
 
-    protected List<LocalValueEntry> initLocalInfoList(BytecodePosition pos, MethodEntry method, int frameSize) {
-        List<LocalValueEntry> localInfos = new ArrayList<>();
+    protected Map<LocalEntry, LocalValueEntry> initLocalInfoList(BytecodePosition pos, MethodEntry method, int frameSize) {
+        Map<LocalEntry, LocalValueEntry> localInfos = new HashMap<>();
 
         if (pos instanceof BytecodeFrame frame && frame.numLocals > 0) {
             /*
@@ -1070,11 +1065,23 @@ protected List<LocalValueEntry> initLocalInfoList(BytecodePosition pos, MethodEn
                 String name;
                 SharedType type;
                 if (local == null) {
+                    if (method.getLastParamSlot() >= slot) {
+                        /*
+                         * If we e.g. get an int from the frame values can we really be sure that
+                         * this is a param and not just any other local value that happens to be an
+                         * int?
+                         *
+                         * Better just skip inferring params if we have no local in the local
+                         * variable table.
+                         */
+                        continue;
+                    }
+
                     /*
                      * We don't have a corresponding local in the local variable table. Collect some
                      * usable information for this local from the frame local kind.
                      */
-                    name = storageKind.getTypeChar() + "_" + slot;
+                    name = "__" + storageKind.getJavaName() + (method.isStatic() ? slot : slot - 1);
                     Class<?> clazz = storageKind.isObject() ? Object.class : storageKind.toJavaClass();
                     type = (SharedType) metaAccess.lookupJavaType(clazz);
                 } else {
@@ -1092,7 +1099,7 @@ protected List<LocalValueEntry> initLocalInfoList(BytecodePosition pos, MethodEn
                 JavaKind kind = type.getJavaKind();
 
                 debug.log(DebugContext.DETAILED_LEVEL, "local %s type %s slot %d", name, typeEntry.getTypeName(), slot);
-                debug.log(DebugContext.DETAILED_LEVEL, "  =>  %s kind %s", value, storageKind);
+                debug.log(DebugContext.DETAILED_LEVEL, "  =>  %s", value);
 
                 // Double-check the kind from the frame local value with the kind from the local
                 // variable table.
@@ -1101,42 +1108,36 @@ protected List<LocalValueEntry> initLocalInfoList(BytecodePosition pos, MethodEn
                      * Lookup a LocalEntry from the MethodEntry. If the LocalEntry was already read
                      * upfront from the local variable table, the LocalEntry already exists.
                      */
-                    LocalEntry localEntry = method.lookupLocalEntry(name, slot, typeEntry, kind, line);
-                    LocalValueEntry localValueEntry = createLocalValueEntry(value, frameSize, localEntry);
-                    if (localValueEntry != null) {
-                        localInfos.add(localValueEntry);
+                    LocalEntry localEntry = method.lookupLocalEntry(name, slot, typeEntry, line);
+                    LocalValueEntry localValueEntry = createLocalValueEntry(value, frameSize);
+                    if (localEntry != null && localValueEntry != null) {
+                        localInfos.put(localEntry, localValueEntry);
                     }
                 } else {
                     debug.log(DebugContext.DETAILED_LEVEL, "  value kind incompatible with var kind %s!", kind);
                 }
-
-                // No need to check the next slot if the current value needs two slots (next slot
-                // contains an illegal value which is skipped anyway).
-                if (storageKind.needsTwoSlots()) {
-                    slot++;
-                }
             }
         }
 
         return localInfos;
     }
 
-    private LocalValueEntry createLocalValueEntry(JavaValue value, int frameSize, LocalEntry local) {
+    private LocalValueEntry createLocalValueEntry(JavaValue value, int frameSize) {
         switch (value) {
             case RegisterValue registerValue -> {
-                return new RegisterValueEntry(registerValue.getRegister().number, local);
+                return new RegisterValueEntry(registerValue.getRegister().number);
             }
             case StackSlot stackValue -> {
                 int stackSlot = frameSize == 0 ? stackValue.getRawOffset() : stackValue.getOffset(frameSize);
-                return new StackValueEntry(stackSlot, local);
+                return new StackValueEntry(stackSlot);
             }
             case JavaConstant constantValue -> {
                 if (constantValue instanceof PrimitiveConstant || constantValue.isNull()) {
-                    return new ConstantValueEntry(-1, constantValue, local);
+                    return new ConstantValueEntry(-1, constantValue);
                 } else {
                     long heapOffset = objectOffset(constantValue);
                     if (heapOffset >= 0) {
-                        return new ConstantValueEntry(heapOffset, constantValue, local);
+                        return new ConstantValueEntry(heapOffset, constantValue);
                     }
                 }
                 return null;

From e6c2104caddcafcabfa4c7abc79f804d1e7df847 Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Thu, 12 Dec 2024 16:50:57 +0100
Subject: [PATCH 13/34] More Code Cleanup, Remove unused code, merge BFD Name
 providers

---
 .../objectfile/debugentry/DebugInfoBase.java  |   8 -
 .../debugentry/range/CallRange.java           |  38 +-
 .../debugentry/range/LeafRange.java           |   1 -
 .../objectfile/debugentry/range/Range.java    |   6 +-
 .../debuginfo/DebugInfoProvider.java          |   9 -
 .../svm/core/debug/BFDNameProvider.java}      |  70 +-
 .../svm/core/debug/GDBJITInterface.java       | 123 ++-
 .../core/debug/GDBJITInterfaceSystemJava.java | 158 ----
 .../core/debug/SharedDebugInfoProvider.java   |  99 +-
 .../core/debug/SubstrateBFDNameProvider.java  | 882 ------------------
 .../debug/SubstrateDebugInfoInstaller.java    |  16 +-
 .../debug/SubstrateDebugInfoProvider.java     |   3 +-
 .../image/NativeImageDebugInfoFeature.java    |  78 +-
 .../image/NativeImageDebugInfoProvider.java   |   7 +-
 .../svm/hosted/image/NativeImageViaCC.java    |   2 +-
 ...face.h => gdb_jit_compilation_interface.h} |   5 -
 .../src/gdbJITCompilationInterface.c          |  61 --
 .../src/gdb_jit_compilation_interface.c       |   7 +
 18 files changed, 285 insertions(+), 1288 deletions(-)
 rename substratevm/src/{com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageBFDNameProvider.java => com.oracle.svm.core/src/com/oracle/svm/core/debug/BFDNameProvider.java} (96%)
 delete mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterfaceSystemJava.java
 delete mode 100644 substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateBFDNameProvider.java
 rename substratevm/src/com.oracle.svm.native.debug/include/{gdbJITCompilationInterface.h => gdb_jit_compilation_interface.h} (84%)
 delete mode 100644 substratevm/src/com.oracle.svm.native.debug/src/gdbJITCompilationInterface.c
 create mode 100644 substratevm/src/com.oracle.svm.native.debug/src/gdb_jit_compilation_interface.c

diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java
index 656715a4a938..5358a8c0336d 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java
@@ -188,11 +188,6 @@ public DebugInfoBase(ByteOrder byteOrder) {
         this.objectAlignment = 0;
         this.numAlignmentBits = 0;
         this.hubClassEntry = null;
-        this.compiledCodeMax = 0;
-    }
-
-    public int compiledCodeMax() {
-        return compiledCodeMax;
     }
 
     /**
@@ -240,9 +235,6 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) {
         /* Reference alignment must be 8 bytes. */
         assert objectAlignment == 8;
 
-        /* retrieve limit for Java code address range */
-        compiledCodeMax = debugInfoProvider.compiledCodeMax();
-
         stringTable = debugInfoProvider.getStringTable();
 
         cachePath = debugInfoProvider.cachePath();
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/CallRange.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/CallRange.java
index 4fef52f59525..264335d3e08f 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/CallRange.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/CallRange.java
@@ -26,19 +26,21 @@
 
 package com.oracle.objectfile.debugentry.range;
 
-import com.oracle.objectfile.debugentry.MethodEntry;
-
-import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
 import java.util.stream.Stream;
 
+import com.oracle.objectfile.debugentry.MethodEntry;
+
 public class CallRange extends Range {
 
     /**
-     * The direct callees whose ranges are wholly contained in this range. Empty if this is a
-     * leaf range.
+     * The direct callees whose ranges are wholly contained in this range. Empty if this is a leaf
+     * range.
      */
-    private final List<Range> callees = new ArrayList<>();
+    private final Set<Range> callees = new TreeSet<>(Comparator.comparing(Range::getLoOffset));
 
     protected CallRange(PrimaryRange primary, MethodEntry methodEntry, int lo, int hi, int line, CallRange caller, int depth) {
         super(primary, methodEntry, lo, hi, line, caller, depth);
@@ -46,7 +48,7 @@ protected CallRange(PrimaryRange primary, MethodEntry methodEntry, int lo, int h
 
     @Override
     public List<Range> getCallees() {
-        return callees;
+        return List.copyOf(callees);
     }
 
     @Override
@@ -54,26 +56,10 @@ public Stream<Range> rangeStream() {
         return Stream.concat(super.rangeStream(), callees.stream().flatMap(Range::rangeStream));
     }
 
-    protected void addCallee(Range callee, boolean isInitialRange) {
-        //System.out.println("Caller lo: " + this.getLo() + ", Callee lo: " + callee.getLo());
-        //System.out.println("Caller hi: " + this.getHi() + ", Callee hi: " + callee.getHi());
-        //System.out.println("Caller method: " + this.getMethodName() + ", Callee method: " + callee.getMethodName());
-        assert this.getLoOffset() <= callee.getLoOffset();
-        assert this.getHiOffset() >= callee.getHiOffset();
-        assert callee.getCaller() == this;
-        if (isInitialRange) {
-            callees.addFirst(callee);
-        } else {
-            callees.add(callee);
-        }
-    }
-
-    protected void insertCallee(Range callee, int pos) {
-        assert pos < callees.size();
-        assert this.getLoOffset() <= callee.getLoOffset();
-        assert this.getHiOffset() >= callee.getHiOffset();
+    protected void addCallee(Range callee) {
+        assert this.contains(callee);
         assert callee.getCaller() == this;
-        callees.add(pos, callee);
+        callees.add(callee);
     }
 
     @Override
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/LeafRange.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/LeafRange.java
index e472e90392f7..980d32461342 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/LeafRange.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/LeafRange.java
@@ -33,7 +33,6 @@ protected LeafRange(PrimaryRange primary, MethodEntry methodEntry, int lo, int h
         super(primary, methodEntry, lo, hi, line, caller, depth);
     }
 
-
     @Override
     public boolean isLeaf() {
         return true;
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java
index 053ed7f2e007..ff84b327d034 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java
@@ -136,7 +136,7 @@ public static Range createSubrange(PrimaryRange primary, MethodEntry methodEntry
             callee = new CallRange(primary, methodEntry, lo, hi, line, caller, caller.getDepth() + 1);
         }
 
-        caller.addCallee(callee, isInitialRange);
+        caller.addCallee(callee);
         return callee;
     }
 
@@ -161,10 +161,6 @@ public Range split(int stackDecrement, int frameSize, int preExtendFrameSize) {
         Range newRange = Range.createSubrange(this.primary, this.methodEntry, stackDecrement, this.hiOffset, this.line, this.caller, this.isLeaf());
         this.hiOffset = stackDecrement;
 
-        // TODO fix this properly
-        this.caller.removeCallee(newRange);
-        this.caller.insertCallee(newRange, 1);
-
         for (var localInfo : this.localValueInfos.entrySet()) {
             if (localInfo.getValue() instanceof StackValueEntry stackValue) {
                 // need to redefine the value for this param using a stack slot value
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java
index 4353c5793254..0d51564b9426 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java
@@ -69,8 +69,6 @@ public interface DebugInfoProvider {
      */
     int objectAlignment();
 
-    int compiledCodeMax();
-
     StringTable getStringTable();
 
     List<TypeEntry> typeEntries();
@@ -83,11 +81,4 @@ enum FrameSizeChangeType {
         EXTEND,
         CONTRACT;
     }
-
-    enum LocalValueKind {
-        UNDEFINED,
-        REGISTER,
-        STACK,
-        CONSTANT;
-    }
 }
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageBFDNameProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/BFDNameProvider.java
similarity index 96%
rename from substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageBFDNameProvider.java
rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/BFDNameProvider.java
index e90a74dd6e52..17f1fa137adb 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageBFDNameProvider.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/BFDNameProvider.java
@@ -22,33 +22,33 @@
  * or visit www.oracle.com if you need additional information or have any
  * questions.
  */
-package com.oracle.svm.hosted.image;
+package com.oracle.svm.core.debug;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.util.List;
+
+import org.graalvm.collections.EconomicMap;
 
-import com.oracle.graal.pointsto.meta.AnalysisType;
 import com.oracle.svm.core.SubstrateUtil;
 import com.oracle.svm.core.UniqueShortNameProvider;
+import com.oracle.svm.core.meta.SharedType;
 import com.oracle.svm.core.util.VMError;
-import com.oracle.svm.hosted.c.NativeLibraries;
-import com.oracle.svm.hosted.meta.HostedType;
+
 import jdk.vm.ci.meta.JavaKind;
 import jdk.vm.ci.meta.JavaType;
 import jdk.vm.ci.meta.ResolvedJavaType;
 import jdk.vm.ci.meta.Signature;
 import jdk.vm.ci.meta.UnresolvedJavaType;
-import org.graalvm.collections.EconomicMap;
-
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Executable;
-import java.lang.reflect.Member;
-import java.lang.reflect.Method;
-import java.lang.reflect.Parameter;
-import java.util.List;
 
 /**
  * A unique name provider employed when debug info generation is enabled on Linux. Names are
  * generated in the C++ mangled format that is understood by the Linux binutils BFD library. An ELF
  * symbol defined using a BFD mangled name is linked to a corresponding DWARF method or field info
- * declaration (DIE) by inserting it as the value of the the <code>linkage_name</code> attribute. In
+ * declaration (DIE) by inserting it as the value of the <code>linkage_name</code> attribute. In
  * order for this linkage to be correctly recognised by the debugger and Linux tools the
  * <code>name</code> attributes of the associated class, method, field, parameter types and return
  * type must be the same as the corresponding names encoded in the mangled name.
@@ -56,15 +56,12 @@
  * Note that when the class component of a mangled name needs to be qualified with a class loader id
  * the corresponding DWARF class record must embed the class in a namespace whose name matches the
  * classloader id otherwise the mangled name will not be recognised and demangled successfully.
- * TODO: Namespace embedding is not yet implemented.
  */
-class NativeImageBFDNameProvider implements UniqueShortNameProvider {
+public class BFDNameProvider implements UniqueShortNameProvider {
 
-    private NativeLibraries nativeLibs;
-
-    NativeImageBFDNameProvider(List<ClassLoader> ignore) {
+    public BFDNameProvider(List<ClassLoader> ignore) {
         this.ignoredLoaders = ignore;
-        this.nativeLibs = null;
+        this.wordBaseType = null;
     }
 
     @Override
@@ -113,15 +110,16 @@ private static ClassLoader getClassLoader(ResolvedJavaType type) {
         if (type.isArray()) {
             return getClassLoader(type.getElementalType());
         }
-        if (type instanceof HostedType) {
-            HostedType hostedType = (HostedType) type;
-            return hostedType.getJavaClass().getClassLoader();
+        if (type instanceof SharedType sharedType && sharedType.getHub().isLoaded()) {
+            return sharedType.getHub().getClassLoader();
         }
         return null;
     }
 
     private final List<ClassLoader> ignoredLoaders;
 
+    private ResolvedJavaType wordBaseType;
+
     private static final String BUILTIN_CLASSLOADER_NAME = "jdk.internal.loader.BuiltinClassLoader";
 
     private static boolean isBuiltinLoader(ClassLoader loader) {
@@ -411,14 +409,14 @@ public String bfdMangle(Member m) {
     }
 
     /**
-     * Make the provider aware of the current native libraries. This is needed because the provider
-     * is created in a feature after registration but the native libraries are only available before
-     * analysis.
+     * Make the provider aware of the word base type. This is needed because the same provider is
+     * used for AOT debug info generation and runtime debug info generation. For AOT debug info we
+     * need the HostedType and for Runtime debug info we need the SubstrateType.
      *
-     * @param nativeLibs the current native libraries singleton.
+     * @param wordBaseType the current wordBaseType.
      */
-    public void setNativeLibs(NativeLibraries nativeLibs) {
-        this.nativeLibs = nativeLibs;
+    public void setWordBaseType(ResolvedJavaType wordBaseType) {
+        this.wordBaseType = wordBaseType;
     }
 
     private static class BFDMangler {
@@ -464,7 +462,7 @@ private static class BFDMangler {
          * arise. A name can always be correctly encoded without repeats. In the above example that
          * would be _ZN5Hello9compareToEJiPS_.
          */
-        final NativeImageBFDNameProvider nameProvider;
+        final BFDNameProvider nameProvider;
         final StringBuilder sb;
 
         // A list of lookup names identifying substituted elements. A prospective name for an
@@ -524,7 +522,7 @@ public String toString() {
             }
         }
 
-        BFDMangler(NativeImageBFDNameProvider provider) {
+        BFDMangler(BFDNameProvider provider) {
             nameProvider = provider;
             sb = new StringBuilder("_Z");
             bindings = EconomicMap.create();
@@ -908,16 +906,6 @@ private void recordName(LookupName name) {
      * @return true if the type needs to be encoded using pointer prefix P otherwise false.
      */
     private boolean needsPointerPrefix(ResolvedJavaType type) {
-        ResolvedJavaType target = type;
-        if (type instanceof HostedType) {
-            // unwrap to analysis type as that is what native libs checks test against
-            target = ((HostedType) target).getWrapped();
-        }
-        if (target instanceof AnalysisType) {
-            assert nativeLibs != null : "No native libs during or after analysis!";
-            return !nativeLibs.isWordBase(target);
-        }
-        return true;
+        return wordBaseType == null || !wordBaseType.isAssignableFrom(type);
     }
-
 }
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterface.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterface.java
index bbc6c174152b..33d7d5346f93 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterface.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterface.java
@@ -1,17 +1,26 @@
 package com.oracle.svm.core.debug;
 
-import com.oracle.svm.core.SubstrateOptions;
-import com.oracle.svm.core.c.ProjectHeaderFile;
+import java.util.Collections;
+import java.util.List;
+
+import org.graalvm.nativeimage.ImageSingletons;
 import org.graalvm.nativeimage.c.CContext;
+import org.graalvm.nativeimage.c.constant.CEnum;
+import org.graalvm.nativeimage.c.constant.CEnumValue;
 import org.graalvm.nativeimage.c.function.CFunction;
 import org.graalvm.nativeimage.c.struct.CField;
 import org.graalvm.nativeimage.c.struct.CStruct;
 import org.graalvm.nativeimage.c.type.CCharPointer;
 import org.graalvm.nativeimage.c.type.CUnsigned;
+import org.graalvm.nativeimage.impl.UnmanagedMemorySupport;
 import org.graalvm.word.PointerBase;
+import org.graalvm.word.WordFactory;
 
-import java.util.Collections;
-import java.util.List;
+import com.oracle.svm.core.SubstrateOptions;
+import com.oracle.svm.core.Uninterruptible;
+import com.oracle.svm.core.c.CGlobalData;
+import com.oracle.svm.core.c.CGlobalDataFactory;
+import com.oracle.svm.core.c.ProjectHeaderFile;
 
 @CContext(GDBJITInterface.GDBJITInterfaceDirectives.class)
 public class GDBJITInterface {
@@ -24,7 +33,7 @@ public boolean isInConfiguration() {
 
         @Override
         public List<String> getHeaderFiles() {
-            return Collections.singletonList(ProjectHeaderFile.resolve("com.oracle.svm.native.debug", "include/gdbJITCompilationInterface.h"));
+            return Collections.singletonList(ProjectHeaderFile.resolve("com.oracle.svm.native.debug", "include/gdb_jit_compilation_interface.h"));
         }
 
         @Override
@@ -33,36 +42,130 @@ public List<String> getLibraries() {
         }
     }
 
+    @CEnum(value = "jit_actions_t")
+    public enum JITActions {
+        JIT_NOACTION,
+        JIT_REGISTER,
+        JIT_UNREGISTER;
+
+        @CEnumValue
+        @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+        public native int getCValue();
+    }
+
     @CStruct(value = "jit_code_entry", addStructKeyword = true)
     public interface JITCodeEntry extends PointerBase {
         // struct jit_code_entry *next_entry;
         @CField("next_entry")
         JITCodeEntry getNextEntry();
+
         @CField("next_entry")
         void setNextEntry(JITCodeEntry jitCodeEntry);
 
         // struct jit_code_entry *prev_entry;
         @CField("prev_entry")
         JITCodeEntry getPrevEntry();
+
         @CField("prev_entry")
         void setPrevEntry(JITCodeEntry jitCodeEntry);
 
         // const char *symfile_addr;
         @CField("symfile_addr")
         CCharPointer getSymfileAddr();
+
         @CField("symfile_addr")
         void setSymfileAddr(CCharPointer symfileAddr);
 
         // uint64_t symfile_size;
         @CField("symfile_size")
-        @CUnsigned long getSymfileSize();
+        @CUnsigned
+        long getSymfileSize();
+
         @CField("symfile_size")
         void setSymfileSize(@CUnsigned long symfileSize);
     }
 
-    @CFunction(value = "register_jit_code", transition = CFunction.Transition.NO_TRANSITION)
-    public static native JITCodeEntry registerJITCode(CCharPointer addr, @CUnsigned long size);
+    @CStruct(value = "jit_descriptor", addStructKeyword = true)
+    public interface JITDescriptor extends PointerBase {
+        // uint32_t version;
+        @CField("version")
+        @CUnsigned
+        int getVersion();
+
+        @CField("version")
+        void setVersion(@CUnsigned int version);
+
+        // uint32_t action_flag;
+        @CField("action_flag")
+        @CUnsigned
+        int getActionFlag();
+
+        @CField("action_flag")
+        void setActionFlag(@CUnsigned int actionFlag);
+
+        // struct jit_code_entry *relevant_entry;
+        @CField("relevant_entry")
+        JITCodeEntry getRelevantEntry();
 
-    @CFunction(value = "unregister_jit_code", transition = CFunction.Transition.NO_TRANSITION)
-    public static native void unregisterJITCode(JITCodeEntry entry);
+        @CField("relevant_entry")
+        void setRelevantEntry(JITCodeEntry jitCodeEntry);
+
+        // struct jit_code_entry *first_entry;
+        @CField("first_entry")
+        JITCodeEntry getFirstEntry();
+
+        @CField("first_entry")
+        void setFirstEntry(JITCodeEntry jitCodeEntry);
+    }
+
+    @CFunction(value = "__jit_debug_register_code", transition = CFunction.Transition.NO_TRANSITION)
+    private static native void jitDebugRegisterCode();
+
+    private static final CGlobalData<JITDescriptor> jitDebugDescriptor = CGlobalDataFactory.forSymbol("__jit_debug_descriptor");
+
+    public static void registerJITCode(CCharPointer addr, @CUnsigned long size, JITCodeEntry entry) {
+        /* Create new jit_code_entry */
+        entry.setSymfileAddr(addr);
+        entry.setSymfileSize(size);
+
+        /* Insert entry at head of the list. */
+        JITCodeEntry nextEntry = jitDebugDescriptor.get().getFirstEntry();
+        entry.setPrevEntry(WordFactory.nullPointer());
+        entry.setNextEntry(nextEntry);
+
+        if (nextEntry.isNonNull()) {
+            nextEntry.setPrevEntry(entry);
+        }
+
+        /* Notify GDB. */
+        jitDebugDescriptor.get().setActionFlag(JITActions.JIT_REGISTER.getCValue());
+        jitDebugDescriptor.get().setFirstEntry(entry);
+        jitDebugDescriptor.get().setRelevantEntry(entry);
+        jitDebugRegisterCode();
+    }
+
+    @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+    public static void unregisterJITCode(JITCodeEntry entry) {
+        JITCodeEntry prevEntry = entry.getPrevEntry();
+        JITCodeEntry nextEntry = entry.getNextEntry();
+
+        /* Fix prev and next in list */
+        if (nextEntry.isNonNull()) {
+            nextEntry.setPrevEntry(prevEntry);
+        }
+
+        if (prevEntry.isNonNull()) {
+            prevEntry.setNextEntry(nextEntry);
+        } else {
+            assert (jitDebugDescriptor.get().getFirstEntry().equal(entry));
+            jitDebugDescriptor.get().setFirstEntry(nextEntry);
+        }
+
+        /* Notify GDB. */
+        jitDebugDescriptor.get().setActionFlag(JITActions.JIT_REGISTER.ordinal());
+        jitDebugDescriptor.get().setRelevantEntry(entry);
+        jitDebugRegisterCode();
+
+        ImageSingletons.lookup(UnmanagedMemorySupport.class).free(entry);
+    }
 }
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterfaceSystemJava.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterfaceSystemJava.java
deleted file mode 100644
index c2067e4cc43a..000000000000
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterfaceSystemJava.java
+++ /dev/null
@@ -1,158 +0,0 @@
-package com.oracle.svm.core.debug;
-
-import com.oracle.svm.core.SubstrateOptions;
-import com.oracle.svm.core.Uninterruptible;
-import com.oracle.svm.core.c.CGlobalData;
-import com.oracle.svm.core.c.CGlobalDataFactory;
-import com.oracle.svm.core.c.ProjectHeaderFile;
-import org.graalvm.nativeimage.ImageSingletons;
-import org.graalvm.nativeimage.UnmanagedMemory;
-import org.graalvm.nativeimage.c.CContext;
-import org.graalvm.nativeimage.c.constant.CEnum;
-import org.graalvm.nativeimage.c.constant.CEnumValue;
-import org.graalvm.nativeimage.c.function.CFunction;
-import org.graalvm.nativeimage.c.struct.CField;
-import org.graalvm.nativeimage.c.struct.CStruct;
-import org.graalvm.nativeimage.c.struct.SizeOf;
-import org.graalvm.nativeimage.c.type.CCharPointer;
-import org.graalvm.nativeimage.c.type.CUnsigned;
-import org.graalvm.nativeimage.impl.UnmanagedMemorySupport;
-import org.graalvm.word.PointerBase;
-import org.graalvm.word.WordFactory;
-
-import java.util.Collections;
-import java.util.List;
-
-
-@CContext(GDBJITInterfaceSystemJava.GDBJITInterfaceDirectives.class)
-public class GDBJITInterfaceSystemJava {
-
-    public static class GDBJITInterfaceDirectives implements CContext.Directives {
-        @Override
-        public boolean isInConfiguration() {
-            return SubstrateOptions.RuntimeDebugInfo.getValue();
-        }
-
-        @Override
-        public List<String> getHeaderFiles() {
-            return Collections.singletonList(ProjectHeaderFile.resolve("com.oracle.svm.native.debug", "include/gdbJITCompilationInterface.h"));
-        }
-    }
-
-    @CEnum(value = "jit_actions_t")
-    public enum JITActions {
-        JIT_NOACTION,
-        JIT_REGISTER,
-        JIT_UNREGISTER;
-
-        @CEnumValue
-        @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
-        public native int getCValue();
-    }
-
-
-    @CStruct(value = "jit_code_entry", addStructKeyword = true)
-    public interface JITCodeEntry extends PointerBase {
-        // struct jit_code_entry *next_entry;
-        @CField("next_entry")
-        JITCodeEntry getNextEntry();
-        @CField("next_entry")
-        void setNextEntry(JITCodeEntry jitCodeEntry);
-
-        // struct jit_code_entry *prev_entry;
-        @CField("prev_entry")
-        JITCodeEntry getPrevEntry();
-        @CField("prev_entry")
-        void setPrevEntry(JITCodeEntry jitCodeEntry);
-
-        // const char *symfile_addr;
-        @CField("symfile_addr")
-        CCharPointer getSymfileAddr();
-        @CField("symfile_addr")
-        void setSymfileAddr(CCharPointer symfileAddr);
-
-        // uint64_t symfile_size;
-        @CField("symfile_size")
-        @CUnsigned long getSymfileSize();
-        @CField("symfile_size")
-        void setSymfileSize(@CUnsigned long symfileSize);
-    }
-
-    @CStruct(value = "jit_descriptor", addStructKeyword = true)
-    public interface JITDescriptor extends PointerBase {
-        // uint32_t version;
-        @CField("version")
-        @CUnsigned int getVersion();
-        @CField("version")
-        void setVersion(@CUnsigned int version);
-
-        // uint32_t action_flag;
-        @CField("action_flag")
-        @CUnsigned int getActionFlag();
-        @CField("action_flag")
-        void setActionFlag(@CUnsigned int actionFlag);
-
-        // struct jit_code_entry *relevant_entry;
-        @CField("relevant_entry")
-        JITCodeEntry getRelevantEntry();
-        @CField("relevant_entry")
-        void setRelevantEntry(JITCodeEntry jitCodeEntry);
-
-        // struct jit_code_entry *first_entry;
-        @CField("first_entry")
-        JITCodeEntry getFirstEntry();
-        @CField("first_entry")
-        void setFirstEntry(JITCodeEntry jitCodeEntry);
-    }
-
-    @CFunction(value = "__jit_debug_register_code", transition = CFunction.Transition.NO_TRANSITION)
-    private static native void jitDebugRegisterCode();
-
-    private static final CGlobalData<JITDescriptor> jitDebugDescriptor = CGlobalDataFactory.forSymbol("__jit_debug_descriptor");
-
-    public static void registerJITCode(CCharPointer addr, @CUnsigned long size, JITCodeEntry entry) {
-        /* Create new jit_code_entry */
-        entry.setSymfileAddr(addr);
-        entry.setSymfileSize(size);
-
-        /* Insert entry at head of the list. */
-        JITCodeEntry nextEntry = jitDebugDescriptor.get().getFirstEntry();
-        entry.setPrevEntry(WordFactory.nullPointer());
-        entry.setNextEntry(nextEntry);
-
-        if (nextEntry.isNonNull()) {
-            nextEntry.setPrevEntry(entry);
-        }
-
-        /* Notify GDB.  */
-        jitDebugDescriptor.get().setActionFlag(JITActions.JIT_REGISTER.getCValue());
-        jitDebugDescriptor.get().setFirstEntry(entry);
-        jitDebugDescriptor.get().setRelevantEntry(entry);
-        jitDebugRegisterCode();
-    }
-
-    @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
-    public static void unregisterJITCode(JITCodeEntry entry) {
-        JITCodeEntry prevEntry = entry.getPrevEntry();
-        JITCodeEntry nextEntry = entry.getNextEntry();
-
-        /* Fix prev and next in list */
-        if (nextEntry.isNonNull()) {
-            nextEntry.setPrevEntry(prevEntry);
-        }
-
-        if (prevEntry.isNonNull()) {
-            prevEntry.setNextEntry(nextEntry);
-        } else {
-            assert(jitDebugDescriptor.get().getFirstEntry().equal(entry));
-            jitDebugDescriptor.get().setFirstEntry(nextEntry);
-        }
-
-        /* Notify GDB.  */
-        jitDebugDescriptor.get().setActionFlag(2); // JITActions.JIT_UNREGISTER.getCValue());
-        jitDebugDescriptor.get().setRelevantEntry(entry);
-        jitDebugRegisterCode();
-
-        ImageSingletons.lookup(UnmanagedMemorySupport.class).free(entry);
-    }
-}
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 9399e8046af1..dde6a1e99503 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
@@ -48,7 +48,6 @@
 import com.oracle.svm.core.graal.meta.RuntimeConfiguration;
 import com.oracle.svm.core.graal.meta.SubstrateRegisterConfig;
 import com.oracle.svm.core.heap.Heap;
-import com.oracle.svm.core.heap.ObjectHeader;
 import com.oracle.svm.core.heap.ReferenceAccess;
 import com.oracle.svm.core.meta.SharedMethod;
 import com.oracle.svm.core.meta.SharedType;
@@ -135,8 +134,8 @@
 public abstract class SharedDebugInfoProvider implements DebugInfoProvider {
 
     protected final RuntimeConfiguration runtimeConfiguration;
+
     protected final MetaAccessProvider metaAccess;
-    protected final UniqueShortNameProvider nameProvider;
 
     protected final DebugContext debug;
 
@@ -176,26 +175,40 @@ public abstract class SharedDebugInfoProvider implements DebugInfoProvider {
      */
     public static final String LAYOUT_PREFIX = "_layout_.";
 
-    public static final String CLASS_CONSTANT_SUFFIX = ".class";
-
     static final Path EMPTY_PATH = Paths.get("");
 
-    public SharedDebugInfoProvider(DebugContext debug, RuntimeConfiguration runtimeConfiguration, MetaAccessProvider metaAccess, UniqueShortNameProvider nameProvider, Path cachePath) {
+    public SharedDebugInfoProvider(DebugContext debug, RuntimeConfiguration runtimeConfiguration, MetaAccessProvider metaAccess, Path cachePath) {
         this.runtimeConfiguration = runtimeConfiguration;
         this.metaAccess = metaAccess;
-        this.nameProvider = nameProvider;
+
+        /*
+         * Use a disabled DebugContext if log is disabled here. We need to make sure the log stays
+         * disabled, as we use parallel streams if it is disabled
+         */
         this.debug = debug.isLogEnabled() ? debug : DebugContext.disabled(null);
+
+        // Create the cachePath string entry which serves as base directory for source files
         this.cachePathEntry = stringTable.uniqueDebugString(cachePath.toString());
+
+        // Fetch special types that have special use cases.
+        // hubType: type of the 'hub' field in the object header.
+        // wordBaseType: for checking for foreign types.
+        // voidType: fallback type to point to for foreign pointer types
         this.hubType = (SharedType) metaAccess.lookupJavaType(Class.class);
         this.wordBaseType = (SharedType) metaAccess.lookupJavaType(WordBase.class);
         this.voidType = (SharedType) metaAccess.lookupJavaType(Void.class);
-        this.pointerSize = ConfigurationValues.getTarget().wordSize;
-        ObjectHeader objectHeader = Heap.getHeap().getObjectHeader();
-        this.reservedBitsMask = objectHeader.getReservedBitsMask();
+
+        if (UniqueShortNameProvider.singleton() instanceof BFDNameProvider bfdNameProvider) {
+            bfdNameProvider.setWordBaseType(this.wordBaseType);
+        }
+
+        // Get some information on heap layout and object/object header layout
         this.useHeapBase = ReferenceAccess.singleton().haveCompressedReferences() && ReferenceAccess.singleton().getCompressEncoding().hasBase();
         this.compressionShift = ReferenceAccess.singleton().getCompressionShift();
+        this.pointerSize = ConfigurationValues.getTarget().wordSize;
         this.referenceSize = getObjectLayout().getReferenceSize();
         this.objectAlignment = getObjectLayout().getAlignment();
+        this.reservedBitsMask = Heap.getHeap().getObjectHeader().getReservedBitsMask();
     }
 
     protected abstract Stream<SharedType> typeInfo();
@@ -239,11 +252,6 @@ public int objectAlignment() {
         return objectAlignment;
     }
 
-    @Override
-    public int compiledCodeMax() {
-        return 0;
-    }
-
     @Override
     public String cachePath() {
         return cachePathEntry;
@@ -267,6 +275,7 @@ public List<CompiledMethodEntry> compiledMethodEntries() {
 
     /* Functions for installing debug info into the index maps. */
     @Override
+    @SuppressWarnings("try")
     public void installDebugInfo() {
         // we can only meaningfully provide logging if debug info is produced sequentially
         Stream<SharedType> typeStream = debug.isLogEnabled() ? typeInfo() : typeInfo().parallel();
@@ -304,9 +313,10 @@ private void handleTypeInfo(SharedType type) {
     }
 
     private void handleCodeInfo(SharedMethod method, CompilationResult compilation) {
-        // First make sure the underlying MethodEntry exists
+        // First make sure the underlying MethodEntry exists.
         MethodEntry methodEntry = lookupMethodEntry(method);
-        // Then process the compilation for frame states from infopoints/sourcemappings
+        // Then process the compilation for frame states from infopoints/sourcemappings.
+        // For performance reasons we mostly only use infopoints for processing compilations
         lookupCompiledMethodEntry(methodEntry, method, compilation);
     }
 
@@ -325,8 +335,10 @@ static boolean debugCodeInfoUseSourceMappings() {
         return SubstrateOptions.DebugCodeInfoUseSourceMappings.getValue();
     }
 
+    @SuppressWarnings("try")
     protected CompiledMethodEntry installCompilationInfo(MethodEntry methodEntry, SharedMethod method, CompilationResult compilation) {
         try (DebugContext.Scope s = debug.scope("DebugInfoCompilation")) {
+            // Mark the method entry for the compilation.
             methodEntry.setInRange();
 
             int primaryLine = methodEntry.getLine();
@@ -334,17 +346,23 @@ protected CompiledMethodEntry installCompilationInfo(MethodEntry methodEntry, Sh
             List<FrameSizeChangeEntry> frameSizeChanges = getFrameSizeChanges(compilation);
             ClassEntry ownerType = methodEntry.getOwnerType();
 
+            // Create a primary range that spans over the compilation.
+            // The primary range entry holds the code offset information for all its sub ranges.
             PrimaryRange primaryRange = Range.createPrimary(methodEntry, 0, compilation.getTargetCodeSize(), primaryLine, getCodeOffset(method));
             debug.log(DebugContext.INFO_LEVEL, "PrimaryRange %s.%s %s %s:%d [0x%x, 0x%x]", ownerType.getTypeName(), methodEntry.getMethodName(), primaryRange.getFileEntry().getPathName(),
                             primaryRange.getFileName(), primaryLine, primaryRange.getLo(), primaryRange.getHi());
 
             CompiledMethodEntry compiledMethodEntry = new CompiledMethodEntry(primaryRange, frameSizeChanges, frameSize, ownerType);
             if (compiledMethodIndex.putIfAbsent(compilation.getCompilationId(), compiledMethodEntry) == null) {
-                // can we still provide locals if we have no file name?
+                // If the compiled method entry was added, we still need to check the frame states
+                // for subranges.
+
+                // Can we still provide locals if we have no file name?
                 if (methodEntry.getFileName().isEmpty()) {
                     return compiledMethodEntry;
                 }
 
+                // Restrict the frame state traversal based on options.
                 boolean omitInline = omitInline();
                 int maxDepth = debugCodeInfoMaxDepth();
                 boolean useSourceMappings = debugCodeInfoUseSourceMappings();
@@ -352,6 +370,10 @@ protected CompiledMethodEntry installCompilationInfo(MethodEntry methodEntry, Sh
                     /* TopLevelVisitor will not go deeper than level 2 */
                     maxDepth = 2;
                 }
+
+                // The root node is represented by the primary range.
+                // A call nodes in the frame tree will be stored as call ranges and leaf nodes as
+                // leaf ranges
                 final CompilationResultFrameTree.CallNode root = new CompilationResultFrameTree.Builder(debug, compilation.getTargetCodeSize(), maxDepth, useSourceMappings,
                                 true)
                                 .build(compilation);
@@ -359,6 +381,8 @@ protected CompiledMethodEntry installCompilationInfo(MethodEntry methodEntry, Sh
                     return compiledMethodEntry;
                 }
                 final List<Range> subRanges = new ArrayList<>();
+                // The top level visitor will only traverse the direct children of the primary
+                // range. All sub call ranges will be treated as leaf ranges.
                 final CompilationResultFrameTree.Visitor visitor = omitInline ? new TopLevelVisitor(subRanges, frameSize, primaryRange) : new MultiLevelVisitor(subRanges, frameSize, primaryRange);
                 // arguments passed by visitor to apply are
                 // NativeImageDebugLocationInfo caller location info
@@ -366,7 +390,7 @@ protected CompiledMethodEntry installCompilationInfo(MethodEntry methodEntry, Sh
                 // NativeImageDebugLocationInfo leaf into which current leaf may be merged
                 root.visitChildren(visitor, primaryRange, null, null);
                 // try to add a location record for offset zero
-                updateInitialLocation(primaryRange, subRanges, compilation, method);
+                updateInitialLocation(primaryRange, subRanges, compilation, method, methodEntry);
 
                 ownerType.addCompiledMethod(compiledMethodEntry);
             }
@@ -402,6 +426,7 @@ public List<FrameSizeChangeEntry> getFrameSizeChanges(CompilationResult compilat
 
     protected abstract long getCodeOffset(SharedMethod method);
 
+    @SuppressWarnings("try")
     protected MethodEntry installMethodEntry(SharedMethod method) {
         try (DebugContext.Scope s = debug.scope("DebugInfoMethod")) {
             FileEntry fileEntry = lookupFileEntry(method);
@@ -409,7 +434,7 @@ protected MethodEntry installMethodEntry(SharedMethod method) {
             LineNumberTable lineNumberTable = method.getLineNumberTable();
             int line = lineNumberTable == null ? 0 : lineNumberTable.getLineNumber(0);
 
-            String methodName = getMethodName(method); // stringTable.uniqueDebugString(getMethodName(method));
+            String methodName = getMethodName(method);
             StructureTypeEntry ownerType = (StructureTypeEntry) lookupTypeEntry((SharedType) method.getDeclaringClass());
             assert ownerType instanceof ClassEntry;
             TypeEntry valueType = lookupTypeEntry((SharedType) method.getSignature().getReturnType(null));
@@ -425,7 +450,7 @@ protected MethodEntry installMethodEntry(SharedMethod method) {
             // look for locals in the methods local variable table
             List<LocalEntry> locals = getLocalEntries(method, lastParamSlot);
 
-            String symbolName = getSymbolName(method); // stringTable.uniqueDebugString(getSymbolName(method));
+            String symbolName = getSymbolName(method);
             int vTableOffset = getVTableOffset(method);
 
             boolean isDeopt = isDeopt(method, methodName);
@@ -440,18 +465,14 @@ protected MethodEntry installMethodEntry(SharedMethod method) {
         }
     }
 
+    @SuppressWarnings("try")
     private TypeEntry installTypeEntry(SharedType type) {
         try (DebugContext.Scope s = debug.scope("DebugInfoType")) {
             debug.log(DebugContext.INFO_LEVEL, "Register type %s ", type.getName());
 
             TypeEntry typeEntry = createTypeEntry(type);
             if (typeIndex.putIfAbsent(type, typeEntry) == null) {
-                /* Add class constant name to string table. */
-                if (typeEntry.getClassOffset() != -1) {
-                    // stringTable.uniqueDebugString(typeEntry.getTypeName() +
-                    // CLASS_CONSTANT_SUFFIX);
-                }
-                // typeEntry was added to the type index, now we need to process the type
+                // TypeEntry was added to the type index, now we need to process the type.
                 debug.log(DebugContext.INFO_LEVEL, "Process type %s ", type.getName());
                 processTypeEntry(type, typeEntry);
             }
@@ -470,7 +491,7 @@ protected void installHeaderTypeEntry() {
 
         int hubOffset = ol.getHubOffset();
 
-        String typeName = "_objhdr"; // stringTable.uniqueDebugString("_objhdr");
+        String typeName = "_objhdr";
         long typeSignature = getTypeSignature(typeName);
 
         headerTypeEntry = new HeaderTypeEntry(typeName, ol.getFirstFieldOffset(), typeSignature);
@@ -484,7 +505,6 @@ protected void installHeaderTypeEntry() {
     }
 
     protected FieldEntry createSyntheticFieldEntry(String name, StructureTypeEntry ownerType, SharedType type, int offset, int size) {
-        // stringTable.uniqueDebugString(name);
         TypeEntry typeEntry = lookupTypeEntry(type);
         debug.log("typename %s adding synthetic (public) field %s type %s size %d at offset 0x%x%n",
                         ownerType.getTypeName(), name, typeEntry.getTypeName(), size, offset);
@@ -492,7 +512,6 @@ protected FieldEntry createSyntheticFieldEntry(String name, StructureTypeEntry o
     }
 
     protected FieldEntry createFieldEntry(FileEntry fileEntry, String name, StructureTypeEntry ownerType, SharedType type, int offset, int size, boolean isEmbedded, int modifier) {
-        // stringTable.uniqueDebugString(name);
         TypeEntry typeEntry = lookupTypeEntry(type);
         debug.log("typename %s adding %s field %s type %s%s size %d at offset 0x%x%n",
                         ownerType.getTypeName(), ownerType.getModifiersString(modifier), name, typeEntry.getTypeName(), (isEmbedded ? "(embedded)" : ""), size, offset);
@@ -530,7 +549,7 @@ public List<LocalEntry> getLocalEntries(SharedMethod method, int lastParamSlot)
             // check if this is a local (slot is after last param slot)
             if (local != null && local.getSlot() > lastParamSlot) {
                 // we have a local with a known name, type and slot
-                String name = local.getName(); // stringTable.uniqueDebugString(l.getName());
+                String name = local.getName();
                 SharedType type = (SharedType) local.getType().resolve(ownerType);
                 int slot = local.getSlot();
                 int bciStart = local.getStartBCI();
@@ -563,7 +582,6 @@ public List<LocalEntry> getParamEntries(SharedMethod method, int line) {
         SharedType ownerType = (SharedType) method.getDeclaringClass();
         if (!method.isStatic()) {
             JavaKind kind = ownerType.getJavaKind();
-            JavaKind storageKind = ownerType.getStorageKind();
             assert kind == JavaKind.Object : "must be an object";
             paramInfos.add(new LocalEntry("this", lookupTypeEntry(ownerType), slot, line));
             slot += kind.getSlotCount();
@@ -574,7 +592,6 @@ public List<LocalEntry> getParamEntries(SharedMethod method, int line) {
             JavaKind kind = paramType.getJavaKind();
             JavaKind storageKind = paramType.getStorageKind();
             String name = local != null ? local.getName() : "__" + storageKind.getJavaName() + i;
-            // stringTable.uniqueDebugString(local != null ? local.getName() : "__" + i);
             paramInfos.add(new LocalEntry(name, lookupTypeEntry(paramType), slot, line));
             slot += kind.getSlotCount();
         }
@@ -586,7 +603,9 @@ public int getModifiers(SharedMethod method) {
     }
 
     public String getSymbolName(SharedMethod method) {
-        return nameProvider.uniqueShortName(null, method.getDeclaringClass(), method.getName(), method.getSignature(), method.isConstructor());
+        return UniqueShortNameProvider.singleton().uniqueShortName(null, method.getDeclaringClass(), method.getName(), method.getSignature(), method.isConstructor());
+        // return nameProvider.uniqueShortName(null, method.getDeclaringClass(), method.getName(),
+        // method.getSignature(), method.isConstructor());
     }
 
     public boolean isDeopt(SharedMethod method, String methodName) {
@@ -661,7 +680,7 @@ public LoaderEntry lookupLoaderEntry(SharedType type) {
         if (type.isArray()) {
             type = (SharedType) type.getElementalType();
         }
-        return type.getHub().isLoaded() ? lookupLoaderEntry(nameProvider.uniqueShortLoaderName(type.getHub().getClassLoader())) : null;
+        return type.getHub().isLoaded() ? lookupLoaderEntry(UniqueShortNameProvider.singleton().uniqueShortLoaderName(type.getHub().getClassLoader())) : null;
     }
 
     public LoaderEntry lookupLoaderEntry(String loaderName) {
@@ -690,7 +709,7 @@ public FileEntry lookupFileEntry(Path fullFilePath) {
             return null;
         }
 
-        String fileName = fullFilePath.getFileName().toString(); // stringTable.uniqueDebugString(fullFilePath.getFileName().toString());
+        String fileName = fullFilePath.getFileName().toString();
         Path dirPath = fullFilePath.getParent();
 
         DirEntry dirEntry = lookupDirEntry(dirPath);
@@ -706,7 +725,6 @@ public DirEntry lookupDirEntry(Path dirPath) {
             dirPath = EMPTY_PATH;
         }
 
-        // stringTable.uniqueDebugString(dirPath.toString());
         return dirIndex.computeIfAbsent(dirPath, DirEntry::new);
     }
 
@@ -763,7 +781,7 @@ private int findMarkOffset(SubstrateBackend.SubstrateMarkId markId, CompilationR
         return -1;
     }
 
-    private void updateInitialLocation(PrimaryRange primary, List<Range> locationInfos, CompilationResult compilation, SharedMethod method) {
+    private void updateInitialLocation(PrimaryRange primary, List<Range> locationInfos, CompilationResult compilation, SharedMethod method, MethodEntry methodEntry) {
         int prologueEnd = findMarkOffset(SubstrateBackend.SubstrateMarkId.PROLOGUE_END, compilation);
         if (prologueEnd < 0) {
             // this is not a normal compiled method so give up
@@ -805,8 +823,7 @@ private void updateInitialLocation(PrimaryRange primary, List<Range> locationInf
         }
         // create a synthetic location record including details of passed arguments
         ParamLocationProducer locProducer = new ParamLocationProducer(method);
-        debug.log(DebugContext.DETAILED_LEVEL, "Add synthetic Location Info : %s (0, %d)", method.getName(), firstLocationOffset - 1);
-        MethodEntry methodEntry = lookupMethodEntry(method);
+        debug.log(DebugContext.DETAILED_LEVEL, "Add synthetic Location Info : %s (0, %d)", methodEntry.getMethodName(), firstLocationOffset - 1);
         Range locationInfo = Range.createSubrange(primary, methodEntry, 0, firstLocationOffset, methodEntry.getLine(), primary, true, true);
 
         locationInfo.setLocalValueInfo(initSyntheticInfoList(locProducer, methodEntry));
@@ -816,7 +833,7 @@ private void updateInitialLocation(PrimaryRange primary, List<Range> locationInf
         // splitting at the extent point with the stack offsets adjusted in the new info
         if (locProducer.usesStack() && firstLocationOffset > stackDecrement) {
             Range splitLocationInfo = locationInfo.split(stackDecrement, compilation.getTotalFrameSize(), PRE_EXTEND_FRAME_SIZE);
-            debug.log(DebugContext.DETAILED_LEVEL, "Split synthetic Location Info : %s (0, %d) (%d, %d)", method.getName(),
+            debug.log(DebugContext.DETAILED_LEVEL, "Split synthetic Location Info : %s (0, %d) (%d, %d)", methodEntry.getMethodName(),
                             locationInfo.getLoOffset() - 1, locationInfo.getLoOffset(), locationInfo.getHiOffset() - 1);
             locationInfos.addFirst(splitLocationInfo);
         }
@@ -946,7 +963,7 @@ private abstract class SingleLevelVisitor implements CompilationResultFrameTree.
 
         @Override
         public void apply(CompilationResultFrameTree.FrameNode node, Object... args) {
-            // Visits all nodes at this level and handles call nodes depth first (by default do
+            // Visits all nodes at this level and handle call nodes depth first (by default do
             // nothing, just add the call nodes range info).
             if (node instanceof CompilationResultFrameTree.CallNode && skipPos(node.frame)) {
                 node.visitChildren(this, args);
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateBFDNameProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateBFDNameProvider.java
deleted file mode 100644
index 45765febdc8b..000000000000
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateBFDNameProvider.java
+++ /dev/null
@@ -1,882 +0,0 @@
-/*
- * Copyright (c) 2012, 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.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * 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.
- */
-package com.oracle.svm.core.debug;
-
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Executable;
-import java.lang.reflect.Member;
-import java.lang.reflect.Method;
-import java.lang.reflect.Parameter;
-import java.util.List;
-
-import org.graalvm.collections.EconomicMap;
-
-import com.oracle.svm.core.SubstrateUtil;
-import com.oracle.svm.core.UniqueShortNameProvider;
-import com.oracle.svm.core.util.VMError;
-
-import jdk.vm.ci.meta.JavaKind;
-import jdk.vm.ci.meta.JavaType;
-import jdk.vm.ci.meta.ResolvedJavaType;
-import jdk.vm.ci.meta.Signature;
-import jdk.vm.ci.meta.UnresolvedJavaType;
-
-
-public class SubstrateBFDNameProvider implements UniqueShortNameProvider {
-
-    public SubstrateBFDNameProvider(List<ClassLoader> ignore) {
-        this.ignoredLoaders = ignore;
-    }
-
-    @Override
-    public String uniqueShortName(ClassLoader loader, ResolvedJavaType declaringClass, String methodName, Signature methodSignature, boolean isConstructor) {
-        String loaderName = uniqueShortLoaderName(loader);
-        return bfdMangle(loaderName, declaringClass, methodName, methodSignature, isConstructor);
-    }
-
-    @Override
-    public String uniqueShortName(Member m) {
-        return bfdMangle(m);
-    }
-
-    @Override
-    public String uniqueShortLoaderName(ClassLoader loader) {
-        // no need to qualify classes loaded by a builtin loader
-        if (isBuiltinLoader(loader)) {
-            return "";
-        }
-        if (isGraalImageLoader(loader)) {
-            return "";
-        }
-        String name = SubstrateUtil.classLoaderNameAndId(loader);
-        // name will look like "org.foo.bar.FooBarClassLoader @1234"
-        // trim it down to something more manageable
-        // escaping quotes in the classlaoder name does not work in GDB
-        // but the name is still unique without quotes
-        name = SubstrateUtil.stripPackage(name);
-        name = stripOuterClass(name);
-        name = name.replace(" @", "_").replace("'", "").replace("\"", "");
-        return name;
-    }
-
-    private String classLoaderNameAndId(ResolvedJavaType type) {
-        return uniqueShortLoaderName(getClassLoader(type));
-    }
-
-    private static String stripOuterClass(String name) {
-        return name.substring(name.lastIndexOf('$') + 1);
-    }
-
-    private static ClassLoader getClassLoader(ResolvedJavaType type) {
-        if (type.isPrimitive()) {
-            return null;
-        }
-        if (type.isArray()) {
-            return getClassLoader(type.getElementalType());
-        }
-        return type.getClass().getClassLoader();
-    }
-
-    private final List<ClassLoader> ignoredLoaders;
-
-    private static final String BUILTIN_CLASSLOADER_NAME = "jdk.internal.loader.BuiltinClassLoader";
-
-    private static boolean isBuiltinLoader(ClassLoader loader) {
-        if (loader == null) {
-            return true;
-        }
-        // built in loaders are all subclasses of jdk.internal.loader.BuiltinClassLoader
-        Class<?> loaderClazz = loader.getClass();
-        do {
-            if (loaderClazz.getName().equals(BUILTIN_CLASSLOADER_NAME)) {
-                return true;
-            }
-            loaderClazz = loaderClazz.getSuperclass();
-        } while (loaderClazz != Object.class);
-
-        return false;
-    }
-
-    private boolean isGraalImageLoader(ClassLoader loader) {
-        // Graal installs its own system loader and loaders for application and image classes whose
-        // classes do not need qualifying with a loader id
-        if (ignoredLoaders.contains(loader)) {
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * mangle a resolved method name in a format the binutils demangler will understand. This should
-     * allow all Linux tools to translate mangled symbol names to recognisable Java names in the
-     * same format as derived from the DWARF info, i.e. fully qualified classname using '.'
-     * separator, member name separated using '::' and parameter/return types printed either using
-     * the Java primitive name or, for oops, as a pointer to a struct whose name is that of the Java
-     * type.
-     *
-     * @param declaringClass the class owning the method implementation
-     * @param memberName the simple name of the method
-     * @param methodSignature the signature of the method
-     * @param isConstructor true if this method is a constructor otherwise false
-     * @return a unique mangled name for the method
-     */
-    public String bfdMangle(String loaderName, ResolvedJavaType declaringClass, String memberName, Signature methodSignature, boolean isConstructor) {
-        /*-
-         * The bfd library demangle API currently supports decoding of Java names if they are
-         * using a scheme similar to that used for C++. Unfortunately, none of the tools which
-         * reply on this API pass on the details that the mangled name in question is for a
-         * Java method. However, it is still possible to pass a name to the demangler that the
-         * C++ demangle algorithm will correctly demangle to a Java name. The scheme used mirrors
-         * the one used to generate names for DWARF. It assumes that the linker can tolerate '.',
-         * '[' and ']' characters in an ELF symbol name, which is not a problem on Linux.
-         *
-         * The BFD Demangle Encoding and Algorithm:
-         *
-         * The bfd Java encoding expects java symbols in variant of C++ format.
-         *
-         * A mangled name starts with "_Z"
-         *
-         * Base Symbols:
-         *
-         * Base symbols encode with a decimal length prefix followed by the relevant characters:
-         *
-         *   Foo -> 3Foo
-         *   org.my.Foo -> 10org.my.Foo
-         *
-         * n.b. The latter encoding is acceptable by the demangler -- characters in the encoded
-         * symbol text are opaque as far as it is concerned.
-         *
-         * Qualified Class Name Symbols:
-         *
-         * The standard Java encoding assumes that the package separator '.' will be elided from
-         * symbol text by encoding a package qualified class name as a (possibly recursive)
-         * namespace encoding:
-         *
-         *   org.my.Foo -> N3org2my3FooE
-         *
-         * The bfd Java demangle algorithm understands that the leading base symbols in this
-         * encoding are package elements and will successfully demangle this latter encoding to
-         * "org.my.Foo". However, the default C++ demangle algorithm will demangle it to
-         * "org::my::Foo" since it regards the leading base symbols as naming namespaces.
-         *
-         * In consequence, the Graal mangler chooses to encode Java package qualified class names
-         * in the format preceding the last one i.e. as a single base symbol with embedded '.'
-         * characters:
-         *
-         *   org.my.Foo -> 10org.my.Foo
-         *
-         * Qualified Member Name Symbols:
-         *
-         * A member name is encoded by concatenating the class name and member selector
-         * as elements of a hierarchical (namespace) encoding:
-         *
-         *   org.my.Foo.doAFoo -> N10org.my.Foo6doAFooE
-         *   org.my.Foo.doAFoo -> N3org2my3Foo6doAFooE
-         *
-         * Note again that although the Java demangle algorithm expects the second of the above
-         * two formats the Graal mangler employs the preceding format where the package and class
-         * name are presented as a single base name with embedded '.' characters.
-         *
-         * Loader Qualified Class and Member Name Symbols:
-         *
-         * The above encoding scheme can fail to guarantee uniqueness of the encoding of a class
-         * name or the corresponding class component of a qualified member name. Duplication can
-         * occur when the same class bytecode is loaded by more than one class loader. Not all
-         * loaders are susceptible to this problem. However, for those that are susceptible a unique
-         * id String for the class loader needs to be composed with the class id String:
-         *
-         * {loader AppLoader @1234}org.my.Foo -> N14AppLoader_123410org.my.FooE
-         * {loader AppLoader @1234}org.my.Foo.doAFoo -> N14AppLoader_123410org.my.Foo6doAFooE
-         *
-         * Note that in order for the debugger and Linux tools to be able to link the demangled name
-         * to the corresponding DWARF records the same namespace identifier must be used to qualify the
-         * DWARF declaration of the class.
-         *
-         * Qualified Method Name With Signature Symbols:
-         *
-         * A full encoding for a method name requires concatenating encodings for the return
-         * type and parameter types to the method name encoding.
-         *
-         * <rType> <method> '(' <paramType>+ ')' -> <methodencoding> 'J' (<rTypeencoding> <paramencoding>+ | 'v')
-         *
-         * Return Types:
-         *
-         * The encoding for the return type is appended first. It is preceded with a 'J', to
-         * mark it as a return type rather than the first parameter type. A primitive return type
-         * is encoded using a single letter encoding (see below). An object return type employs the
-         * qualified class encoding described with a preceding 'P', to mark it as a pointer type:
-         *
-         *   java.lang.String doAFoo(...) -> <methodencoding> JP16java.lang.String <paramencoding>+
-         *
-         * An array return type also requires a J and P prefix. Details of the array type encoding
-         * are provided below.
-         *
-         * Note that a pointer type is employed for consistency with the DWARF type scheme.
-         * That scheme also models oops as pointers to a struct whose name is taken from the
-         * Java class.
-         *
-         * Note also that a foreign pointer type -- i.e. a Java interface or, occasionally, class used to
-         * model a native pointer -- is encoded without the P prefix. That is because for such cases the
-         * DWARF encoding uses the Java name as a typedef to the relevant pointer type.
-         *
-         * Void Signatures and Void Return Types:
-         *
-         * If the method has no parameters then this is represented by encoding the single type
-         * void using the standard primitive encoding for that type:
-         *
-         *   void -> v
-         *
-         * The void encoding is also used to encode the return type of a void method:
-         *
-         *   void doAFoobar(...) <methodencoding> Jv <paramencoding>+
-         *
-         * Non-void Signatures
-         *
-         * Parameter type encodings are simply appended in order. The demangle algorithm detects
-         * the parameter count by decoding each successive type encoding. Parameters have either
-         * primitive type, non-array object type or array type.
-         *
-         * Primitive Parameter Types
-         *
-         * Primitive parameter types (or return types) may be encoded using a single letter
-         * where the symbol encodes a C++ primitive type with the same name and bit layout and
-         * interpretation as the Java type:
-         *
-         *   short -> s
-         *   int -> i
-         *   long -> l
-         *   float -> f
-         *   double -> d
-         *   void -> v
-         *
-         * Other primitive types need to be encoded as symbols
-         *
-         *   boolean -> 7boolean
-         *   byte -> 4byte
-         *   char -> 4char
-         *
-         * In these latter cases the single letter encodings that identifies a C++ type with the
-         * same bit layout and interpretation (respectively, b, c and t) encode for differently
-         * named C++ type (respectively "bool", "char", and unsigned short). Their use would cause
-         * method signatures to be printed with a wrong and potentially misleading name e.g. if
-         * method void Foo::foo(boolean, byte, char) were encoded as _ZN3Foo3fooEJvbct it would
-         * then demangle as Foo::foo(bool, char, unsigned short).
-         *
-         * It would be possible to encode the name "char" using symbol c. However, that would
-         * suggest that the type is a signed 8 bit value rather than a 16 bit unsigned value.
-         * Note that the info section includes appropriate definitions of these three primitive
-         * types with the appropriate names so tools which attempt to resolve the symbol names to
-         * type info should work correctly.
-         *
-         * Object parameter types (which includes interfaces and enums) are encoded using the class
-         * name encoding described above preceded by  prefix 'P'.
-         *
-         * java.lang.String doAFoo(int, java.lang.String) ->  <methodencoding> JP16java.lang.StringiP16java.lang.String
-         *
-         * Note that no type is emitted for the implicit 'this' argument
-         *
-         * Array Parameter Types:
-         *
-         * Array parameter types are encoded as bare symbols with the relevant number of square bracket pairs
-         * for their dimension:
-         *
-         *   Foo[] -> 5Foo[]
-         *   java.lang.Object[][] -> 20java.lang.Object[][]
-         *
-         * Note that the Java bfd encoding expects arrays to be encoded as template type instances
-         * using a pseudo-template named JArray, with 'I' and 'E' appended as start and end delimiters
-         * for the embedded template argument lists:
-         *
-         * Foo[] ->  P6JArrayI3FooE
-         * Foo[][] ->  P6JArrayIP6JArrayI3FooEE
-         *
-         * The bfd Java demangler recognises this special pseudo template and translates it back into
-         * the expected Java form i.e. decode(JArray<XXX>) -> concatenate(decode(XXX), "[]"). However,
-         * the default bfd C++ demangler will not perform this translation.
-         *
-         * Foreign pointer parameter types (which may be modeled as interfaces and enums) are encoded using the
-         * class name encoding but without the prefix 'P'.
-         *
-         * com.oracle.svm.core.posix.PosixUtils.dlsym(org.graalvm.word.PointerBase, java.lang.String)
-         *   ->
-         * <method-encoding>  28org.graalvm.word.PointerBaseP16java.lang.String
-         *
-         *
-         * Note that no type is emitted for the implicit 'this' argument
-         *
-         * Substitutions:
-         *
-         * Symbol elements are indexed and can be referenced using a shorthand index, saving
-         * space in the symbol encoding.
-         *
-         * For example, consider C++ equivalent of String.compareTo and its corresponding encoding
-         *
-         *   int java::lang::String::compareTo(java::lang::String)
-         *      -> _ZN4java4lang6String9compareToEJiPS1_
-         *
-         * The symbol encodings 4java, 4lang and 6String and 9compareTo establish
-         * successive bindings for the indexed substitution variables S_, S0_, S1_ and S2_ to
-         * the respective names java, java::lang, java::lang::String and java::lang::String::compareTo
-         * (note that bindings accumulate preceding elements when symbols occur inside a namespace).
-         * The encoding of the argument list uses S1_ references the 3rd binding i.e. the class
-         * name. The problem with this, as seen before when using namespaces is that the demangler
-         * accumulates names using a '::' separator between the namespace components rather than the
-         * desired  '.' separator.
-         *
-         * The Graal encoding can work around the namespace separator as shown earlier and still employ
-         * symbols as prefixes in order to produce more concise encodings. The full encoding for the
-         *
-         *   int String.compareTo(java.lang.String)
-         *      -> _ZN16java.lang.String9compareToEJiP16java.lang.String
-         *
-         * However, it is also possible to encode this more concisely as
-         *
-         *   int String.compareTo(java.lang.String)
-         *      -> _ZN16java.lang.String9compareToEJiPS_
-         *
-         * S_ translates to the first symbol introduced in the namespace i.e. java.lang.String.
-         *
-         * Substitutions can also occur when parameter types are repeated e.g.
-         *
-         * int Arrays.NaturalOrder.compare(Object first, Object second)
-         *      -> _ZN19Arrays$NaturalOrder7compareEJiPP16java.lang.ObjectPS_2
-         * 
-         * In this case the class name symbol 19Arrays$NaturalOrder binds $_ to Arrays$NaturalOrder,
-         * the method name symbol 7compare binds $1_ to Arrays$NaturalOrder::compareTo and the
-         * first parameter type name symbol 16java.lang.Object binds $2_ to java.lang.Object.
-         *
-         * Indexed symbol references are encoded as "S_", "S0_", ... "S9_", "SA_", ... "SZ_", "S10_", ...
-         * i.e. after "$_" for index 0, successive encodings for index i embded the base 36 digits for
-         * (i - 1) between "S" and "_".
-         */
-        return new BFDMangler(this).mangle(loaderName, declaringClass, memberName, methodSignature, isConstructor);
-    }
-
-    /**
-     * mangle a reflective member name in a format the binutils demangler will understand. This
-     * should allow all Linux tools to translate mangled symbol names to recognisable Java names in
-     * the same format as derived from the DWARF info, i.e. fully qualified classname using '.'
-     * separator, member name separated using '::' and parameter/return types printed either using
-     * the Java primitive name or, for oops, as a pointer to a struct whose name is that of the Java
-     * type.
-     *
-     * @param m the reflective member whose name is to be mangled
-     * @return a unique mangled name for the method
-     */
-    public String bfdMangle(Member m) {
-        return new BFDMangler(this).mangle(m);
-    }
-
-    private static class BFDMangler {
-
-        /*
-         * The mangler tracks certain elements as they are inserted into the mangled name and bind
-         * short symbol name to use as substitutions. If the same element needs to be re-inserted
-         * the mangler can embed the short symbol instead of generating the previously mangled full
-         * text. Short symbol names are bound in a well-defined sequence at well-defined points
-         * during mangling. This allows the demangler to identify exactly which text in the previous
-         * input must be used to replace a short symbol name.
-         *
-         * A binding occurs whenever a simple name is mangled outside of a namespace. For example, a
-         * top level class name mangled as 3Hello will bind the next available short name with the
-         * result that it will demangle to Hello and can be used to replace later occurrences of the
-         * string Hello.
-         *
-         * When a sequence of simple names is mangled inside a namespace substitution bindings are
-         * recorded for each successive composite namespace prefix but not for the final symbol
-         * itself. For example, when method name Hello::main is mangled to N5Hello4main4 a single
-         * binding is recorded which demangles to Hello. If a class has been loaded by an
-         * application loader and, hence, has a name which includes a loader namespace prefix (to
-         * avoid the possibility of the same named class being loaded by two different loaders) then
-         * the method name, say AppCL504::Hello::main, would mangle to a namespace encoding with 3
-         * elements N8AppCL5045Hello4mainE which would introduce two bindings, the first demangling
-         * to AppCL504 and the second to AppCL504::Hello.
-         *
-         * A binding is also recorded whenever a pointer type is mangled. The bound symbol demangles
-         * to whatever the text decoded from the scope of the P followed by a '*' to translate it to
-         * a pointer type. For example, when the type in parameter list (Foo*) is encoded as P3Foo a
-         * binding is recorded for the pointer as well as for the type Foo. The first binding
-         * demangles to Foo. The second binding demangles to Foo*.
-         *
-         * n.b. repeated use of the pointer operator results in repeated bindings. So, if a
-         * parameter with type Foo** were to be encoded as PP3Foo three bindings would recorded
-         * which demangle to Foo, Foo* and Foo**. However, multiple indirections do not occur in
-         * Java signatures.
-         *
-         * n.b.b. repeated mentions of the same symbol would redundantly record a binding. For
-         * example if int Hello::compareTo(Hello*) were mangled to _ZN5Hello9compareToEJiP5Hello the
-         * resulting bindings would be S_ ==> Hello, S0_ ==> Hello::compareTo, S1_ ==> Hello and S2_
-         * ==> Hello* i.e. both S_ and S1_ would demangle to Hello. This situation should never
-         * arise. A name can always be correctly encoded without repeats. In the above example that
-         * would be _ZN5Hello9compareToEJiPS_.
-         */
-        final SubstrateBFDNameProvider nameProvider;
-        final StringBuilder sb;
-
-        // A list of lookup names identifying substituted elements. A prospective name for an
-        // element that is about to be encoded can be looked up in this list. If a match is found
-        // the list index can be used to identify the relevant short symbol. If it is not found
-        // then inserting the name serves to allocate the short name associated with the inserted
-        // element's result index.
-        /**
-         * A map relating lookup names to the index for the corresponding short name with which it
-         * can be substituted. A prospective name for an element that is about to be encoded can be
-         * looked up in this list. If a match is found the list index can be used to identify the
-         * relevant short symbol. If it is not found then it can be inserted to allocate the next
-         * short name, using the current size of the map to identify the next available index.
-         */
-        EconomicMap<LookupName, Integer> bindings;
-
-        /**
-         * A lookup name is used as a key to record and subsequently lookup a short symbol that can
-         * replace one or more elements of a mangled name. A lookup name is usually just a simple
-         * string, i.e. some text that is to be mangled and to which the corresponding short symbol
-         * should decode. However, substitutions for namespace prefixes (e.g. AppLoader506::Foo)
-         * require a composite lookup name that composes a namespace prefix (AppLoader506) with a
-         * trailing simple name (Foo). The corresponding short symbol will demangle to the string
-         * produced by composing the prefix and simple name with a :: separator (i.e. restoring
-         * AppLoader506::Foo). Short symbols for pointer types (e.g. Foo* or AppLoader506::Foo*)
-         * require a pointer lookup name that identifies the substituted text as a pointer to some
-         * underlying type (e.g. Foo or AppLoader506::Foo). The corresponding short symbol will
-         * demangle to the string produced by demangling the underlying type with a * suffix. In
-         * theory the prefix for a pointer type lookup name might be defined by another pointer type
-         * lookup name (if, say, we needed to encode type Foo**). In practice, that case should not
-         * arise with Java method signatures.
-         */
-        private sealed interface LookupName permits SimpleLookupName, CompositeLookupName {
-        }
-
-        private sealed interface CompositeLookupName extends LookupName permits NamespaceLookupName, PointerLookupName {
-        }
-
-        private record SimpleLookupName(String value) implements LookupName {
-            @Override
-            public String toString() {
-                return value;
-            }
-        }
-
-        private record NamespaceLookupName(String prefix, LookupName tail) implements CompositeLookupName {
-            @Override
-            public String toString() {
-                return prefix + "::" + tail.toString();
-            }
-        }
-
-        private record PointerLookupName(LookupName tail) implements CompositeLookupName {
-            @Override
-            public String toString() {
-                return tail.toString() + "*";
-            }
-        }
-
-        BFDMangler(SubstrateBFDNameProvider provider) {
-            nameProvider = provider;
-            sb = new StringBuilder("_Z");
-            bindings = EconomicMap.create();
-        }
-
-        public String mangle(String loaderName, ResolvedJavaType declaringClass, String memberName, Signature methodSignature, boolean isConstructor) {
-            String fqn = declaringClass.toJavaName();
-            String selector = memberName;
-            if (isConstructor) {
-                assert methodSignature != null;
-                // replace <init> with the class name n.b. it may include a disambiguating suffix
-                assert selector.startsWith("<init>");
-                String replacement = fqn;
-                int index = replacement.lastIndexOf('.');
-                if (index >= 0) {
-                    replacement = fqn.substring(index + 1);
-                }
-                selector = selector.replace("<init>", replacement);
-            }
-            mangleClassAndMemberName(loaderName, fqn, selector);
-            if (methodSignature != null) {
-                if (!isConstructor) {
-                    mangleReturnType(methodSignature, declaringClass);
-                }
-                mangleParams(methodSignature, declaringClass);
-            }
-            return sb.toString();
-        }
-
-        public String mangle(Member member) {
-            Class<?> declaringClass = member.getDeclaringClass();
-            String loaderName = nameProvider.uniqueShortLoaderName(declaringClass.getClassLoader());
-            String className = declaringClass.getName();
-            String selector = member.getName();
-            boolean isConstructor = member instanceof Constructor<?>;
-            boolean isMethod = member instanceof Method;
-
-            if (isConstructor) {
-                assert selector.equals("<init>");
-                selector = SubstrateUtil.stripPackage(className);
-            }
-            mangleClassAndMemberName(loaderName, className, selector);
-            if (isMethod) {
-                Method method = (Method) member;
-                mangleReturnType(method.getReturnType());
-            }
-            if (isConstructor || isMethod) {
-                Executable executable = (Executable) member;
-                mangleParams(executable.getParameters());
-            }
-            return sb.toString();
-        }
-
-        private void mangleWriteSimpleName(String s) {
-            // a simple name starting with a digit would invalidate the C++ mangled encoding scheme
-            assert !s.startsWith("[0-9]");
-            sb.append(s.length());
-            sb.append(s);
-        }
-
-        private void mangleWriteSubstitutableNameRecord(String name) {
-            LookupName lookupName = new SimpleLookupName(name);
-            if (!substituteName(new SimpleLookupName(name))) {
-                // failed so mangle the name and create a binding to track it
-                mangleWriteSimpleName(name);
-                recordName(lookupName);
-            }
-        }
-
-        private void mangleWriteSubstitutableNameNoRecord(String name) {
-            LookupName lookupName = new SimpleLookupName(name);
-            if (!substituteName(lookupName)) {
-                // failed so mangle the name
-                mangleWriteSimpleName(name);
-            }
-        }
-
-        private void mangleWriteSubstitutablePrefixedName(String prefix, String name) {
-            // this should only be called when inserting a sequence into a namespace
-            assert sb.charAt(sb.length() - 1) == 'N';
-            // we can substitute both symbols
-            mangleWriteSubstitutableNameRecord(prefix);
-            // In theory the trailing name at the end of the namespace ought
-            // to be able to be encoded with a short name i.e. we ought to be
-            // able to call mangleWriteSubstitutableNameNoRecord(name) at this
-            // point. Indeed, the demangler *will* translate a substitution when it
-            // is inserted at the end of a namespace e.g.
-            //
-            // c++filt _ZN3foo3barS_E --> foo::bar::foo
-            //
-            // However, gdb will barf on the $_ and refuse to translate the symbol
-            // So, in order to keep gdb happy we have to avoid translating this
-            // final name ane emit it as a simple name e.g. with the above example
-            // we would generate _ZN3foo3bar3fooE.
-
-            mangleWriteSimpleName(name);
-        }
-
-        private void mangleWriteSubstitutablePrefixedName(String prefix1, String prefix2, String name) {
-            // this should only be called when inserting a sequence into a namespace
-            assert sb.charAt(sb.length() - 1) == 'N';
-            // we can substitute the composed prefix followed by the name
-            // or we can substitute all three individual symbols
-            LookupName simpleLookupName = new SimpleLookupName(prefix2);
-            LookupName namespaceLookupName = new NamespaceLookupName(prefix1, simpleLookupName);
-            // try substituting the combined prefix
-            if (!substituteName(namespaceLookupName)) {
-                // we may still be able to establish a binding for the first prefix
-                mangleWriteSubstitutableNameRecord(prefix1);
-                // we cannot establish a binding for the trailing prefix
-                mangleWriteSubstitutableNameNoRecord(prefix2);
-                recordName(namespaceLookupName);
-            }
-            // see comment in previous method as to why we call mangleWriteSimpleName
-            // instead of mangleWriteSubstitutableNameNoRecord(name)
-            mangleWriteSimpleName(name);
-        }
-
-        private boolean substituteName(LookupName name) {
-            Integer index = bindings.get(name);
-            if (index != null) {
-                writeSubstitution(index.intValue());
-                return true;
-            }
-            return false;
-        }
-
-        private static boolean encodeLoaderName(String loaderName) {
-            return loaderName != null && !loaderName.isEmpty();
-        }
-
-        private void mangleClassAndMemberName(String loaderName, String className, String methodName) {
-            sb.append('N');
-            if (encodeLoaderName(loaderName)) {
-                mangleWriteSubstitutablePrefixedName(loaderName, className, methodName);
-            } else {
-                mangleWriteSubstitutablePrefixedName(className, methodName);
-            }
-            sb.append('E');
-        }
-
-        private void mangleClassName(String loaderName, String className) {
-            boolean encodeLoaderName = encodeLoaderName(loaderName);
-            if (encodeLoaderName) {
-                sb.append('N');
-                // only leading elements of namespace encoding may be recorded as substitutions
-                mangleWriteSubstitutablePrefixedName(loaderName, className);
-                sb.append('E');
-            } else {
-                mangleWriteSubstitutableNameRecord(className);
-            }
-        }
-
-        private void mangleClassPointer(String loaderName, String className) {
-            boolean encodeLoaderName = encodeLoaderName(loaderName);
-            LookupName classLookup = new SimpleLookupName(className);
-            LookupName lookup = classLookup;
-            if (encodeLoaderName) {
-                lookup = new NamespaceLookupName(loaderName, classLookup);
-            }
-            PointerLookupName pointerLookup = new PointerLookupName(lookup);
-            // see if we can use a short name for the pointer
-            if (!substituteName(pointerLookup)) {
-                // failed - so we need to mark this as a pointer,
-                // encode the class name and (only) then record a
-                // binding for the pointer, ensuring that bindings
-                // for the pointer type and its referent appear in
-                // the expected order
-                sb.append("P");
-                mangleClassName(loaderName, className);
-                recordName(pointerLookup);
-            }
-        }
-
-        private void mangleReturnType(Signature methodSignature, ResolvedJavaType owner) {
-            sb.append('J');
-            mangleType(methodSignature.getReturnType(owner));
-        }
-
-        private void mangleReturnType(Class<?> type) {
-            sb.append('J');
-            mangleType(type);
-        }
-
-        private void mangleParams(Signature methodSignature, ResolvedJavaType owner) {
-            int count = methodSignature.getParameterCount(false);
-            if (count == 0) {
-                mangleTypeChar('V');
-            } else {
-                for (int i = 0; i < count; i++) {
-                    mangleType(methodSignature.getParameterType(i, owner));
-                }
-            }
-        }
-
-        private void mangleParams(Parameter[] params) {
-            if (params.length == 0) {
-                mangleTypeChar('V');
-            } else {
-                for (int i = 0; i < params.length; i++) {
-                    mangleType(params[i].getType());
-                }
-            }
-        }
-
-        private void mangleType(JavaType type) {
-            if (type instanceof ResolvedJavaType) {
-                mangleType((ResolvedJavaType) type);
-            } else if (type instanceof UnresolvedJavaType) {
-                mangleType((UnresolvedJavaType) type);
-            } else {
-                throw VMError.shouldNotReachHere("Unexpected JavaType for " + type);
-            }
-        }
-
-        private void mangleType(ResolvedJavaType type) {
-            if (type.isPrimitive()) {
-                manglePrimitiveType(type);
-            } else if (type.isArray()) {
-                mangleArrayType(type);
-            } else {
-                String loaderName = nameProvider.classLoaderNameAndId(type);
-                String className = type.toJavaName();
-                if (nameProvider.needsPointerPrefix(type)) {
-                    mangleClassPointer(loaderName, className);
-                } else {
-                    mangleClassName(loaderName, className);
-                }
-            }
-        }
-
-        private void mangleType(UnresolvedJavaType type) {
-            if (type.isArray()) {
-                mangleArrayType(type);
-            } else {
-                mangleClassPointer("", type.toJavaName());
-            }
-        }
-
-        private void mangleType(Class<?> type) {
-            if (type.isPrimitive()) {
-                manglePrimitiveType(type);
-            } else if (type.isArray()) {
-                mangleArrayType(type);
-            } else {
-                mangleClassPointer(nameProvider.uniqueShortLoaderName(type.getClassLoader()), type.getName());
-            }
-        }
-
-        private void mangleArrayType(ResolvedJavaType arrayType) {
-            // It may seem odd that we consider placing array names in a namespace.
-            // However, note that we are faking arrays with classes that embed the
-            // actual array data and identifying the array class with a name derived by
-            // appending [] pairs to the base element type. If the base name may need a
-            // loader namespace prefix to disambiguate repeated loads of the same class
-            // then so may this fake array name.
-            int count = 1;
-            ResolvedJavaType baseType = arrayType.getComponentType();
-            while (baseType.isArray()) {
-                count++;
-                baseType = baseType.getComponentType();
-            }
-            String loaderName = nameProvider.classLoaderNameAndId(baseType);
-            mangleArrayPointer(loaderName, baseType.toJavaName(), count);
-        }
-
-        private void mangleArrayType(UnresolvedJavaType arrayType) {
-            int count = 1;
-            JavaType baseType = arrayType.getComponentType();
-            while (baseType.isArray()) {
-                count++;
-                baseType = baseType.getComponentType();
-            }
-            mangleArrayPointer("", baseType.toJavaName(), count);
-        }
-
-        private void mangleArrayType(Class<?> arrayType) {
-            // See above for why we consider placing array names in a namespace.
-            int count = 1;
-            Class<?> baseType = arrayType.getComponentType();
-            while (baseType.isArray()) {
-                baseType = baseType.getComponentType();
-            }
-            String loaderName = nameProvider.uniqueShortLoaderName(baseType.getClassLoader());
-            mangleArrayPointer(loaderName, baseType.getName(), count);
-        }
-
-        private void mangleArrayPointer(String loaderName, String baseName, int dims) {
-            // an array is just a class with a name that includes trailing [] pairs
-            mangleClassPointer(loaderName, makeArrayName(baseName, dims));
-        }
-
-        private static String makeArrayName(String baseName, int dims) {
-            StringBuilder sb1 = new StringBuilder();
-            sb1.append(baseName);
-            for (int i = 0; i < dims; i++) {
-                sb1.append("[]");
-            }
-            return sb1.toString();
-        }
-
-        private void manglePrimitiveType(ResolvedJavaType type) {
-            char c = type.getJavaKind().getTypeChar();
-            mangleTypeChar(c);
-        }
-
-        private void manglePrimitiveType(Class<?> type) {
-            char c = JavaKind.fromJavaClass(type).getTypeChar();
-            mangleTypeChar(c);
-        }
-
-        private void mangleTypeChar(char c) {
-            // we can use single char encodings for most primitive types
-            // but we need to encode boolean, byte and char specially
-            switch (c) {
-                case 'Z':
-                    mangleWriteSubstitutableNameRecord("boolean");
-                    return;
-                case 'B':
-                    mangleWriteSubstitutableNameRecord("byte");
-                    return;
-                case 'S':
-                    sb.append("s");
-                    return;
-                case 'C':
-                    mangleWriteSubstitutableNameRecord("char");
-                    return;
-                case 'I':
-                    sb.append("i");
-                    return;
-                case 'J':
-                    sb.append("l");
-                    return;
-                case 'F':
-                    sb.append("f");
-                    return;
-                case 'D':
-                    sb.append("d");
-                    return;
-                case 'V':
-                    sb.append("v");
-                    return;
-                default:
-                    // should never reach here
-                    assert false : "invalid kind for primitive type " + c;
-            }
-        }
-
-        private void writeSubstitution(int i) {
-            sb.append('S');
-            // i = 0 has no digits, i = 1 -> 0, ... i = 10 -> 9, i = 11 -> A, ... i = 36 -> Z, i =
-            // 37 -> 10, ...
-            // allow for at most up 2 base 36 digits
-            if (i > 36) {
-                sb.append(b36((i - 1) / 36));
-                sb.append(b36((i - 1) % 36));
-            } else if (i > 0) {
-                sb.append(b36(i - 1));
-            }
-            sb.append('_');
-        }
-
-        private static char b36(int i) {
-            if (i < 10) {
-                return (char) ('0' + i);
-            } else {
-                return (char) ('A' + (i - 10));
-            }
-        }
-
-        private void recordName(LookupName name) {
-            bindings.put(name, bindings.size());
-        }
-    }
-
-    /**
-     * Determine whether a type modeled as a Java object type needs to be encoded using pointer
-     * prefix P.
-     *
-     * @param type The type to be checked.
-     * @return true if the type needs to be encoded using pointer prefix P otherwise false.
-     */
-    private boolean needsPointerPrefix(ResolvedJavaType type) {
-        return true;
-    }
-
-}
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
index e0f2924990aa..f4ecd692a155 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
@@ -97,10 +97,10 @@ private interface Handle extends InstalledCodeObserverHandle {
         int RELEASED = 2;
 
         @RawField
-        GDBJITInterfaceSystemJava.JITCodeEntry getRawHandle();
+        GDBJITInterface.JITCodeEntry getRawHandle();
 
         @RawField
-        void setRawHandle(GDBJITInterfaceSystemJava.JITCodeEntry value);
+        void setRawHandle(GDBJITInterface.JITCodeEntry value);
 
         @RawField
         NonmovableArray<Byte> getDebugInfoData();
@@ -119,7 +119,7 @@ static final class Accessor implements InstalledCodeObserverHandleAccessor {
 
         static Handle createHandle(NonmovableArray<Byte> debugInfoData) {
             Handle handle = UnmanagedMemory.malloc(SizeOf.get(Handle.class));
-            GDBJITInterfaceSystemJava.JITCodeEntry entry = UnmanagedMemory.calloc(SizeOf.get(GDBJITInterfaceSystemJava.JITCodeEntry.class));
+            GDBJITInterface.JITCodeEntry entry = UnmanagedMemory.calloc(SizeOf.get(GDBJITInterface.JITCodeEntry.class));
             handle.setAccessor(ImageSingletons.lookup(Accessor.class));
             handle.setRawHandle(entry);
             handle.setDebugInfoData(debugInfoData);
@@ -136,7 +136,7 @@ public void activate(InstalledCodeObserverHandle installedCodeObserverHandle) {
             NonmovableArray<Byte> debugInfoData = handle.getDebugInfoData();
             CCharPointer address = NonmovableArrays.addressOf(debugInfoData, 0);
             int size = NonmovableArrays.lengthOf(debugInfoData);
-            GDBJITInterfaceSystemJava.registerJITCode(address, size, handle.getRawHandle());
+            GDBJITInterface.registerJITCode(address, size, handle.getRawHandle());
 
             handle.setState(Handle.ACTIVATED);
         }
@@ -147,8 +147,8 @@ public void release(InstalledCodeObserverHandle installedCodeObserverHandle) {
             VMOperation.guaranteeInProgress("SubstrateDebugInfoInstaller.Accessor.release must run in a VMOperation");
             VMError.guarantee(handle.getState() == Handle.ACTIVATED);
 
-            GDBJITInterfaceSystemJava.JITCodeEntry entry = handle.getRawHandle();
-            GDBJITInterfaceSystemJava.unregisterJITCode(entry);
+            GDBJITInterface.JITCodeEntry entry = handle.getRawHandle();
+            GDBJITInterface.unregisterJITCode(entry);
 
             handle.setState(Handle.RELEASED);
             NonmovableArrays.releaseUnmanagedArray(handle.getDebugInfoData());
@@ -172,8 +172,8 @@ public void attachToCurrentIsolate(InstalledCodeObserverHandle installedCodeObse
         public void releaseOnTearDown(InstalledCodeObserverHandle installedCodeObserverHandle) {
             Handle handle = (Handle) installedCodeObserverHandle;
             if (handle.getState() == Handle.ACTIVATED) {
-                GDBJITInterfaceSystemJava.JITCodeEntry entry = handle.getRawHandle();
-                GDBJITInterfaceSystemJava.unregisterJITCode(entry);
+                GDBJITInterface.JITCodeEntry entry = handle.getRawHandle();
+                GDBJITInterface.unregisterJITCode(entry);
                 handle.setState(Handle.RELEASED);
             }
             NonmovableArrays.releaseUnmanagedArray(handle.getDebugInfoData());
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
index 2b04eff75881..bc6bc304d4e7 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
@@ -29,7 +29,6 @@
 import java.util.stream.Stream;
 
 import org.graalvm.collections.Pair;
-import org.graalvm.nativeimage.ImageSingletons;
 
 import com.oracle.objectfile.debugentry.ArrayTypeEntry;
 import com.oracle.objectfile.debugentry.ClassEntry;
@@ -58,7 +57,7 @@ public class SubstrateDebugInfoProvider extends SharedDebugInfoProvider {
 
     public SubstrateDebugInfoProvider(DebugContext debug, SharedMethod method, CompilationResult compilation, RuntimeConfiguration runtimeConfiguration, MetaAccessProvider metaAccess,
                     long codeAddress) {
-        super(debug, runtimeConfiguration, metaAccess, ImageSingletons.lookup(SubstrateBFDNameProvider.class), SubstrateOptions.getRuntimeSourceDestDir());
+        super(debug, runtimeConfiguration, metaAccess, SubstrateOptions.getRuntimeSourceDestDir());
         this.method = method;
         this.compilation = compilation;
         this.codeAddress = codeAddress;
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoFeature.java
index 23d822604f97..e8c8a0780187 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoFeature.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoFeature.java
@@ -34,8 +34,6 @@
 import com.oracle.svm.core.ReservedRegisters;
 import jdk.graal.compiler.word.Word;
 import com.oracle.svm.core.code.CodeInfoDecoder;
-import com.oracle.svm.core.debug.GDBJITInterfaceSystemJava;
-import com.oracle.svm.core.debug.SubstrateBFDNameProvider;
 import jdk.vm.ci.code.Architecture;
 import org.graalvm.nativeimage.ImageSingletons;
 import org.graalvm.nativeimage.Platform;
@@ -54,6 +52,8 @@
 import com.oracle.svm.core.c.CGlobalData;
 import com.oracle.svm.core.c.CGlobalDataFactory;
 import com.oracle.svm.core.config.ConfigurationValues;
+import com.oracle.svm.core.debug.BFDNameProvider;
+import com.oracle.svm.core.debug.GDBJITInterface;
 import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
 import com.oracle.svm.core.feature.InternalFeature;
 import com.oracle.svm.core.graal.meta.RuntimeConfiguration;
@@ -73,7 +73,6 @@
 @AutomaticallyRegisteredFeature
 @SuppressWarnings("unused")
 class NativeImageDebugInfoFeature implements InternalFeature {
-    private NativeImageBFDNameProvider bfdNameProvider;
 
     @Override
     public boolean isInConfiguration(IsInConfigurationAccess access) {
@@ -88,24 +87,29 @@ public void afterRegistration(AfterRegistrationAccess access) {
          */
         if (!UniqueShortNameProviderDefaultImpl.UseDefault.useDefaultProvider()) {
             if (!ImageSingletons.contains(UniqueShortNameProvider.class)) {
-                // configure a BFD mangler to provide unique short names for method and field
-                // symbols
+                /*
+                 * Configure a BFD mangler to provide unique short names for methods, fields and
+                 * classloaders.
+                 */
                 FeatureImpl.AfterRegistrationAccessImpl accessImpl = (FeatureImpl.AfterRegistrationAccessImpl) access;
-                // the Graal system loader will not duplicate JDK builtin loader classes
+
+                /*
+                 * Ensure the mangle ignores prefix generation for Graal loaders.
+                 *
+                 * The Graal system loader will not duplicate JDK builtin loader classes.
+                 *
+                 * The Graal app loader and image loader and their parent loader will not duplicate
+                 * classes. The app and image loader should both have the same parent.
+                 */
                 ClassLoader systemLoader = ClassLoader.getSystemClassLoader();
-                // the Graal app loader and image loader and their parent loader will not duplicate
-                // classes
                 ClassLoader appLoader = accessImpl.getApplicationClassLoader();
                 ClassLoader imageLoader = accessImpl.getImageClassLoader().getClassLoader();
                 ClassLoader imageLoaderParent = imageLoader.getParent();
-                // the app and image loader should both have the same parent
                 assert imageLoaderParent == appLoader.getParent();
-                // ensure the mangle ignores prefix generation for Graal loaders
                 List<ClassLoader> ignored = List.of(systemLoader, imageLoaderParent, appLoader, imageLoader);
-                bfdNameProvider = new NativeImageBFDNameProvider(ignored);
-                SubstrateBFDNameProvider substrateBFDNameProvider = new SubstrateBFDNameProvider(ignored);
+
+                BFDNameProvider bfdNameProvider = new BFDNameProvider(ignored);
                 ImageSingletons.add(UniqueShortNameProvider.class, bfdNameProvider);
-                ImageSingletons.add(SubstrateBFDNameProvider.class, substrateBFDNameProvider);
             }
         }
     }
@@ -113,18 +117,13 @@ public void afterRegistration(AfterRegistrationAccess access) {
     @Override
     public void beforeAnalysis(BeforeAnalysisAccess access) {
         /*
-         * Make the name provider aware of the native libs
+         * Ensure ClassLoader.nameAndId is available at runtime for type lookup from GDB.
          */
-        if (bfdNameProvider != null) {
-            var accessImpl = (FeatureImpl.BeforeAnalysisAccessImpl) access;
-            bfdNameProvider.setNativeLibs(accessImpl.getNativeLibraries());
-        }
+        access.registerAsAccessed(ReflectionUtil.lookupField(ClassLoader.class, "nameAndId"));
 
         /*
-         * Ensure ClassLoader.nameAndId is available at runtime for type lookup from gdb
+         * Provide some global symbol for the gdb-debughelpers script.
          */
-        access.registerAsAccessed(ReflectionUtil.lookupField(ClassLoader.class, "nameAndId"));
-
         CompressEncoding compressEncoding = ImageSingletons.lookup(CompressEncoding.class);
         CGlobalData<PointerBase> compressionShift = CGlobalDataFactory.createWord(Word.signed(compressEncoding.getShift()), "__svm_compression_shift");
         CGlobalData<PointerBase> useHeapBase = CGlobalDataFactory.createWord(Word.unsigned(compressEncoding.hasBase() ? 1 : 0), "__svm_use_heap_base");
@@ -139,13 +138,34 @@ public void beforeAnalysis(BeforeAnalysisAccess access) {
         CGlobalDataFeature.singleton().registerWithGlobalHiddenSymbol(frameSizeStatusMask);
         CGlobalDataFeature.singleton().registerWithGlobalHiddenSymbol(heapBaseRegnum);
 
+        /*
+         * Create a global symbol for the jit debug descriptor with proper initial values for the
+         * GDB JIT compilation interface.
+         */
         if (SubstrateOptions.RuntimeDebugInfo.getValue()) {
             Architecture arch = ConfigurationValues.getTarget().arch;
-            ByteBuffer buffer = ByteBuffer.allocate(SizeOf.get(GDBJITInterfaceSystemJava.JITDescriptor.class)).order(arch.getByteOrder());
-            buffer.putInt(1);  // version 1
-            buffer.putInt(0);  // action flag 0
-            buffer.putLong(0);  // relevant entry nullptr
-            buffer.putLong(0);  // first entry nullptr
+            ByteBuffer buffer = ByteBuffer.allocate(SizeOf.get(GDBJITInterface.JITDescriptor.class)).order(arch.getByteOrder());
+
+            /*
+             * Set version to 1. Must be 1 otherwise GDB does not register breakpoints for the GDB
+             * JIT Compilation interface.
+             */
+            buffer.putInt(1);
+
+            /* Set action flag to JIT_NOACTION (0). */
+            buffer.putInt(GDBJITInterface.JITActions.JIT_NOACTION.ordinal());
+
+            /*
+             * Set relevant entry to nullptr. This is the pointer to the debug info entry that is
+             * affected by the GDB JIT interface action.
+             */
+            buffer.putLong(0);
+
+            /*
+             * Set first entry to nullptr. This is the pointer to the last debug info entry notified
+             * to the GDB JIT interface We will prepend new entries here.
+             */
+            buffer.putLong(0);
 
             CGlobalDataFeature.singleton().registerWithGlobalSymbol(CGlobalDataFactory.createBytes(buffer::array, "__jit_debug_descriptor"));
         }
@@ -186,6 +206,10 @@ public boolean isLoadable() {
                     };
                 };
 
+                /*
+                 * Create a section that triggers GDB to read debugging assistance information from
+                 * gdb-debughelpers.py in the current working directory.
+                 */
                 Supplier<BasicProgbitsSectionImpl> makeGDBSectionImpl = () -> {
                     var content = AssemblyBuffer.createOutputAssembler(objectFile.getByteOrder());
                     // 1 -> python file
@@ -205,7 +229,7 @@ public boolean isLoadable() {
                 objectFile.newUserDefinedSection(".debug.svm.imagebuild.arguments", makeSectionImpl.apply(DiagnosticUtils.getBuilderArguments(imageClassLoader)));
                 objectFile.newUserDefinedSection(".debug.svm.imagebuild.java.properties", makeSectionImpl.apply(DiagnosticUtils.getBuilderProperties()));
 
-                Path svmDebugHelper = Path.of(System.getProperty("java.home"), "lib/svm/debug/gdb-debughelpers.py");
+                Path svmDebugHelper = Path.of(System.getProperty("java.home"), "lib", "svm", "debug", "gdb-debughelpers.py");
                 if (Files.exists(svmDebugHelper)) {
                     objectFile.newUserDefinedSection(".debug_gdb_scripts", makeGDBSectionImpl.get());
                 }
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 5535a5f464a2..648c58c7e345 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
@@ -69,7 +69,6 @@
 import com.oracle.svm.common.meta.MultiMethod;
 import com.oracle.svm.core.StaticFieldsSupport;
 import com.oracle.svm.core.SubstrateOptions;
-import com.oracle.svm.core.UniqueShortNameProvider;
 import com.oracle.svm.core.debug.SharedDebugInfoProvider;
 import com.oracle.svm.core.graal.meta.RuntimeConfiguration;
 import com.oracle.svm.core.image.ImageHeapPartition;
@@ -122,7 +121,7 @@ class NativeImageDebugInfoProvider extends SharedDebugInfoProvider {
 
     NativeImageDebugInfoProvider(DebugContext debug, NativeImageCodeCache codeCache, NativeImageHeap heap, NativeLibraries nativeLibs, HostedMetaAccess metaAccess,
                     RuntimeConfiguration runtimeConfiguration) {
-        super(debug, runtimeConfiguration, metaAccess, UniqueShortNameProvider.singleton(), SubstrateOptions.getDebugInfoSourceCacheRoot());
+        super(debug, runtimeConfiguration, metaAccess, SubstrateOptions.getDebugInfoSourceCacheRoot());
         this.heap = heap;
         this.codeCache = codeCache;
         this.nativeLibs = nativeLibs;
@@ -186,6 +185,7 @@ private static ResolvedJavaMethod getAnnotatedOrOriginal(ResolvedJavaMethod meth
     }
 
     @Override
+    @SuppressWarnings("try")
     protected void handleDataInfo(Object data) {
         // log ObjectInfo data
         if (debug.isLogEnabled(DebugContext.INFO_LEVEL) && data instanceof NativeImageHeap.ObjectInfo objectInfo) {
@@ -768,6 +768,7 @@ private static void indentElementInfo(StringBuilder stringBuilder, int indent) {
     }
 
     @Override
+    @SuppressWarnings("try")
     public FileEntry lookupFileEntry(ResolvedJavaType type) {
         Class<?> clazz = OriginalClassProvider.getJavaClass(type);
         SourceManager sourceManager = ImageSingletons.lookup(SourceManager.class);
@@ -810,7 +811,7 @@ private int getTypeSize(HostedType type) {
             case HostedPrimitiveType hostedPrimitiveType -> {
                 /* Use the number of bytes needed to store the value. */
                 JavaKind javaKind = hostedPrimitiveType.getStorageKind();
-                return (javaKind == JavaKind.Void ? 0 : javaKind.getByteCount());
+                return javaKind == JavaKind.Void ? 0 : javaKind.getByteCount();
             }
             default -> {
                 return 0;
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageViaCC.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageViaCC.java
index 53e4149885fd..b5ab3f81ed2c 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageViaCC.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageViaCC.java
@@ -167,7 +167,7 @@ private void runLinkerCommand(String imageName, LinkerInvocation inv, List<Strin
 
             if (SubstrateOptions.useDebugInfoGeneration()) {
                 BuildArtifacts.singleton().add(ArtifactType.DEBUG_INFO, SubstrateOptions.getDebugInfoSourceCacheRoot());
-                Path svmDebugHelper = Path.of(System.getProperty("java.home"), "lib/svm/debug/gdb-debughelpers.py");
+                Path svmDebugHelper = Path.of(System.getProperty("java.home"), "lib", "svm", "debug", "gdb-debughelpers.py");
                 if (Files.exists(svmDebugHelper)) {
                     Path svmDebugHelperCopy = imagePath.resolveSibling(svmDebugHelper.getFileName());
                     Files.copy(svmDebugHelper, svmDebugHelperCopy, StandardCopyOption.REPLACE_EXISTING);
diff --git a/substratevm/src/com.oracle.svm.native.debug/include/gdbJITCompilationInterface.h b/substratevm/src/com.oracle.svm.native.debug/include/gdb_jit_compilation_interface.h
similarity index 84%
rename from substratevm/src/com.oracle.svm.native.debug/include/gdbJITCompilationInterface.h
rename to substratevm/src/com.oracle.svm.native.debug/include/gdb_jit_compilation_interface.h
index cee76091b3d5..57a1ff6cf04b 100644
--- a/substratevm/src/com.oracle.svm.native.debug/include/gdbJITCompilationInterface.h
+++ b/substratevm/src/com.oracle.svm.native.debug/include/gdb_jit_compilation_interface.h
@@ -33,11 +33,6 @@ struct jit_descriptor
   struct jit_code_entry *first_entry;
 };
 
-
-/* Make sure to specify the version statically, because the
-   debugger may check the version before we can set it.  */
-// struct jit_descriptor __jit_debug_descriptor = { 1, 0, 0, 0 };
-
 /* GDB puts a breakpoint in this function.  */
 void __attribute__((noinline)) __jit_debug_register_code() { };
 
diff --git a/substratevm/src/com.oracle.svm.native.debug/src/gdbJITCompilationInterface.c b/substratevm/src/com.oracle.svm.native.debug/src/gdbJITCompilationInterface.c
deleted file mode 100644
index 80fe68983afc..000000000000
--- a/substratevm/src/com.oracle.svm.native.debug/src/gdbJITCompilationInterface.c
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
- * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
- */
-
-#include <gdbJITCompilationInterface.h>
-
-#include <stdlib.h>
-#include <assert.h>
-
-/*
-struct jit_code_entry *register_jit_code(const char *addr, uint64_t size)
-{
-    /* Create new jit_code_entry /
-    struct jit_code_entry *const entry = calloc(1, sizeof(*entry));
-    entry->symfile_addr = addr;
-    entry->symfile_size = size;
-
-    /* Insert entry at head of the list. /
-    struct jit_code_entry *const next_entry = __jit_debug_descriptor.first_entry;
-    entry->prev_entry = NULL;
-    entry->next_entry = next_entry;
-
-    if (next_entry != NULL) {
-        next_entry->prev_entry = entry;
-    }
-
-    /* Notify GDB.  /
-    __jit_debug_descriptor.action_flag = JIT_REGISTER;
-    __jit_debug_descriptor.first_entry = entry;
-    __jit_debug_descriptor.relevant_entry = entry;
-    __jit_debug_register_code();
-
-    return entry;
-}
-
-
-void unregister_jit_code(struct jit_code_entry *const entry)
-{
-    struct jit_code_entry *const prev_entry = entry->prev_entry;
-    struct jit_code_entry *const next_entry = entry->next_entry;
-
-    /* Fix prev and next in list /
-    if (next_entry != NULL) {
-	next_entry->prev_entry = prev_entry;
-    }
-
-    if (prev_entry != NULL) {
-	prev_entry->next_entry = next_entry;
-    } else {
-	assert(__jit_debug_descriptor.first_entry == entry);
-	__jit_debug_descriptor.first_entry = next_entry;
-    }
-
-    /* Notify GDB.  /
-    __jit_debug_descriptor.action_flag = JIT_UNREGISTER;
-    __jit_debug_descriptor.relevant_entry = entry;
-    __jit_debug_register_code();
-
-    free(entry);
-}*/
diff --git a/substratevm/src/com.oracle.svm.native.debug/src/gdb_jit_compilation_interface.c b/substratevm/src/com.oracle.svm.native.debug/src/gdb_jit_compilation_interface.c
new file mode 100644
index 000000000000..6a8cdfea014c
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.native.debug/src/gdb_jit_compilation_interface.c
@@ -0,0 +1,7 @@
+/*
+ * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
+ * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
+ */
+
+// dummy include
+#include <gdb_jit_compilation_interface.h>

From 43bf74a0a606e25b4b6343936117ffedcb84c397 Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Mon, 16 Dec 2024 22:41:41 +0100
Subject: [PATCH 14/34] Code Cleanup and fix some issues with concurrency

---
 substratevm/mx.substratevm/gdb_utils.py       |   2 +
 .../objectfile/debugentry/ClassEntry.java     |  83 +++-----
 .../debugentry/InterfaceClassEntry.java       |  10 +-
 .../objectfile/debugentry/MethodEntry.java    |  27 +--
 .../debugentry/StructureTypeEntry.java        |  10 +-
 .../debugentry/range/CallRange.java           |   7 +-
 .../debugentry/range/LeafRange.java           |   8 +-
 .../debugentry/range/PrimaryRange.java        |   4 +-
 .../objectfile/debugentry/range/Range.java    |  41 ++--
 .../debuginfo/DebugInfoProvider.java          |   6 +-
 .../objectfile/elf/dwarf/DwarfDebugInfo.java  |   2 -
 .../elf/dwarf/DwarfInfoSectionImpl.java       |  43 ++--
 .../pecoff/cv/CVTypeSectionBuilder.java       |  50 ++---
 .../svm/core/debug/BFDNameProvider.java       |  20 +-
 .../core/debug/SharedDebugInfoProvider.java   | 200 ++++++++++--------
 .../debug/SubstrateDebugInfoProvider.java     |   2 +-
 .../image/NativeImageDebugInfoProvider.java   |   1 -
 17 files changed, 249 insertions(+), 267 deletions(-)

diff --git a/substratevm/mx.substratevm/gdb_utils.py b/substratevm/mx.substratevm/gdb_utils.py
index 1499bf28678f..47d7d9c33af6 100644
--- a/substratevm/mx.substratevm/gdb_utils.py
+++ b/substratevm/mx.substratevm/gdb_utils.py
@@ -110,6 +110,8 @@ def check(self, text, skip_fails=True):
         for i in range(0, num_rexps):
             rexp = rexps[i]
             match = None
+            if skip_fails:
+                line_idx = 0
             while line_idx < num_lines and match is None:
                 line = lines[line_idx]
                 match = rexp.match(line)
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
index 75855ac056f7..c02eb1183bf5 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
@@ -26,9 +26,9 @@
 
 package com.oracle.objectfile.debugentry;
 
-import java.util.ArrayList;
-import java.util.HashSet;
+import java.util.Comparator;
 import java.util.List;
+import java.util.concurrent.ConcurrentSkipListSet;
 
 import com.oracle.objectfile.debugentry.range.Range;
 
@@ -51,23 +51,23 @@ public class ClassEntry extends StructureTypeEntry {
     /**
      * Details of methods located in this instance.
      */
-    private final List<MethodEntry> methods;
+    private final ConcurrentSkipListSet<MethodEntry> methods;
     /**
      * A list recording details of all normal compiled methods included in this class sorted by
      * ascending address range. Note that the associated address ranges are disjoint and contiguous.
      */
-    private final List<CompiledMethodEntry> compiledMethods;
+    private final ConcurrentSkipListSet<CompiledMethodEntry> compiledMethods;
 
     /**
      * A list of all files referenced from info associated with this class, including info detailing
      * inline method ranges.
      */
-    private final List<FileEntry> files;
+    private final ConcurrentSkipListSet<FileEntry> files;
     /**
      * A list of all directories referenced from info associated with this class, including info
      * detailing inline method ranges.
      */
-    private final List<DirEntry> dirs;
+    private final ConcurrentSkipListSet<DirEntry> dirs;
 
     public ClassEntry(String typeName, int size, long classOffset, long typeSignature,
                     long compressedTypeSignature, long layoutTypeSignature,
@@ -76,57 +76,36 @@ public ClassEntry(String typeName, int size, long classOffset, long typeSignatur
         this.superClass = superClass;
         this.fileEntry = fileEntry;
         this.loader = loader;
-        this.methods = new ArrayList<>();
-        this.compiledMethods = new ArrayList<>();
-        this.files = new ArrayList<>();
-        this.dirs = new ArrayList<>();
-    }
-
-    public void collectFilesAndDirs() {
-        HashSet<FileEntry> fileSet = new HashSet<>();
-
-        // add containing file
-        fileSet.add(fileEntry);
-
-        // add all files of declared methods
-        fileSet.addAll(methods.stream().map(MethodEntry::getFileEntry).toList());
+        this.methods = new ConcurrentSkipListSet<>(Comparator.comparingInt(MethodEntry::getModifiers).thenComparingInt(MethodEntry::getLine).thenComparing(MethodEntry::getSymbolName));
+        this.compiledMethods = new ConcurrentSkipListSet<>(Comparator.comparing(CompiledMethodEntry::primary));
+        this.files = new ConcurrentSkipListSet<>(Comparator.comparing(FileEntry::fileName).thenComparing(file -> file.dirEntry().path()));
+        this.dirs = new ConcurrentSkipListSet<>(Comparator.comparing(DirEntry::path));
 
-        // add all files required for compilations
-        // no need to add the primary range as this is the same as the corresponding method
-        // declaration file
-        fileSet.addAll(compiledMethods.parallelStream()
-                        .flatMap(CompiledMethodEntry::topDownRangeStream)
-                        .map(Range::getFileEntry)
-                        .toList());
-
-        // add all files of fields
-        fileSet.addAll(getFields().stream().map(FieldEntry::getFileEntry).toList());
-
-        // fill file list from set
-        fileSet.forEach(this::addFile);
+        addFile(fileEntry);
     }
 
-    public void addMethod(MethodEntry methodEntry) {
-        if (!methods.contains(methodEntry)) {
-            methods.add(methodEntry);
-        }
+    private void addFile(FileEntry fileEntry) {
+        files.add(fileEntry);
+        dirs.add(fileEntry.dirEntry());
     }
 
-    public void addCompiledMethod(CompiledMethodEntry compiledMethodEntry) {
-        compiledMethods.add(compiledMethodEntry);
+    @Override
+    public void addField(FieldEntry field) {
+        addFile(field.getFileEntry());
+        super.addField(field);
     }
 
-    public void addFile(FileEntry fileEntry) {
-        if (fileEntry != null && !fileEntry.fileName().isEmpty() && !files.contains(fileEntry)) {
-            files.add(fileEntry);
-            addDir(fileEntry.dirEntry());
-        }
+    public void addMethod(MethodEntry methodEntry) {
+        addFile(methodEntry.getFileEntry());
+        methods.add(methodEntry);
     }
 
-    public void addDir(DirEntry dirEntry) {
-        if (dirEntry != null && !dirEntry.getPathString().isEmpty() && !dirs.contains(dirEntry)) {
-            dirs.add(dirEntry);
+    public void addCompiledMethod(CompiledMethodEntry compiledMethodEntry) {
+        addFile(compiledMethodEntry.primary().getFileEntry());
+        for (Range range : compiledMethodEntry.topDownRangeStream().toList()) {
+            addFile(range.getFileEntry());
         }
+        compiledMethods.add(compiledMethodEntry);
     }
 
     @Override
@@ -171,7 +150,7 @@ public int getFileIdx(FileEntry file) {
         if (file == null || files.isEmpty() || !files.contains(file)) {
             return 0;
         }
-        return files.indexOf(file) + 1;
+        return (int) (files.stream().takeWhile(f -> f != file).count() + 1);
     }
 
     public DirEntry getDirEntry(FileEntry file) {
@@ -187,10 +166,10 @@ public int getDirIdx(FileEntry file) {
     }
 
     public int getDirIdx(DirEntry dir) {
-        if (dir == null || dir.getPathString().isEmpty() || dirs.isEmpty()) {
+        if (dir == null || dir.getPathString().isEmpty() || dirs.isEmpty() || !dirs.contains(dir)) {
             return 0;
         }
-        return dirs.indexOf(dir) + 1;
+        return (int) (dirs.stream().takeWhile(d -> d != dir).count() + 1);
     }
 
     public String getLoaderId() {
@@ -226,7 +205,7 @@ public List<MethodEntry> getMethods() {
      */
     public long lowpc() {
         assert hasCompiledMethods();
-        return compiledMethods.stream().map(CompiledMethodEntry::primary).mapToLong(Range::getLo).min().orElse(0);
+        return compiledMethods.first().primary().getLo();
     }
 
     /**
@@ -238,7 +217,7 @@ public long lowpc() {
      */
     public long hipc() {
         assert hasCompiledMethods();
-        return compiledMethods.stream().map(CompiledMethodEntry::primary).mapToLong(Range::getHi).max().orElse(0);
+        return compiledMethods.last().primary().getHi();
     }
 
     /**
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/InterfaceClassEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/InterfaceClassEntry.java
index 88616655d9f4..9e1b16050d3c 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/InterfaceClassEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/InterfaceClassEntry.java
@@ -26,18 +26,18 @@
 
 package com.oracle.objectfile.debugentry;
 
-import java.util.ArrayList;
-import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
+import java.util.concurrent.ConcurrentSkipListSet;
 
 public class InterfaceClassEntry extends ClassEntry {
-    private final List<ClassEntry> implementors;
+    private final ConcurrentSkipListSet<ClassEntry> implementors;
 
     public InterfaceClassEntry(String typeName, int size, long classOffset, long typeSignature,
                     long compressedTypeSignature, long layoutTypeSignature,
                     ClassEntry superClass, FileEntry fileEntry, LoaderEntry loader) {
         super(typeName, size, classOffset, typeSignature, compressedTypeSignature, layoutTypeSignature, superClass, fileEntry, loader);
-        this.implementors = new ArrayList<>();
+        this.implementors = new ConcurrentSkipListSet<>(Comparator.comparingLong(ClassEntry::getTypeSignature));
     }
 
     @Override
@@ -55,7 +55,7 @@ public void addImplementor(ClassEntry classEntry) {
     }
 
     public List<ClassEntry> getImplementors() {
-        return Collections.unmodifiableList(implementors);
+        return List.copyOf(implementors);
     }
 
     @Override
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 c7966a7dabec..eb72efe97baf 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
@@ -27,14 +27,15 @@
 package com.oracle.objectfile.debugentry;
 
 import java.util.List;
+import java.util.SortedSet;
 
 public class MethodEntry extends MemberEntry {
     private final LocalEntry thisParam;
-    private final List<LocalEntry> paramInfos;
+    private final SortedSet<LocalEntry> paramInfos;
     private final int lastParamSlot;
     // local vars are accumulated as they are referenced sorted by slot, then name, then
     // type name. we don't currently deal handle references to locals with no slot.
-    private final List<LocalEntry> locals;
+    private final SortedSet<LocalEntry> locals;
     private final boolean isDeopt;
     private boolean isInRange;
     private boolean isInlined;
@@ -45,9 +46,9 @@ public class MethodEntry extends MemberEntry {
 
     @SuppressWarnings("this-escape")
     public MethodEntry(FileEntry fileEntry, int line, String methodName, StructureTypeEntry ownerType,
-                    TypeEntry valueType, int modifiers, List<LocalEntry> paramInfos, LocalEntry thisParam,
+                    TypeEntry valueType, int modifiers, SortedSet<LocalEntry> paramInfos, LocalEntry thisParam,
                     String symbolName, boolean isDeopt, boolean isOverride, boolean isConstructor, int vtableOffset,
-                    int lastParamSlot, List<LocalEntry> locals) {
+                    int lastParamSlot, SortedSet<LocalEntry> locals) {
         super(fileEntry, line, methodName, ownerType, valueType, modifiers);
         this.paramInfos = paramInfos;
         this.thisParam = thisParam;
@@ -74,24 +75,10 @@ public ClassEntry getOwnerType() {
         return (ClassEntry) ownerType;
     }
 
-    public int getParamCount() {
-        return paramInfos.size();
-    }
-
-    public TypeEntry getParamType(int idx) {
-        assert idx < paramInfos.size();
-        return paramInfos.get(idx).type();
-    }
-
     public List<TypeEntry> getParamTypes() {
         return paramInfos.stream().map(LocalEntry::type).toList();
     }
 
-    public LocalEntry getParam(int i) {
-        assert i >= 0 && i < paramInfos.size() : "bad param index";
-        return paramInfos.get(i);
-    }
-
     public List<LocalEntry> getParams() {
         return List.copyOf(paramInfos);
     }
@@ -125,9 +112,7 @@ public LocalEntry lookupLocalEntry(String name, int slot, TypeEntry type, int li
 
             LocalEntry local = new LocalEntry(name, type, slot, line);
             synchronized (locals) {
-                if (!locals.contains(local)) {
-                    locals.add(local);
-                }
+                locals.add(local);
             }
             return local;
         }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java
index c0456db2ce96..d4d102f9d933 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java
@@ -27,9 +27,9 @@
 package com.oracle.objectfile.debugentry;
 
 import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
+import java.util.concurrent.ConcurrentSkipListSet;
 
 /**
  * An intermediate type that provides behaviour for managing fields. This unifies code for handling
@@ -39,7 +39,7 @@ public abstract class StructureTypeEntry extends TypeEntry {
     /**
      * Details of fields located in this instance.
      */
-    private final List<FieldEntry> fields;
+    private final ConcurrentSkipListSet<FieldEntry> fields;
 
     /**
      * The type signature of this types' layout. The layout of a type contains debug info of fields
@@ -53,7 +53,7 @@ public StructureTypeEntry(String typeName, int size, long classOffset, long type
         super(typeName, size, classOffset, typeSignature, compressedTypeSignature);
         this.layoutTypeSignature = layoutTypeSignature;
 
-        this.fields = new ArrayList<>();
+        this.fields = new ConcurrentSkipListSet<>(Comparator.comparingInt(FieldEntry::getOffset));
     }
 
     public long getLayoutTypeSignature() {
@@ -65,7 +65,7 @@ public void addField(FieldEntry field) {
     }
 
     public List<FieldEntry> getFields() {
-        return Collections.unmodifiableList(fields);
+        return List.copyOf(fields);
     }
 
     String memberModifiers(int modifiers) {
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/CallRange.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/CallRange.java
index 264335d3e08f..d3d96138a2db 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/CallRange.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/CallRange.java
@@ -28,10 +28,13 @@
 
 import java.util.Comparator;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.stream.Stream;
 
+import com.oracle.objectfile.debugentry.LocalEntry;
+import com.oracle.objectfile.debugentry.LocalValueEntry;
 import com.oracle.objectfile.debugentry.MethodEntry;
 
 public class CallRange extends Range {
@@ -42,8 +45,8 @@ public class CallRange extends Range {
      */
     private final Set<Range> callees = new TreeSet<>(Comparator.comparing(Range::getLoOffset));
 
-    protected CallRange(PrimaryRange primary, MethodEntry methodEntry, int lo, int hi, int line, CallRange caller, int depth) {
-        super(primary, methodEntry, lo, hi, line, caller, depth);
+    protected CallRange(PrimaryRange primary, MethodEntry methodEntry, Map<LocalEntry, LocalValueEntry> localInfoList, int lo, int hi, int line, CallRange caller, int depth) {
+        super(primary, methodEntry, localInfoList, lo, hi, line, caller, depth);
     }
 
     @Override
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/LeafRange.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/LeafRange.java
index 980d32461342..6bc3cd3a76ba 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/LeafRange.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/LeafRange.java
@@ -26,11 +26,15 @@
 
 package com.oracle.objectfile.debugentry.range;
 
+import java.util.Map;
+
+import com.oracle.objectfile.debugentry.LocalEntry;
+import com.oracle.objectfile.debugentry.LocalValueEntry;
 import com.oracle.objectfile.debugentry.MethodEntry;
 
 public class LeafRange extends Range {
-    protected LeafRange(PrimaryRange primary, MethodEntry methodEntry, int lo, int hi, int line, CallRange caller, int depth) {
-        super(primary, methodEntry, lo, hi, line, caller, depth);
+    protected LeafRange(PrimaryRange primary, MethodEntry methodEntry, Map<LocalEntry, LocalValueEntry> localInfoList, int lo, int hi, int line, CallRange caller, int depth) {
+        super(primary, methodEntry, localInfoList, lo, hi, line, caller, depth);
     }
 
     @Override
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/PrimaryRange.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/PrimaryRange.java
index 082b22cdf0bb..f718c9343094 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/PrimaryRange.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/PrimaryRange.java
@@ -1,12 +1,14 @@
 package com.oracle.objectfile.debugentry.range;
 
+import java.util.HashMap;
+
 import com.oracle.objectfile.debugentry.MethodEntry;
 
 public class PrimaryRange extends CallRange {
     private final long codeOffset;
 
     protected PrimaryRange(MethodEntry methodEntry, int lo, int hi, int line, long codeOffset) {
-        super(null, methodEntry, lo, hi, line, null, -1);
+        super(null, methodEntry, new HashMap<>(), lo, hi, line, null, -1);
         this.codeOffset = codeOffset;
     }
 
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java
index ff84b327d034..c574b2d2fd1d 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java
@@ -27,6 +27,7 @@
 package com.oracle.objectfile.debugentry.range;
 
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -77,7 +78,7 @@
  * parameter values for separate sub-extents of an inline called method whose full extent is
  * represented by the parent call range at level N.
  */
-public abstract class Range {
+public abstract class Range implements Comparable<Range> {
     private final MethodEntry methodEntry;
     private final int loOffset;
     private int hiOffset;
@@ -95,7 +96,7 @@ public abstract class Range {
      * Values for the associated method's local and parameter variables that are available or,
      * alternatively, marked as invalid in this range.
      */
-    private final Map<LocalEntry, LocalValueEntry> localValueInfos = new HashMap<>();
+    private final Map<LocalEntry, LocalValueEntry> localValueInfos;
 
     /**
      * Create a primary range representing the root of the subrange tree for a top level compiled
@@ -124,27 +125,23 @@ public static PrimaryRange createPrimary(MethodEntry methodEntry, int lo, int hi
      * @param isLeaf true if this is a leaf range with no subranges
      * @return a new subrange to be linked into the range tree below the primary range.
      */
-    public static Range createSubrange(PrimaryRange primary, MethodEntry methodEntry, int lo, int hi, int line, CallRange caller, boolean isLeaf, boolean isInitialRange) {
+    public static Range createSubrange(PrimaryRange primary, MethodEntry methodEntry, Map<LocalEntry, LocalValueEntry> localValueInfos, int lo, int hi, int line, CallRange caller, boolean isLeaf) {
         assert caller != null;
         if (caller != primary) {
             methodEntry.setInlined();
         }
         Range callee;
         if (isLeaf) {
-            callee = new LeafRange(primary, methodEntry, lo, hi, line, caller, caller.getDepth() + 1);
+            callee = new LeafRange(primary, methodEntry, localValueInfos, lo, hi, line, caller, caller.getDepth() + 1);
         } else {
-            callee = new CallRange(primary, methodEntry, lo, hi, line, caller, caller.getDepth() + 1);
+            callee = new CallRange(primary, methodEntry, localValueInfos, lo, hi, line, caller, caller.getDepth() + 1);
         }
 
         caller.addCallee(callee);
         return callee;
     }
 
-    public static Range createSubrange(PrimaryRange primary, MethodEntry methodEntry, int lo, int hi, int line, CallRange caller, boolean isLeaf) {
-        return createSubrange(primary, methodEntry, lo, hi, line, caller, isLeaf, false);
-    }
-
-    protected Range(PrimaryRange primary, MethodEntry methodEntry, int loOffset, int hiOffset, int line, CallRange caller, int depth) {
+    protected Range(PrimaryRange primary, MethodEntry methodEntry, Map<LocalEntry, LocalValueEntry> localValueInfos, int loOffset, int hiOffset, int line, CallRange caller, int depth) {
         assert methodEntry != null;
         this.primary = primary;
         this.methodEntry = methodEntry;
@@ -153,13 +150,14 @@ protected Range(PrimaryRange primary, MethodEntry methodEntry, int loOffset, int
         this.line = line;
         this.caller = caller;
         this.depth = depth;
+        this.localValueInfos = localValueInfos;
     }
 
     public Range split(int stackDecrement, int frameSize, int preExtendFrameSize) {
         // this should be for an initial range extending beyond the stack decrement
         assert loOffset == 0 && loOffset < stackDecrement && stackDecrement < hiOffset : "invalid split request";
-        Range newRange = Range.createSubrange(this.primary, this.methodEntry, stackDecrement, this.hiOffset, this.line, this.caller, this.isLeaf());
-        this.hiOffset = stackDecrement;
+
+        Map<LocalEntry, LocalValueEntry> localValueInfos = new HashMap<>();
 
         for (var localInfo : this.localValueInfos.entrySet()) {
             if (localInfo.getValue() instanceof StackValueEntry stackValue) {
@@ -169,12 +167,15 @@ public Range split(int stackDecrement, int frameSize, int preExtendFrameSize) {
                 // difference between the caller SP and the pre-extend callee SP
                 // because of a stacked return address.
                 int adjustment = frameSize - preExtendFrameSize;
-                newRange.localValueInfos.put(localInfo.getKey(), new StackValueEntry(stackValue.stackSlot() + adjustment));
+                localValueInfos.put(localInfo.getKey(), new StackValueEntry(stackValue.stackSlot() + adjustment));
             } else {
-                newRange.localValueInfos.put(localInfo.getKey(), localInfo.getValue());
+                localValueInfos.put(localInfo.getKey(), localInfo.getValue());
             }
         }
 
+        Range newRange = Range.createSubrange(this.primary, this.methodEntry, localValueInfos, stackDecrement, this.hiOffset, this.line, this.caller, this.isLeaf());
+        this.hiOffset = stackDecrement;
+
         return newRange;
     }
 
@@ -274,6 +275,14 @@ public String toString() {
         return String.format("Range(lo=0x%05x hi=0x%05x %s %s:%d)", getLo(), getHi(), getFullMethodNameWithParams(), methodEntry.getFullFileName(), line);
     }
 
+    @Override
+    public int compareTo(Range other) {
+        return Comparator.comparingLong(Range::getLo)
+                        .thenComparingLong(Range::getHi)
+                        .thenComparingInt(Range::getLine)
+                        .compare(this, other);
+    }
+
     public String getFileName() {
         return methodEntry.getFileName();
     }
@@ -336,10 +345,6 @@ public Range getCaller() {
         return caller;
     }
 
-    public void setLocalValueInfo(Map<LocalEntry, LocalValueEntry> localValueInfos) {
-        this.localValueInfos.putAll(localValueInfos);
-    }
-
     public LocalValueEntry lookupValue(LocalEntry local) {
         return localValueInfos.getOrDefault(local, null);
     }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java
index 0d51564b9426..759130eb08d6 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java
@@ -26,7 +26,7 @@
 
 package com.oracle.objectfile.debuginfo;
 
-import java.util.List;
+import java.util.SortedSet;
 
 import com.oracle.objectfile.debugentry.CompiledMethodEntry;
 import com.oracle.objectfile.debugentry.StringTable;
@@ -71,9 +71,9 @@ public interface DebugInfoProvider {
 
     StringTable getStringTable();
 
-    List<TypeEntry> typeEntries();
+    SortedSet<TypeEntry> typeEntries();
 
-    List<CompiledMethodEntry> compiledMethodEntries();
+    SortedSet<CompiledMethodEntry> compiledMethodEntries();
 
     String cachePath();
 
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java
index 2dba77ae051d..1d9ad2da5c9e 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java
@@ -36,10 +36,8 @@
 import com.oracle.objectfile.debugentry.StructureTypeEntry;
 import com.oracle.objectfile.debugentry.TypeEntry;
 import com.oracle.objectfile.debugentry.range.Range;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider;
 import com.oracle.objectfile.elf.ELFMachine;
 import com.oracle.objectfile.elf.dwarf.constants.DwarfLanguage;
-import jdk.graal.compiler.debug.DebugContext;
 
 /**
  * A class that models the debug info in an organization that facilitates generation of the required
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 ee66de24317b..1aad8aea6d1f 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
@@ -414,15 +414,14 @@ private int writeInstanceClasses(DebugContext context, byte[] buffer, int pos) {
     private int writeTypeUnits(DebugContext context, StructureTypeEntry typeEntry, byte[] buffer, int p) {
         int pos = p;
 
-        if (typeEntry.isForeign()) {
-            ForeignTypeEntry foreignTypeEntry = (ForeignTypeEntry) typeEntry;
+        if (typeEntry instanceof ForeignTypeEntry foreignTypeEntry) {
             pos = writeForeignLayoutTypeUnit(context, foreignTypeEntry, buffer, pos);
             pos = writeForeignTypeUnit(context, foreignTypeEntry, buffer, pos);
         } else {
-            if (typeEntry.isArray()) {
-                pos = writeArrayLayoutTypeUnit(context, (ArrayTypeEntry) typeEntry, buffer, pos);
-            } else if (typeEntry.isInterface()) {
-                pos = writeInterfaceLayoutTypeUnit(context, (InterfaceClassEntry) typeEntry, buffer, pos);
+            if (typeEntry instanceof ArrayTypeEntry arrayTypeEntry) {
+                pos = writeArrayLayoutTypeUnit(context, arrayTypeEntry, buffer, pos);
+            } else if (typeEntry instanceof InterfaceClassEntry interfaceClassEntry) {
+                pos = writeInterfaceLayoutTypeUnit(context, interfaceClassEntry, buffer, pos);
             } else {
                 assert typeEntry instanceof ClassEntry;
                 pos = writeClassLayoutTypeUnit(context, (ClassEntry) typeEntry, buffer, pos);
@@ -464,10 +463,10 @@ private int writePointerTypeUnit(DebugContext context, StructureTypeEntry typeEn
         int pos = p;
 
         String loaderId = "";
-        if (typeEntry.isArray()) {
-            loaderId = ((ArrayTypeEntry) typeEntry).getLoaderId();
-        } else if (typeEntry.isClass()) {
-            loaderId = ((ClassEntry) typeEntry).getLoaderId();
+        if (typeEntry instanceof ArrayTypeEntry arrayTypeEntry) {
+            loaderId = arrayTypeEntry.getLoaderId();
+        } else if (typeEntry instanceof ClassEntry classEntry) {
+            loaderId = classEntry.getLoaderId();
         }
         int lengthPos = pos;
         long typeSignature = typeEntry.getTypeSignature();
@@ -517,10 +516,10 @@ private int writePointerTypeUnitForCompressed(DebugContext context, StructureTyp
 
         /* if the class has a loader then embed the children in a namespace */
         String loaderId = "";
-        if (typeEntry.isArray()) {
-            loaderId = ((ArrayTypeEntry) typeEntry).getLoaderId();
-        } else if (typeEntry.isClass()) {
-            loaderId = ((ClassEntry) typeEntry).getLoaderId();
+        if (typeEntry instanceof ArrayTypeEntry arrayTypeEntry) {
+            loaderId = arrayTypeEntry.getLoaderId();
+        } else if (typeEntry instanceof ClassEntry classEntry) {
+            loaderId = classEntry.getLoaderId();
         }
         if (!loaderId.isEmpty()) {
             pos = writeNameSpace(context, loaderId, buffer, pos);
@@ -1080,8 +1079,7 @@ private int writeSkeletonMethodParameterDeclarations(DebugContext context, Metho
             LocalEntry paramInfo = method.getThisParam();
             pos = writeSkeletonMethodParameterDeclaration(context, paramInfo, true, buffer, pos);
         }
-        for (int i = 0; i < method.getParamCount(); i++) {
-            LocalEntry paramInfo = method.getParam(i);
+        for (LocalEntry paramInfo : method.getParams()) {
             pos = writeSkeletonMethodParameterDeclaration(context, paramInfo, false, buffer, pos);
         }
         return pos;
@@ -1200,17 +1198,13 @@ private int writeMethodDeclaration(DebugContext context, ClassEntry classEntry,
 
     private int writeMethodParameterDeclarations(DebugContext context, ClassEntry classEntry, MethodEntry method, int fileIdx, int level, byte[] buffer, int p) {
         int pos = p;
-        int refAddr;
         if (!Modifier.isStatic(method.getModifiers())) {
-            refAddr = pos;
             LocalEntry paramInfo = method.getThisParam();
-            setMethodLocalIndex(classEntry, method, paramInfo, refAddr);
+            setMethodLocalIndex(classEntry, method, paramInfo, pos);
             pos = writeMethodParameterDeclaration(context, paramInfo, fileIdx, true, level, buffer, pos);
         }
-        for (int i = 0; i < method.getParamCount(); i++) {
-            refAddr = pos;
-            LocalEntry paramInfo = method.getParam(i);
-            setMethodLocalIndex(classEntry, method, paramInfo, refAddr);
+        for (LocalEntry paramInfo : method.getParams()) {
+            setMethodLocalIndex(classEntry, method, paramInfo, pos);
             pos = writeMethodParameterDeclaration(context, paramInfo, fileIdx, false, level, buffer, pos);
         }
         return pos;
@@ -1795,8 +1789,7 @@ private int writeMethodParameterLocations(DebugContext context, ClassEntry class
             int refAddr = getMethodLocalIndex(classEntry, methodEntry, thisParamInfo);
             pos = writeMethodLocalLocation(context, range, thisParamInfo, refAddr, depth, true, buffer, pos);
         }
-        for (int i = 0; i < methodEntry.getParamCount(); i++) {
-            LocalEntry paramInfo = methodEntry.getParam(i);
+        for (LocalEntry paramInfo : methodEntry.getParams()) {
             int refAddr = getMethodLocalIndex(classEntry, methodEntry, paramInfo);
             pos = writeMethodLocalLocation(context, range, paramInfo, refAddr, depth, true, buffer, pos);
         }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVTypeSectionBuilder.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVTypeSectionBuilder.java
index 8409679b6ac8..c3dd8bcd07af 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVTypeSectionBuilder.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVTypeSectionBuilder.java
@@ -25,28 +25,6 @@
  */
 package com.oracle.objectfile.pecoff.cv;
 
-import com.oracle.objectfile.debugentry.ArrayTypeEntry;
-import com.oracle.objectfile.debugentry.ClassEntry;
-import com.oracle.objectfile.debugentry.CompiledMethodEntry;
-import com.oracle.objectfile.debugentry.FieldEntry;
-import com.oracle.objectfile.debugentry.HeaderTypeEntry;
-import com.oracle.objectfile.debugentry.MemberEntry;
-import com.oracle.objectfile.debugentry.MethodEntry;
-import com.oracle.objectfile.debugentry.StructureTypeEntry;
-import com.oracle.objectfile.debugentry.TypeEntry;
-
-import jdk.graal.compiler.debug.GraalError;
-
-import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Deque;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-
 import static com.oracle.objectfile.pecoff.cv.CVTypeConstants.ADDRESS_BITS;
 import static com.oracle.objectfile.pecoff.cv.CVTypeConstants.CV_CALL_NEAR_C;
 import static com.oracle.objectfile.pecoff.cv.CVTypeConstants.FUNC_IS_CONSTRUCTOR;
@@ -82,8 +60,30 @@
 import static com.oracle.objectfile.pecoff.cv.CVTypeConstants.T_UINT4;
 import static com.oracle.objectfile.pecoff.cv.CVTypeConstants.T_VOID;
 import static com.oracle.objectfile.pecoff.cv.CVTypeConstants.T_WCHAR;
-import static com.oracle.objectfile.pecoff.cv.CVTypeRecord.CVClassRecord.ATTR_FORWARD_REF;
 import static com.oracle.objectfile.pecoff.cv.CVTypeRecord.CV_TYPE_RECORD_MAX_SIZE;
+import static com.oracle.objectfile.pecoff.cv.CVTypeRecord.CVClassRecord.ATTR_FORWARD_REF;
+
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import com.oracle.objectfile.debugentry.ArrayTypeEntry;
+import com.oracle.objectfile.debugentry.ClassEntry;
+import com.oracle.objectfile.debugentry.CompiledMethodEntry;
+import com.oracle.objectfile.debugentry.FieldEntry;
+import com.oracle.objectfile.debugentry.HeaderTypeEntry;
+import com.oracle.objectfile.debugentry.MemberEntry;
+import com.oracle.objectfile.debugentry.MethodEntry;
+import com.oracle.objectfile.debugentry.StructureTypeEntry;
+import com.oracle.objectfile.debugentry.TypeEntry;
+
+import jdk.graal.compiler.debug.GraalError;
 
 class CVTypeSectionBuilder {
 
@@ -412,8 +412,8 @@ CVTypeRecord.CVTypeMFunctionRecord buildMemberFunction(ClassEntry classEntry, Me
         mFunctionRecord.setFuncAttr(attr);
         mFunctionRecord.setReturnType(types.getIndexForPointerOrPrimitive(methodEntry.getValueType()));
         CVTypeRecord.CVTypeArglistRecord argListType = new CVTypeRecord.CVTypeArglistRecord();
-        for (int i = 0; i < methodEntry.getParamCount(); i++) {
-            argListType.add(types.getIndexForPointerOrPrimitive(methodEntry.getParamType(i)));
+        for (TypeEntry paramType : methodEntry.getParamTypes()) {
+            argListType.add(types.getIndexForPointerOrPrimitive(paramType));
         }
         argListType = addTypeRecord(argListType);
         mFunctionRecord.setArgList(argListType);
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/BFDNameProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/BFDNameProvider.java
index 17f1fa137adb..36cae914abd4 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/BFDNameProvider.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/BFDNameProvider.java
@@ -61,7 +61,6 @@ public class BFDNameProvider implements UniqueShortNameProvider {
 
     public BFDNameProvider(List<ClassLoader> ignore) {
         this.ignoredLoaders = ignore;
-        this.wordBaseType = null;
     }
 
     @Override
@@ -118,8 +117,6 @@ private static ClassLoader getClassLoader(ResolvedJavaType type) {
 
     private final List<ClassLoader> ignoredLoaders;
 
-    private ResolvedJavaType wordBaseType;
-
     private static final String BUILTIN_CLASSLOADER_NAME = "jdk.internal.loader.BuiltinClassLoader";
 
     private static boolean isBuiltinLoader(ClassLoader loader) {
@@ -408,17 +405,6 @@ public String bfdMangle(Member m) {
         return new BFDMangler(this).mangle(m);
     }
 
-    /**
-     * Make the provider aware of the word base type. This is needed because the same provider is
-     * used for AOT debug info generation and runtime debug info generation. For AOT debug info we
-     * need the HostedType and for Runtime debug info we need the SubstrateType.
-     *
-     * @param wordBaseType the current wordBaseType.
-     */
-    public void setWordBaseType(ResolvedJavaType wordBaseType) {
-        this.wordBaseType = wordBaseType;
-    }
-
     private static class BFDMangler {
 
         /*
@@ -906,6 +892,10 @@ private void recordName(LookupName name) {
      * @return true if the type needs to be encoded using pointer prefix P otherwise false.
      */
     private boolean needsPointerPrefix(ResolvedJavaType type) {
-        return wordBaseType == null || !wordBaseType.isAssignableFrom(type);
+        if (type instanceof SharedType sharedType) {
+            /* Word types have the kind Object, but a primitive storageKind. */
+            return sharedType.getJavaKind() == JavaKind.Object && sharedType.getStorageKind() == sharedType.getJavaKind();
+        }
+        return false;
     }
 }
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 dde6a1e99503..9f2710fdd879 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
@@ -5,10 +5,13 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.SortedSet;
+import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Stream;
 
@@ -198,10 +201,6 @@ public SharedDebugInfoProvider(DebugContext debug, RuntimeConfiguration runtimeC
         this.wordBaseType = (SharedType) metaAccess.lookupJavaType(WordBase.class);
         this.voidType = (SharedType) metaAccess.lookupJavaType(Void.class);
 
-        if (UniqueShortNameProvider.singleton() instanceof BFDNameProvider bfdNameProvider) {
-            bfdNameProvider.setWordBaseType(this.wordBaseType);
-        }
-
         // Get some information on heap layout and object/object header layout
         this.useHeapBase = ReferenceAccess.singleton().haveCompressedReferences() && ReferenceAccess.singleton().getCompressEncoding().hasBase();
         this.compressionShift = ReferenceAccess.singleton().getCompressionShift();
@@ -263,14 +262,24 @@ public StringTable getStringTable() {
     }
 
     @Override
-    public List<TypeEntry> typeEntries() {
+    public SortedSet<TypeEntry> typeEntries() {
+        SortedSet<TypeEntry> typeEntries = new TreeSet<>(Comparator.comparingLong(TypeEntry::getTypeSignature));
+
+        typeEntries.add(headerTypeEntry);
+        typeEntries.addAll(typeIndex.values());
+
         // we will always create the headerTypeEntry, as it will be used as superclass for object
-        return Stream.concat(Stream.of(headerTypeEntry), typeIndex.values().stream()).toList();
+        return typeEntries;
     }
 
     @Override
-    public List<CompiledMethodEntry> compiledMethodEntries() {
-        return compiledMethodIndex.values().stream().toList();
+    public SortedSet<CompiledMethodEntry> compiledMethodEntries() {
+        SortedSet<CompiledMethodEntry> compiledMethodEntries = new TreeSet<>(
+                        Comparator.comparing(CompiledMethodEntry::primary).thenComparingLong(compiledMethodEntry -> compiledMethodEntry.classEntry().getTypeSignature()));
+
+        compiledMethodEntries.addAll(compiledMethodIndex.values());
+
+        return compiledMethodEntries;
     }
 
     /* Functions for installing debug info into the index maps. */
@@ -283,17 +292,18 @@ public void installDebugInfo() {
         Stream<Object> dataStream = debug.isLogEnabled() ? dataInfo() : dataInfo().parallel();
 
         try (DebugContext.Scope s = debug.scope("DebugInfoProvider")) {
-            // create and index an empty dir with index 0 for null paths.
+            // Create and index an empty dir with index 0 for null paths.
             lookupDirEntry(EMPTY_PATH);
 
-            // handle types, compilations and data
-            // code info needs to be handled first as it contains source file infos of compilations
-            // which are collected in the class entry
+            /*
+             * Handle types, compilations and data. Code info needs to be handled first as it
+             * contains source file infos of compilations which are collected in the class entry.
+             */
             codeStream.forEach(pair -> handleCodeInfo(pair.getLeft(), pair.getRight()));
             typeStream.forEach(this::handleTypeInfo);
             dataStream.forEach(this::handleDataInfo);
 
-            // create the header type
+            // Create the header type.
             handleTypeInfo(null);
         } catch (Throwable e) {
             throw debug.handle(e);
@@ -305,11 +315,6 @@ protected void handleDataInfo(Object data) {
 
     private void handleTypeInfo(SharedType type) {
         TypeEntry typeEntry = lookupTypeEntry(type);
-
-        // collect all files and directories referenced by each classEntry
-        if (typeEntry instanceof ClassEntry classEntry) {
-            classEntry.collectFilesAndDirs();
-        }
     }
 
     private void handleCodeInfo(SharedMethod method, CompilationResult compilation) {
@@ -335,67 +340,85 @@ static boolean debugCodeInfoUseSourceMappings() {
         return SubstrateOptions.DebugCodeInfoUseSourceMappings.getValue();
     }
 
-    @SuppressWarnings("try")
-    protected CompiledMethodEntry installCompilationInfo(MethodEntry methodEntry, SharedMethod method, CompilationResult compilation) {
-        try (DebugContext.Scope s = debug.scope("DebugInfoCompilation")) {
-            // Mark the method entry for the compilation.
-            methodEntry.setInRange();
+    protected CompiledMethodEntry createCompilationInfo(MethodEntry methodEntry, SharedMethod method, CompilationResult compilation) {
+        int primaryLine = methodEntry.getLine();
+        int frameSize = compilation.getTotalFrameSize();
+        List<FrameSizeChangeEntry> frameSizeChanges = getFrameSizeChanges(compilation);
+        ClassEntry ownerType = methodEntry.getOwnerType();
 
-            int primaryLine = methodEntry.getLine();
-            int frameSize = compilation.getTotalFrameSize();
-            List<FrameSizeChangeEntry> frameSizeChanges = getFrameSizeChanges(compilation);
-            ClassEntry ownerType = methodEntry.getOwnerType();
+        // Create a primary range that spans over the compilation.
+        // The primary range entry holds the code offset information for all its sub ranges.
+        PrimaryRange primaryRange = Range.createPrimary(methodEntry, 0, compilation.getTargetCodeSize(), primaryLine, getCodeOffset(method));
+        debug.log(DebugContext.INFO_LEVEL, "PrimaryRange %s.%s %s %s:%d [0x%x, 0x%x]", ownerType.getTypeName(), methodEntry.getMethodName(), primaryRange.getFileEntry().getPathName(),
+                        primaryRange.getFileName(), primaryLine, primaryRange.getLo(), primaryRange.getHi());
 
-            // Create a primary range that spans over the compilation.
-            // The primary range entry holds the code offset information for all its sub ranges.
-            PrimaryRange primaryRange = Range.createPrimary(methodEntry, 0, compilation.getTargetCodeSize(), primaryLine, getCodeOffset(method));
-            debug.log(DebugContext.INFO_LEVEL, "PrimaryRange %s.%s %s %s:%d [0x%x, 0x%x]", ownerType.getTypeName(), methodEntry.getMethodName(), primaryRange.getFileEntry().getPathName(),
-                            primaryRange.getFileName(), primaryLine, primaryRange.getLo(), primaryRange.getHi());
+        return new CompiledMethodEntry(primaryRange, frameSizeChanges, frameSize, ownerType);
+    }
 
-            CompiledMethodEntry compiledMethodEntry = new CompiledMethodEntry(primaryRange, frameSizeChanges, frameSize, ownerType);
-            if (compiledMethodIndex.putIfAbsent(compilation.getCompilationId(), compiledMethodEntry) == null) {
-                // If the compiled method entry was added, we still need to check the frame states
-                // for subranges.
+    protected void processCompilationInfo(MethodEntry methodEntry, SharedMethod method, CompilationResult compilation, CompiledMethodEntry compiledMethodEntry) {
+        // Mark the method entry for the compilation.
+        methodEntry.setInRange();
 
-                // Can we still provide locals if we have no file name?
-                if (methodEntry.getFileName().isEmpty()) {
-                    return compiledMethodEntry;
-                }
+        // If the compiled method entry was added, we still need to check the frame states
+        // for subranges.
 
-                // Restrict the frame state traversal based on options.
-                boolean omitInline = omitInline();
-                int maxDepth = debugCodeInfoMaxDepth();
-                boolean useSourceMappings = debugCodeInfoUseSourceMappings();
-                if (omitInline) {
-                    /* TopLevelVisitor will not go deeper than level 2 */
-                    maxDepth = 2;
-                }
+        // Can we still provide locals if we have no file name?
+        if (methodEntry.getFileName().isEmpty()) {
+            return;
+        }
 
-                // The root node is represented by the primary range.
-                // A call nodes in the frame tree will be stored as call ranges and leaf nodes as
-                // leaf ranges
-                final CompilationResultFrameTree.CallNode root = new CompilationResultFrameTree.Builder(debug, compilation.getTargetCodeSize(), maxDepth, useSourceMappings,
-                                true)
-                                .build(compilation);
-                if (root == null) {
-                    return compiledMethodEntry;
-                }
-                final List<Range> subRanges = new ArrayList<>();
-                // The top level visitor will only traverse the direct children of the primary
-                // range. All sub call ranges will be treated as leaf ranges.
-                final CompilationResultFrameTree.Visitor visitor = omitInline ? new TopLevelVisitor(subRanges, frameSize, primaryRange) : new MultiLevelVisitor(subRanges, frameSize, primaryRange);
-                // arguments passed by visitor to apply are
-                // NativeImageDebugLocationInfo caller location info
-                // CallNode nodeToEmbed parent call node to convert to entry code leaf
-                // NativeImageDebugLocationInfo leaf into which current leaf may be merged
-                root.visitChildren(visitor, primaryRange, null, null);
-                // try to add a location record for offset zero
-                updateInitialLocation(primaryRange, subRanges, compilation, method, methodEntry);
-
-                ownerType.addCompiledMethod(compiledMethodEntry);
-            }
+        // Restrict the frame state traversal based on options.
+        boolean omitInline = omitInline();
+        int maxDepth = debugCodeInfoMaxDepth();
+        boolean useSourceMappings = debugCodeInfoUseSourceMappings();
+        if (omitInline) {
+            /* TopLevelVisitor will not go deeper than level 2 */
+            maxDepth = 2;
+        }
+
+        // The root node is represented by the primary range.
+        // A call nodes in the frame tree will be stored as call ranges and leaf nodes as
+        // leaf ranges
+        final CompilationResultFrameTree.CallNode root = new CompilationResultFrameTree.Builder(debug, compilation.getTargetCodeSize(), maxDepth, useSourceMappings,
+                        true)
+                        .build(compilation);
+        if (root == null) {
+            return;
+        }
+        PrimaryRange primaryRange = compiledMethodEntry.primary();
+        int frameSize = compiledMethodEntry.frameSize();
+        final List<Range> subRanges = new ArrayList<>();
+        // The top level visitor will only traverse the direct children of the primary
+        // range. All sub call ranges will be treated as leaf ranges.
+        final CompilationResultFrameTree.Visitor visitor = omitInline ? new TopLevelVisitor(subRanges, frameSize, primaryRange) : new MultiLevelVisitor(subRanges, frameSize, primaryRange);
+        // arguments passed by visitor to apply are
+        // NativeImageDebugLocationInfo caller location info
+        // CallNode nodeToEmbed parent call node to convert to entry code leaf
+        // NativeImageDebugLocationInfo leaf into which current leaf may be merged
+        root.visitChildren(visitor, primaryRange, null, null);
+        // try to add a location record for offset zero
+        updateInitialLocation(primaryRange, subRanges, compilation, method, methodEntry);
+
+        methodEntry.getOwnerType().addCompiledMethod(compiledMethodEntry);
+    }
+
+    @SuppressWarnings("try")
+    protected CompiledMethodEntry installCompilationInfo(MethodEntry methodEntry, SharedMethod method, CompilationResult compilation) {
+        try (DebugContext.Scope s = debug.scope("DebugInfoCompilation")) {
+            debug.log(DebugContext.INFO_LEVEL, "Register compilation %s ", compilation.getName());
 
-            return compiledMethodEntry;
+            CompiledMethodEntry compiledMethodEntry = createCompilationInfo(methodEntry, method, compilation);
+            if (compiledMethodIndex.putIfAbsent(compilation.getCompilationId(), compiledMethodEntry) == null) {
+                // CompiledMethodEntry was added to the index, now we need to process the
+                // compilation.
+                debug.log(DebugContext.INFO_LEVEL, "Process compilation %s ", compilation.getName());
+                processCompilationInfo(methodEntry, method, compilation, compiledMethodEntry);
+                return compiledMethodEntry;
+            } else {
+                // The compilation entry was created in the meantime, so we return the one unique
+                // type.
+                return compiledMethodIndex.get(compilation.getCompilationId());
+            }
         } catch (Throwable e) {
             throw debug.handle(e);
         }
@@ -443,12 +466,12 @@ protected MethodEntry installMethodEntry(SharedMethod method) {
             // check the local variable table for parameters
             // if the params are not in the table, we create synthetic ones from the method
             // signature
-            List<LocalEntry> paramInfos = getParamEntries(method, line);
+            SortedSet<LocalEntry> paramInfos = getParamEntries(method, line);
             int lastParamSlot = paramInfos.isEmpty() ? -1 : paramInfos.getLast().slot();
             LocalEntry thisParam = Modifier.isStatic(modifiers) ? null : paramInfos.removeFirst();
 
             // look for locals in the methods local variable table
-            List<LocalEntry> locals = getLocalEntries(method, lastParamSlot);
+            SortedSet<LocalEntry> locals = getLocalEntries(method, lastParamSlot);
 
             String symbolName = getSymbolName(method);
             int vTableOffset = getVTableOffset(method);
@@ -475,8 +498,11 @@ private TypeEntry installTypeEntry(SharedType type) {
                 // TypeEntry was added to the type index, now we need to process the type.
                 debug.log(DebugContext.INFO_LEVEL, "Process type %s ", type.getName());
                 processTypeEntry(type, typeEntry);
+                return typeEntry;
+            } else {
+                // The type entry was created in the meantime, so we return the one unique type.
+                return typeIndex.get(type);
             }
-            return typeEntry;
         } catch (Throwable e) {
             throw debug.handle(e);
         }
@@ -533,8 +559,8 @@ public String getMethodName(SharedMethod method) {
         return name;
     }
 
-    public List<LocalEntry> getLocalEntries(SharedMethod method, int lastParamSlot) {
-        ArrayList<LocalEntry> localEntries = new ArrayList<>();
+    public SortedSet<LocalEntry> getLocalEntries(SharedMethod method, int lastParamSlot) {
+        SortedSet<LocalEntry> localEntries = new TreeSet<>(Comparator.comparingInt(LocalEntry::slot).thenComparing(LocalEntry::name).thenComparingInt(LocalEntry::line));
 
         LineNumberTable lnt = method.getLineNumberTable();
         LocalVariableTable lvt = method.getLocalVariableTable();
@@ -573,10 +599,10 @@ public List<LocalEntry> getLocalEntries(SharedMethod method, int lastParamSlot)
         return localEntries;
     }
 
-    public List<LocalEntry> getParamEntries(SharedMethod method, int line) {
+    public SortedSet<LocalEntry> getParamEntries(SharedMethod method, int line) {
         Signature signature = method.getSignature();
         int parameterCount = signature.getParameterCount(false);
-        List<LocalEntry> paramInfos = new ArrayList<>(parameterCount);
+        SortedSet<LocalEntry> paramInfos = new TreeSet<>(Comparator.comparingInt(LocalEntry::slot));
         LocalVariableTable table = method.getLocalVariableTable();
         int slot = 0;
         SharedType ownerType = (SharedType) method.getDeclaringClass();
@@ -604,8 +630,6 @@ public int getModifiers(SharedMethod method) {
 
     public String getSymbolName(SharedMethod method) {
         return UniqueShortNameProvider.singleton().uniqueShortName(null, method.getDeclaringClass(), method.getName(), method.getSignature(), method.isConstructor());
-        // return nameProvider.uniqueShortName(null, method.getDeclaringClass(), method.getName(),
-        // method.getSignature(), method.isConstructor());
     }
 
     public boolean isDeopt(SharedMethod method, String methodName) {
@@ -824,9 +848,9 @@ private void updateInitialLocation(PrimaryRange primary, List<Range> locationInf
         // create a synthetic location record including details of passed arguments
         ParamLocationProducer locProducer = new ParamLocationProducer(method);
         debug.log(DebugContext.DETAILED_LEVEL, "Add synthetic Location Info : %s (0, %d)", methodEntry.getMethodName(), firstLocationOffset - 1);
-        Range locationInfo = Range.createSubrange(primary, methodEntry, 0, firstLocationOffset, methodEntry.getLine(), primary, true, true);
 
-        locationInfo.setLocalValueInfo(initSyntheticInfoList(locProducer, methodEntry));
+        Map<LocalEntry, LocalValueEntry> localInfoList = initSyntheticInfoList(locProducer, methodEntry);
+        Range locationInfo = Range.createSubrange(primary, methodEntry, localInfoList, 0, firstLocationOffset, methodEntry.getLine(), primary, true);
 
         // if the prologue extends beyond the stack extend and uses the stack then the info
         // needs
@@ -1027,13 +1051,12 @@ public Range process(CompilationResultFrameTree.FrameNode node, CallRange caller
             LineNumberTable lineNumberTable = method.getLineNumberTable();
             int line = lineNumberTable == null ? -1 : lineNumberTable.getLineNumber(pos.getBCI());
 
-            Range locationInfo = Range.createSubrange(primary, methodEntry, node.getStartPos(), node.getEndPos() + 1, line, callerInfo, isLeaf);
+            Map<LocalEntry, LocalValueEntry> localValueInfos = initLocalInfoList(pos, methodEntry, frameSize);
+            Range locationInfo = Range.createSubrange(primary, methodEntry, localValueInfos, node.getStartPos(), node.getEndPos() + 1, line, callerInfo, isLeaf);
 
             debug.log(DebugContext.DETAILED_LEVEL, "Create %s Location Info : %s depth %d (%d, %d)", isLeaf ? "leaf" : "call", method.getName(), locationInfo.getDepth(), locationInfo.getLoOffset(),
                             locationInfo.getHiOffset() - 1);
 
-            locationInfo.setLocalValueInfo(initLocalInfoList(pos, methodEntry, frameSize));
-
             return locationInfo;
         }
     }
@@ -1257,13 +1280,12 @@ private Range createEmbeddedParentLocationInfo(PrimaryRange primary, Compilation
         LineNumberTable lineNumberTable = method.getLineNumberTable();
         int line = lineNumberTable == null ? -1 : lineNumberTable.getLineNumber(pos.getBCI());
 
-        Range locationInfo = Range.createSubrange(primary, methodEntry, startPos, endPos, line, callerLocation, true);
+        Map<LocalEntry, LocalValueEntry> localValueInfos = initLocalInfoList(pos, methodEntry, frameSize);
+        Range locationInfo = Range.createSubrange(primary, methodEntry, localValueInfos, startPos, endPos, line, callerLocation, true);
 
         debug.log(DebugContext.DETAILED_LEVEL, "Embed leaf Location Info : %s depth %d (%d, %d)", locationInfo.getMethodName(), locationInfo.getDepth(), locationInfo.getLoOffset(),
                         locationInfo.getHiOffset() - 1);
 
-        locationInfo.setLocalValueInfo(initLocalInfoList(pos, methodEntry, frameSize));
-
         return locationInfo;
     }
 
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
index bc6bc304d4e7..4f8c225eb203 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
@@ -133,7 +133,7 @@ private int getTypeSize(SharedType type) {
 
     @Override
     protected TypeEntry createTypeEntry(SharedType type) {
-        String typeName = type.toJavaName(); // stringTable.uniqueDebugString(idType.toJavaName());
+        String typeName = type.toJavaName();
         LoaderEntry loaderEntry = lookupLoaderEntry(type);
         int size = getTypeSize(type);
         long classOffset = -1;
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 648c58c7e345..776c807679f8 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
@@ -388,7 +388,6 @@ private void processMethods(HostedType type, ClassEntry classEntry) {
             debug.log("typename %s adding %s method %s %s(%s)%n",
                             classEntry.getTypeName(), methodEntry.getModifiersString(), methodEntry.getValueType().getTypeName(), methodEntry.getMethodName(),
                             formatParams(methodEntry.getThisParam(), methodEntry.getParams()));
-            classEntry.addMethod(methodEntry);
         }
     }
 

From 7b479387cec7f517ba5c745ecdf5f3196bcf31ca Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Tue, 17 Dec 2024 10:42:39 +0100
Subject: [PATCH 15/34] Add, update and fix copyright headers, fix style issues

---
 substratevm/mx.substratevm/suite.py           |  1 -
 .../objectfile/debugentry/ArrayTypeEntry.java |  2 +-
 .../objectfile/debugentry/ClassEntry.java     |  8 ++--
 .../debugentry/CompiledMethodEntry.java       |  2 +-
 .../debugentry/ConstantValueEntry.java        | 25 ++++++++++++
 .../objectfile/debugentry/DebugInfoBase.java  |  5 ---
 .../objectfile/debugentry/DirEntry.java       |  2 +-
 .../objectfile/debugentry/EnumClassEntry.java |  2 +-
 .../objectfile/debugentry/FieldEntry.java     |  4 +-
 .../objectfile/debugentry/FileEntry.java      |  2 +-
 .../debugentry/ForeignFloatTypeEntry.java     |  3 +-
 .../debugentry/ForeignIntegerTypeEntry.java   |  3 +-
 .../debugentry/ForeignPointerTypeEntry.java   |  3 +-
 .../debugentry/ForeignStructTypeEntry.java    |  3 +-
 .../debugentry/ForeignTypeEntry.java          |  2 +-
 .../debugentry/ForeignWordTypeEntry.java      |  3 +-
 .../debugentry/FrameSizeChangeEntry.java      | 25 ++++++++++++
 .../debugentry/HeaderTypeEntry.java           |  2 +-
 .../debugentry/InterfaceClassEntry.java       |  2 +-
 .../objectfile/debugentry/LoaderEntry.java    |  2 +-
 .../objectfile/debugentry/LocalEntry.java     | 25 ++++++++++++
 .../debugentry/LocalValueEntry.java           | 25 ++++++++++++
 .../objectfile/debugentry/MemberEntry.java    |  2 +-
 .../objectfile/debugentry/MethodEntry.java    |  2 +-
 .../debugentry/PrimitiveTypeEntry.java        |  4 +-
 .../debugentry/RegisterValueEntry.java        | 25 ++++++++++++
 .../debugentry/StackValueEntry.java           | 25 ++++++++++++
 .../objectfile/debugentry/StringEntry.java    |  2 +-
 .../objectfile/debugentry/StringTable.java    |  2 +-
 .../debugentry/StructureTypeEntry.java        |  2 +-
 .../objectfile/debugentry/TypeEntry.java      |  2 +-
 .../debugentry/range/CallRange.java           |  2 +-
 .../debugentry/range/LeafRange.java           |  2 +-
 .../debugentry/range/PrimaryRange.java        | 26 +++++++++++++
 .../objectfile/debugentry/range/Range.java    | 16 ++++----
 .../elf/dwarf/DwarfARangesSectionImpl.java    |  2 +-
 .../elf/dwarf/DwarfAbbrevSectionImpl.java     |  2 +-
 .../elf/dwarf/DwarfFrameSectionImpl.java      |  2 +-
 .../dwarf/DwarfFrameSectionImplAArch64.java   |  6 +--
 .../dwarf/DwarfFrameSectionImplX86_64.java    |  6 +--
 .../elf/dwarf/DwarfLineSectionImpl.java       |  4 +-
 .../elf/dwarf/DwarfLocSectionImpl.java        |  2 +-
 .../elf/dwarf/DwarfRangesSectionImpl.java     |  2 +-
 .../elf/dwarf/DwarfSectionImpl.java           |  8 ----
 .../elf/dwarf/DwarfStrSectionImpl.java        |  2 +-
 .../elf/dwarf/constants/DwarfAttribute.java   |  2 +-
 .../elf/dwarf/constants/DwarfForm.java        | 20 ++++------
 .../elf/dwarf/constants/DwarfSectionName.java |  2 +-
 .../elf/dwarf/constants/DwarfTag.java         |  2 +-
 .../elf/dwarf/constants/DwarfVersion.java     |  2 +-
 .../objectfile/pecoff/cv/CVDebugInfo.java     |  6 +--
 .../pecoff/cv/CVLineRecordBuilder.java        |  8 ++--
 .../pecoff/cv/CVSymbolSubsectionBuilder.java  | 14 +++----
 .../com/oracle/svm/core/SubstrateOptions.java |  6 +--
 .../svm/core/debug/GDBJITInterface.java       | 25 ++++++++++++
 .../core/debug/SharedDebugInfoProvider.java   | 25 ++++++++++++
 .../core/debug/SubstrateDebugInfoFeature.java | 39 ++++++++++++++++---
 .../debug/SubstrateDebugInfoInstaller.java    | 27 ++++++++++++-
 .../debug/SubstrateDebugInfoProvider.java     |  1 +
 .../oracle/svm/graal/SubstrateGraalUtils.java | 17 ++++----
 .../image/NativeImageDebugInfoProvider.java   |  2 +-
 .../hosted/image/sources/SourceManager.java   |  2 +-
 62 files changed, 376 insertions(+), 123 deletions(-)

diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py
index 56dc42b6dd8c..5458092822bd 100644
--- a/substratevm/mx.substratevm/suite.py
+++ b/substratevm/mx.substratevm/suite.py
@@ -681,7 +681,6 @@
             "subDir": "src",
             "sourceDirs": ["src"],
             "dependencies": [
-                "com.oracle.objectfile",
                 "com.oracle.graal.reachability",
                 "com.oracle.svm.core.graal.amd64",
             ],
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ArrayTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ArrayTypeEntry.java
index 6c5e27ba2041..cde997d753b7 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ArrayTypeEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ArrayTypeEntry.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
index c02eb1183bf5..1eaaccb2fc0a 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
@@ -84,9 +84,9 @@ public ClassEntry(String typeName, int size, long classOffset, long typeSignatur
         addFile(fileEntry);
     }
 
-    private void addFile(FileEntry fileEntry) {
-        files.add(fileEntry);
-        dirs.add(fileEntry.dirEntry());
+    private void addFile(FileEntry addFileEntry) {
+        files.add(addFileEntry);
+        dirs.add(addFileEntry.dirEntry());
     }
 
     @Override
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/CompiledMethodEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/CompiledMethodEntry.java
index e567435e4830..dc0daaeb4f4f 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/CompiledMethodEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/CompiledMethodEntry.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ConstantValueEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ConstantValueEntry.java
index edec954db932..8388f3369dca 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ConstantValueEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ConstantValueEntry.java
@@ -1,3 +1,28 @@
+/*
+ * Copyright (c) 2024, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
 package com.oracle.objectfile.debugentry;
 
 import jdk.vm.ci.meta.JavaConstant;
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java
index 5358a8c0336d..a6829911219a 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java
@@ -150,11 +150,6 @@ public abstract class DebugInfoBase {
      */
     private String cachePath;
 
-    /**
-     * The offset of the first byte beyond the end of the Java compiled code address range.
-     */
-    private int compiledCodeMax;
-
     /**
      * The type entry for java.lang.Class.
      */
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DirEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DirEntry.java
index 3a0023d55fab..b39b8dc8ac2a 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DirEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DirEntry.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/EnumClassEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/EnumClassEntry.java
index 0e8e82c430e3..ea4d757bd986 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/EnumClassEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/EnumClassEntry.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FieldEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FieldEntry.java
index 24f725b5cdfa..d23b4e61d63d 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FieldEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FieldEntry.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
@@ -33,7 +33,7 @@ public class FieldEntry extends MemberEntry {
     private final boolean isEmbedded;
 
     public FieldEntry(FileEntry fileEntry, String fieldName, StructureTypeEntry ownerType,
-                      TypeEntry valueType, int size, int offset, boolean isEmbedded, int modifiers) {
+                    TypeEntry valueType, int size, int offset, boolean isEmbedded, int modifiers) {
         super(fileEntry, fieldName, ownerType, valueType, modifiers);
         this.size = size;
         this.offset = offset;
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FileEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FileEntry.java
index f8d9a7f4b5e7..296f0ea30646 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FileEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FileEntry.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignFloatTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignFloatTypeEntry.java
index 8b6a198751b9..af251b8949b4 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignFloatTypeEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignFloatTypeEntry.java
@@ -1,6 +1,5 @@
 /*
- * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved.
+ * Copyright (c) 2024, 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
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignIntegerTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignIntegerTypeEntry.java
index 20e8b6868f9c..129390f7a7d4 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignIntegerTypeEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignIntegerTypeEntry.java
@@ -1,6 +1,5 @@
 /*
- * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved.
+ * Copyright (c) 2024, 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
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignPointerTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignPointerTypeEntry.java
index 0b8f83d73268..2f80d24f87a3 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignPointerTypeEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignPointerTypeEntry.java
@@ -1,6 +1,5 @@
 /*
- * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved.
+ * Copyright (c) 2024, 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
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignStructTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignStructTypeEntry.java
index e14f62141c0b..afbe344f5ce3 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignStructTypeEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignStructTypeEntry.java
@@ -1,6 +1,5 @@
 /*
- * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved.
+ * Copyright (c) 2024, 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
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignTypeEntry.java
index e95a2a7b5760..9426554fbf1c 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignTypeEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignTypeEntry.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignWordTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignWordTypeEntry.java
index 3039c24508e2..f0b31bb58ada 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignWordTypeEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignWordTypeEntry.java
@@ -1,6 +1,5 @@
 /*
- * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved.
+ * Copyright (c) 2024, 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
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FrameSizeChangeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FrameSizeChangeEntry.java
index 6b0b98cab0ba..b4c6d15e00cd 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FrameSizeChangeEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FrameSizeChangeEntry.java
@@ -1,3 +1,28 @@
+/*
+ * Copyright (c) 2024, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
 package com.oracle.objectfile.debugentry;
 
 import com.oracle.objectfile.debuginfo.DebugInfoProvider.FrameSizeChangeType;
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/HeaderTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/HeaderTypeEntry.java
index 0ec06e4aa799..c1e9a3908e5b 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/HeaderTypeEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/HeaderTypeEntry.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/InterfaceClassEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/InterfaceClassEntry.java
index 9e1b16050d3c..0f1851c4dbab 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/InterfaceClassEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/InterfaceClassEntry.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LoaderEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LoaderEntry.java
index 1ab62ddc6d94..ed4fbe480081 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LoaderEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LoaderEntry.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalEntry.java
index a97eaf26d21a..7a355d7b074f 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalEntry.java
@@ -1,3 +1,28 @@
+/*
+ * Copyright (c) 2024, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
 package com.oracle.objectfile.debugentry;
 
 public record LocalEntry(String name, TypeEntry type, int slot, int line) {
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalValueEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalValueEntry.java
index 4daf7802999c..a676ba2018db 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalValueEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalValueEntry.java
@@ -1,3 +1,28 @@
+/*
+ * Copyright (c) 2024, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
 package com.oracle.objectfile.debugentry;
 
 public interface LocalValueEntry {
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MemberEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MemberEntry.java
index 223544569f67..827aa48f1623 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MemberEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MemberEntry.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
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 eb72efe97baf..8b2965a056a6 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
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PrimitiveTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PrimitiveTypeEntry.java
index 9631fab6bfca..814971edf53e 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PrimitiveTypeEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PrimitiveTypeEntry.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
@@ -33,7 +33,7 @@ public class PrimitiveTypeEntry extends TypeEntry {
     private final JavaKind kind;
 
     public PrimitiveTypeEntry(String typeName, int size, long classOffset, long typeSignature,
-                              JavaKind kind) {
+                    JavaKind kind) {
         super(typeName, size, classOffset, typeSignature, typeSignature);
         this.kind = kind;
     }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/RegisterValueEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/RegisterValueEntry.java
index e0fdc98780f9..501a0a64cefa 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/RegisterValueEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/RegisterValueEntry.java
@@ -1,3 +1,28 @@
+/*
+ * Copyright (c) 2024, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
 package com.oracle.objectfile.debugentry;
 
 public record RegisterValueEntry(int regIndex) implements LocalValueEntry {
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StackValueEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StackValueEntry.java
index e7ae72bab599..ac79be0943fe 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StackValueEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StackValueEntry.java
@@ -1,3 +1,28 @@
+/*
+ * Copyright (c) 2024, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
 package com.oracle.objectfile.debugentry;
 
 public record StackValueEntry(int stackSlot) implements LocalValueEntry {
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringEntry.java
index 340634db123c..86e64e0069f8 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringEntry.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringTable.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringTable.java
index df040db89197..a2b202363dd2 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringTable.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringTable.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java
index d4d102f9d933..ac7080408d57 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/TypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/TypeEntry.java
index 0c8c5a84bcd2..decd81f326d2 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/TypeEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/TypeEntry.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/CallRange.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/CallRange.java
index d3d96138a2db..02e8b2df1d98 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/CallRange.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/CallRange.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2022, 2022, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/LeafRange.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/LeafRange.java
index 6bc3cd3a76ba..173a3f0cbac2 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/LeafRange.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/LeafRange.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2022, 2022, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/PrimaryRange.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/PrimaryRange.java
index f718c9343094..b675ca6160e1 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/PrimaryRange.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/PrimaryRange.java
@@ -1,3 +1,29 @@
+/*
+ * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2022, 2022, Red Hat Inc. 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
 package com.oracle.objectfile.debugentry.range;
 
 import java.util.HashMap;
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java
index c574b2d2fd1d..bf79275b6ef9 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
@@ -157,9 +157,9 @@ public Range split(int stackDecrement, int frameSize, int preExtendFrameSize) {
         // this should be for an initial range extending beyond the stack decrement
         assert loOffset == 0 && loOffset < stackDecrement && stackDecrement < hiOffset : "invalid split request";
 
-        Map<LocalEntry, LocalValueEntry> localValueInfos = new HashMap<>();
+        Map<LocalEntry, LocalValueEntry> splitLocalValueInfos = new HashMap<>();
 
-        for (var localInfo : this.localValueInfos.entrySet()) {
+        for (var localInfo : localValueInfos.entrySet()) {
             if (localInfo.getValue() instanceof StackValueEntry stackValue) {
                 // need to redefine the value for this param using a stack slot value
                 // that allows for the stack being extended by framesize. however we
@@ -167,16 +167,16 @@ public Range split(int stackDecrement, int frameSize, int preExtendFrameSize) {
                 // difference between the caller SP and the pre-extend callee SP
                 // because of a stacked return address.
                 int adjustment = frameSize - preExtendFrameSize;
-                localValueInfos.put(localInfo.getKey(), new StackValueEntry(stackValue.stackSlot() + adjustment));
+                splitLocalValueInfos.put(localInfo.getKey(), new StackValueEntry(stackValue.stackSlot() + adjustment));
             } else {
-                localValueInfos.put(localInfo.getKey(), localInfo.getValue());
+                splitLocalValueInfos.put(localInfo.getKey(), localInfo.getValue());
             }
         }
 
-        Range newRange = Range.createSubrange(this.primary, this.methodEntry, localValueInfos, stackDecrement, this.hiOffset, this.line, this.caller, this.isLeaf());
-        this.hiOffset = stackDecrement;
+        Range splitRange = Range.createSubrange(primary, methodEntry, splitLocalValueInfos, stackDecrement, hiOffset, line, caller, isLeaf());
+        hiOffset = stackDecrement;
 
-        return newRange;
+        return splitRange;
     }
 
     public boolean contains(Range other) {
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfARangesSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfARangesSectionImpl.java
index 62c0e6bb5944..ec3a149e0a20 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfARangesSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfARangesSectionImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java
index dc75eb1fa6ff..55ade32fe36e 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImpl.java
index 5f26a84e4150..be6b7ecc4732 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplAArch64.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplAArch64.java
index 192c59177ebf..51fc2b6b475a 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplAArch64.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplAArch64.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
@@ -26,10 +26,10 @@
 
 package com.oracle.objectfile.elf.dwarf;
 
-import com.oracle.objectfile.debugentry.FrameSizeChangeEntry;
-
 import java.util.List;
 
+import com.oracle.objectfile.debugentry.FrameSizeChangeEntry;
+
 /**
  * AArch64-specific section generator for debug_frame section that knows details of AArch64
  * registers and frame layout.
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplX86_64.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplX86_64.java
index 45621ae2e72b..16025f9f0899 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplX86_64.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplX86_64.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
@@ -26,10 +26,10 @@
 
 package com.oracle.objectfile.elf.dwarf;
 
-import com.oracle.objectfile.debugentry.FrameSizeChangeEntry;
-
 import java.util.List;
 
+import com.oracle.objectfile.debugentry.FrameSizeChangeEntry;
+
 /**
  * x86_64-specific section generator for debug_frame section that knows details of x86_64 registers
  * and frame layout.
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 34d365183ffa..57cf2993771a 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
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
@@ -125,7 +125,7 @@ private static int headerSize() {
         return LN_HEADER_SIZE;
     }
 
-    private int computeDirTableSize(ClassEntry classEntry) {
+    private static int computeDirTableSize(ClassEntry classEntry) {
         /*
          * Table contains a sequence of 'nul'-terminated UTF8 dir name bytes followed by an extra
          * 'nul'.
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLocSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLocSectionImpl.java
index f34785e52799..cfa45ab326d3 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLocSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLocSectionImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2022, 2022, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfRangesSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfRangesSectionImpl.java
index 9aeb0fa2918f..2709733098de 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfRangesSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfRangesSectionImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java
index ecc81cd22e2a..1c3348720b65 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java
@@ -28,7 +28,6 @@
 
 import java.nio.ByteOrder;
 import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
 import java.util.Map;
 import java.util.Set;
 import java.util.stream.Stream;
@@ -61,7 +60,6 @@
 import com.oracle.objectfile.elf.dwarf.constants.DwarfVersion;
 
 import jdk.graal.compiler.debug.DebugContext;
-import jdk.vm.ci.meta.ResolvedJavaType;
 
 /**
  * A class from which all DWARF debug sections inherit providing common behaviours.
@@ -100,8 +98,6 @@ public int get() {
     protected boolean debug = false;
     protected long debugAddress = 0;
 
-    private final ArrayList<Byte> contentBytes = new ArrayList<>();
-
     /**
      * The name of this section.
      */
@@ -830,10 +826,6 @@ protected String uniqueDebugString(String str) {
         return dwarfSections.uniqueDebugString(str);
     }
 
-    protected TypeEntry lookupType(ResolvedJavaType type) {
-        return null; // dwarfSections.lookupTypeEntry(type, null, null);
-    }
-
     protected ClassEntry lookupObjectClass() {
         return dwarfSections.lookupObjectClass();
     }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfStrSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfStrSectionImpl.java
index 3272cef5f88c..5515d12f1563 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfStrSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfStrSectionImpl.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfAttribute.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfAttribute.java
index 050b9e9c726d..7441dfb12be0 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfAttribute.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfAttribute.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfForm.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfForm.java
index 1855bb16850e..fcc1dcb01cc9 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfForm.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfForm.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
@@ -34,20 +34,14 @@ public enum DwarfForm {
     DW_FORM_addr(0x1),
     DW_FORM_data2(0x05),
     DW_FORM_data4(0x6),
-    @SuppressWarnings("unused")
-    DW_FORM_data8(0x7),
-    @SuppressWarnings("unused")
-    DW_FORM_string(0x8),
-    @SuppressWarnings("unused")
-    DW_FORM_block1(0x0a),
+    @SuppressWarnings("unused") DW_FORM_data8(0x7),
+    @SuppressWarnings("unused") DW_FORM_string(0x8),
+    @SuppressWarnings("unused") DW_FORM_block1(0x0a),
     DW_FORM_ref_addr(0x10),
-    @SuppressWarnings("unused")
-    DW_FORM_ref1(0x11),
-    @SuppressWarnings("unused")
-    DW_FORM_ref2(0x12),
+    @SuppressWarnings("unused") DW_FORM_ref1(0x11),
+    @SuppressWarnings("unused") DW_FORM_ref2(0x12),
     DW_FORM_ref4(0x13),
-    @SuppressWarnings("unused")
-    DW_FORM_ref8(0x14),
+    @SuppressWarnings("unused") DW_FORM_ref8(0x14),
     DW_FORM_sec_offset(0x17),
     DW_FORM_data1(0x0b),
     DW_FORM_flag(0xc),
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfSectionName.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfSectionName.java
index 9d91ebf24437..9e81df746eec 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfSectionName.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfSectionName.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfTag.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfTag.java
index 77527e493900..50ce270354b4 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfTag.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfTag.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfVersion.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfVersion.java
index de98c7e83fb2..970c748638ff 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfVersion.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfVersion.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVDebugInfo.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVDebugInfo.java
index d7a76f6f6902..66b9df7c5e22 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVDebugInfo.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVDebugInfo.java
@@ -26,14 +26,14 @@
 
 package com.oracle.objectfile.pecoff.cv;
 
+import java.nio.ByteOrder;
+
 import com.oracle.objectfile.debugentry.DebugInfoBase;
-import com.oracle.objectfile.debuginfo.DebugInfoProvider;
 import com.oracle.objectfile.pecoff.PECoffMachine;
+
 import jdk.graal.compiler.debug.DebugContext;
 import jdk.graal.compiler.debug.GraalError;
 
-import java.nio.ByteOrder;
-
 /**
  * CVDebugInfo is a container class for all the CodeView sections to be emitted in the object file.
  * Currently, those are.debug$S (CVSymbolSectionImpl) and .debug$T (CVTypeSectionImpl).
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVLineRecordBuilder.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVLineRecordBuilder.java
index 47be6e836924..e80c0730a478 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVLineRecordBuilder.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVLineRecordBuilder.java
@@ -26,12 +26,12 @@
 
 package com.oracle.objectfile.pecoff.cv;
 
-import com.oracle.objectfile.debugentry.FileEntry;
+import java.util.Iterator;
+
 import com.oracle.objectfile.debugentry.CompiledMethodEntry;
+import com.oracle.objectfile.debugentry.FileEntry;
 import com.oracle.objectfile.debugentry.range.Range;
 
-import java.util.Iterator;
-
 /*
  * In CV4, the line table consists of a series of file headers followed by line number entries.
  * If this is a different file, then update the length of the previous file header, write the
@@ -101,7 +101,7 @@ private void processRange(Range range) {
         }
 
         /* Add line record. */
-        int lineLoAddr = (int)(range.getLo() - compiledEntry.primary().getLo());
+        int lineLoAddr = (int) (range.getLo() - compiledEntry.primary().getLo());
         int line = Math.max(range.getLine(), 1);
         debug("  processRange:   addNewLine: 0x%05x-0x%05x %s", lineLoAddr, range.getHi() - compiledEntry.primary().getLo(), line);
         lineRecord.addNewLine(lineLoAddr, line);
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubsectionBuilder.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubsectionBuilder.java
index a1af78d28e61..9c8881d4c492 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubsectionBuilder.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubsectionBuilder.java
@@ -26,6 +26,11 @@
 
 package com.oracle.objectfile.pecoff.cv;
 
+import static com.oracle.objectfile.pecoff.cv.CVConstants.CV_AMD64_R8;
+import static com.oracle.objectfile.pecoff.cv.CVTypeConstants.MAX_PRIMITIVE;
+
+import java.lang.reflect.Modifier;
+
 import com.oracle.objectfile.SectionName;
 import com.oracle.objectfile.debugentry.ClassEntry;
 import com.oracle.objectfile.debugentry.CompiledMethodEntry;
@@ -33,11 +38,6 @@
 import com.oracle.objectfile.debugentry.TypeEntry;
 import com.oracle.objectfile.debugentry.range.Range;
 
-import java.lang.reflect.Modifier;
-
-import static com.oracle.objectfile.pecoff.cv.CVConstants.CV_AMD64_R8;
-import static com.oracle.objectfile.pecoff.cv.CVTypeConstants.MAX_PRIMITIVE;
-
 final class CVSymbolSubsectionBuilder {
 
     private final CVDebugInfo cvDebugInfo;
@@ -129,8 +129,8 @@ private void buildFunction(CompiledMethodEntry compiledEntry) {
         /* S_PROC32 add function definition. */
         int functionTypeIndex = addTypeRecords(compiledEntry);
         byte funcFlags = 0;
-        CVSymbolSubrecord.CVSymbolGProc32Record proc32 = new CVSymbolSubrecord.CVSymbolGProc32Record(cvDebugInfo, externalName, debuggerName, 0, 0, 0, (int)(primaryRange.getHi() - primaryRange.getLo()), 0,
-                        0, functionTypeIndex, (short) 0, funcFlags);
+        CVSymbolSubrecord.CVSymbolGProc32Record proc32 = new CVSymbolSubrecord.CVSymbolGProc32Record(cvDebugInfo, externalName, debuggerName, 0, 0, 0,
+                        primaryRange.getHiOffset() - primaryRange.getLoOffset(), 0, 0, functionTypeIndex, (short) 0, funcFlags);
         addSymbolRecord(proc32);
 
         /* S_FRAMEPROC add frame definitions. */
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java
index bb28247ef133..aab7a65ca387 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java
@@ -45,6 +45,7 @@
 import org.graalvm.nativeimage.ImageSingletons;
 import org.graalvm.nativeimage.Platform;
 import org.graalvm.nativeimage.Platforms;
+import org.graalvm.nativeimage.ProcessProperties;
 
 import com.oracle.svm.core.c.libc.LibCBase;
 import com.oracle.svm.core.c.libc.MuslLibC;
@@ -76,7 +77,6 @@
 import jdk.graal.compiler.options.OptionValues;
 import jdk.graal.compiler.phases.common.DeadCodeEliminationPhase;
 import jdk.internal.misc.Unsafe;
-import org.graalvm.nativeimage.ProcessProperties;
 
 public class SubstrateOptions {
 
@@ -479,7 +479,6 @@ public static void setImageLayerCreateEnabledHandler(OptionEnabledHandler<Boolea
     @Option(help = "Directory where Java source-files will be placed for the debugger")//
     public static final RuntimeOptionKey<String> RuntimeSourceDestDir = new RuntimeOptionKey<>(null, RelevantForCompilationIsolates);
 
-
     public static Path getRuntimeSourceDestDir() {
         String sourceDestDir = RuntimeSourceDestDir.getValue();
         if (sourceDestDir != null) {
@@ -488,8 +487,9 @@ public static Path getRuntimeSourceDestDir() {
         return Paths.get(ProcessProperties.getExecutableName()).getParent().resolve("sources");
     }
 
+    // TODO change to false
     @Option(help = "Provide debuginfo for runtime-compiled code.")//
-    public static final HostedOptionKey<Boolean> RuntimeDebugInfo = new HostedOptionKey<>(true);  // TODO: change to false
+    public static final HostedOptionKey<Boolean> RuntimeDebugInfo = new HostedOptionKey<>(true);
 
     @Option(help = "Search path for C libraries passed to the linker (list of comma-separated directories)", stability = OptionStability.STABLE)//
     @BundleMember(role = BundleMember.Role.Input)//
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterface.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterface.java
index 33d7d5346f93..1825d2cda99a 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterface.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterface.java
@@ -1,3 +1,28 @@
+/*
+ * Copyright (c) 2024, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
 package com.oracle.svm.core.debug;
 
 import java.util.Collections;
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 9f2710fdd879..cf48e3b85d75 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
@@ -1,3 +1,28 @@
+/*
+ * Copyright (c) 2024, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
 package com.oracle.svm.core.debug;
 
 import java.lang.reflect.Modifier;
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoFeature.java
index caff4a263d4e..9f3776006ce1 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoFeature.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoFeature.java
@@ -1,16 +1,42 @@
+/*
+ * Copyright (c) 2024, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
 package com.oracle.svm.core.debug;
 
+import java.util.List;
+
+import org.graalvm.nativeimage.ImageSingletons;
+import org.graalvm.nativeimage.Platform;
+import org.graalvm.nativeimage.hosted.Feature;
+
 import com.oracle.svm.core.SubstrateOptions;
 import com.oracle.svm.core.code.InstalledCodeObserverSupport;
 import com.oracle.svm.core.code.InstalledCodeObserverSupportFeature;
 import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
 import com.oracle.svm.core.feature.InternalFeature;
 import com.oracle.svm.core.graal.meta.RuntimeConfiguration;
-import org.graalvm.nativeimage.ImageSingletons;
-import org.graalvm.nativeimage.Platform;
-import org.graalvm.nativeimage.hosted.Feature;
-
-import java.util.List;
 
 @AutomaticallyRegisteredFeature
 public class SubstrateDebugInfoFeature implements InternalFeature {
@@ -26,7 +52,8 @@ public List<Class<? extends Feature>> getRequiredFeatures() {
 
     @Override
     public void registerCodeObserver(RuntimeConfiguration runtimeConfig) {
-        // This is called at image build-time -> the factory then creates a RuntimeDebugInfoProvider at runtime
+        // This is called at image build-time -> the factory then creates a RuntimeDebugInfoProvider
+        // at runtime
         ImageSingletons.lookup(InstalledCodeObserverSupport.class).addObserverFactory(new SubstrateDebugInfoInstaller.Factory(runtimeConfig.getProviders().getMetaAccess(), runtimeConfig));
         ImageSingletons.add(SubstrateDebugInfoInstaller.Accessor.class, new SubstrateDebugInfoInstaller.Accessor());
     }
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
index f4ecd692a155..ca8bd170f274 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
@@ -1,3 +1,28 @@
+/*
+ * Copyright (c) 2024, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
 package com.oracle.svm.core.debug;
 
 import java.io.IOException;
@@ -35,7 +60,7 @@
 import jdk.graal.compiler.debug.DebugContext;
 import jdk.vm.ci.meta.MetaAccessProvider;
 
-public class SubstrateDebugInfoInstaller implements InstalledCodeObserver {
+public final class SubstrateDebugInfoInstaller implements InstalledCodeObserver {
 
     private final SubstrateDebugInfoProvider substrateDebugInfoProvider;
     private final ObjectFile objectFile;
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
index 4f8c225eb203..2649ba4255d5 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
@@ -22,6 +22,7 @@
  * or visit www.oracle.com if you need additional information or have any
  * questions.
  */
+
 package com.oracle.svm.core.debug;
 
 import java.nio.file.FileSystems;
diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/SubstrateGraalUtils.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/SubstrateGraalUtils.java
index 76a37f0e25c1..53406ed70ef7 100644
--- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/SubstrateGraalUtils.java
+++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/SubstrateGraalUtils.java
@@ -31,15 +31,6 @@
 import java.util.EnumMap;
 import java.util.Map;
 
-import jdk.graal.compiler.phases.util.Providers;
-import com.oracle.svm.core.SubstrateOptions;
-import com.oracle.svm.core.log.Log;
-import com.oracle.svm.core.option.RuntimeOptionValues;
-import com.oracle.svm.graal.isolated.IsolatedGraalUtils;
-import com.oracle.svm.graal.meta.RuntimeCodeInstaller;
-import com.oracle.svm.graal.meta.SubstrateInstalledCodeImpl;
-import jdk.graal.compiler.printer.GraalDebugHandlersFactory;
-import jdk.vm.ci.code.InstalledCode;
 import org.graalvm.collections.EconomicMap;
 import org.graalvm.nativeimage.ImageSingletons;
 
@@ -48,15 +39,21 @@
 import com.oracle.graal.pointsto.util.GraalAccess;
 import com.oracle.svm.common.option.CommonOptionParser;
 import com.oracle.svm.core.CPUFeatureAccess;
+import com.oracle.svm.core.SubstrateOptions;
 import com.oracle.svm.core.SubstrateUtil;
 import com.oracle.svm.core.graal.code.SubstrateCompilationIdentifier;
 import com.oracle.svm.core.graal.code.SubstrateCompilationResult;
 import com.oracle.svm.core.graal.meta.RuntimeConfiguration;
+import com.oracle.svm.core.log.Log;
 import com.oracle.svm.core.meta.SharedMethod;
 import com.oracle.svm.core.meta.SubstrateObjectConstant;
 import com.oracle.svm.core.option.RuntimeOptionKey;
 import com.oracle.svm.core.option.RuntimeOptionParser;
+import com.oracle.svm.core.option.RuntimeOptionValues;
 import com.oracle.svm.core.util.VMError;
+import com.oracle.svm.graal.isolated.IsolatedGraalUtils;
+import com.oracle.svm.graal.meta.RuntimeCodeInstaller;
+import com.oracle.svm.graal.meta.SubstrateInstalledCodeImpl;
 import com.oracle.svm.graal.meta.SubstrateMethod;
 
 import jdk.graal.compiler.code.CompilationResult;
@@ -79,7 +76,9 @@
 import jdk.graal.compiler.phases.OptimisticOptimizations;
 import jdk.graal.compiler.phases.tiers.Suites;
 import jdk.graal.compiler.phases.util.Providers;
+import jdk.graal.compiler.printer.GraalDebugHandlersFactory;
 import jdk.vm.ci.code.Architecture;
+import jdk.vm.ci.code.InstalledCode;
 import jdk.vm.ci.meta.ConstantReflectionProvider;
 import jdk.vm.ci.meta.JavaConstant;
 
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 776c807679f8..0e9bb09b1adb 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
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/sources/SourceManager.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/sources/SourceManager.java
index af7ba1565312..df1299ebfa18 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/sources/SourceManager.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/sources/SourceManager.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
  * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *

From ae3b18e6af13be2efca9119215117d996ad35b93 Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Thu, 19 Dec 2024 11:56:15 +0100
Subject: [PATCH 16/34] Code cleanup and fix style issues

Create file and dir indices in class entries
Update GDB JIT compilation interface to only use a header file
---
 .../include/gdb_jit_compilation_interface.h   |  6 --
 substratevm/mx.substratevm/suite.py           | 27 +--------
 .../objectfile/debugentry/ClassEntry.java     | 38 +++++++++++--
 .../svm/core/debug/BFDNameProvider.java       |  4 +-
 .../svm/core/debug/GDBJITInterface.java       | 32 ++++++-----
 .../core/debug/SharedDebugInfoProvider.java   | 44 +++++----------
 .../core/debug/SubstrateDebugInfoFeature.java |  2 +-
 .../debug/SubstrateDebugInfoInstaller.java    | 32 +++++------
 .../debug/SubstrateDebugInfoProvider.java     | 20 +++----
 .../image/NativeImageDebugInfoProvider.java   | 56 +++++++++----------
 .../src/gdb_jit_compilation_interface.c       |  7 ---
 11 files changed, 122 insertions(+), 146 deletions(-)
 rename substratevm/{src/com.oracle.svm.native.debug => debug}/include/gdb_jit_compilation_interface.h (75%)
 delete mode 100644 substratevm/src/com.oracle.svm.native.debug/src/gdb_jit_compilation_interface.c

diff --git a/substratevm/src/com.oracle.svm.native.debug/include/gdb_jit_compilation_interface.h b/substratevm/debug/include/gdb_jit_compilation_interface.h
similarity index 75%
rename from substratevm/src/com.oracle.svm.native.debug/include/gdb_jit_compilation_interface.h
rename to substratevm/debug/include/gdb_jit_compilation_interface.h
index 57a1ff6cf04b..38de0e86d99c 100644
--- a/substratevm/src/com.oracle.svm.native.debug/include/gdb_jit_compilation_interface.h
+++ b/substratevm/debug/include/gdb_jit_compilation_interface.h
@@ -33,10 +33,4 @@ struct jit_descriptor
   struct jit_code_entry *first_entry;
 };
 
-/* GDB puts a breakpoint in this function.  */
-void __attribute__((noinline)) __jit_debug_register_code() { };
-
-struct jit_code_entry *register_jit_code(const char *addr, uint64_t size);
-void unregister_jit_code(struct jit_code_entry *const entry);
-
 #endif
diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py
index 5458092822bd..5a7d99b26182 100644
--- a/substratevm/mx.substratevm/suite.py
+++ b/substratevm/mx.substratevm/suite.py
@@ -982,28 +982,6 @@
             "jacoco" : "exclude",
         },
 
-        "com.oracle.svm.native.debug": {
-            "native": "static_lib",
-            "subDir": "src",
-            "os_arch": {
-                "solaris": {
-                    "<others>": {
-                        "ignore": "solaris is not supported",
-                    },
-                },
-                "windows": {
-                    "<others>": {
-                        "cflags": ["-W4", "-O2", "-Zi"],
-                    },
-                },
-                "<others>": {
-                    "<others>": {
-                        "cflags": ["-Wall", "-fPIC", "-O2", "-g", "-gdwarf-5"],
-                    },
-                },
-            },
-        },
-
         "svm-jvmfuncs-fallback-builder": {
             "class" : "SubstrateJvmFuncsFallbacksBuilder",
         },
@@ -2022,7 +2000,7 @@
                             "dependency:com.oracle.svm.native.libchelper/*",
                             "dependency:com.oracle.svm.native.jvm.posix/*",
                             "dependency:com.oracle.svm.native.libcontainer/*",
-                            "dependency:com.oracle.svm.native.debug/*",
+                            "file:debug/include",
                         ],
                     },
                 },
@@ -2031,11 +2009,10 @@
                         # on all other os's we don't want libc specific subdirectories
                         "include/": [
                             "dependency:com.oracle.svm.native.libchelper/include/*",
-                            "dependency:com.oracle.svm.native.debug/include/*",
+                            "file:debug/include/*",
                         ],
                         "<os>-<arch>/": [
                             "dependency:com.oracle.svm.native.libchelper/<os>-<arch>/default/*",
-                            "dependency:com.oracle.svm.native.debug/<os>-<arch>/default/*",
                             "dependency:com.oracle.svm.native.jvm.posix/<os>-<arch>/default/*",
                             "dependency:com.oracle.svm.native.darwin/*",
                             "dependency:com.oracle.svm.native.jvm.windows/*",
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
index 1eaaccb2fc0a..0e39b8a138b9 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
@@ -27,7 +27,9 @@
 package com.oracle.objectfile.debugentry;
 
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.ConcurrentSkipListSet;
 
 import com.oracle.objectfile.debugentry.range.Range;
@@ -63,11 +65,14 @@ public class ClassEntry extends StructureTypeEntry {
      * inline method ranges.
      */
     private final ConcurrentSkipListSet<FileEntry> files;
+    private final Map<FileEntry, Integer> indexedFiles = new HashMap<>();
+
     /**
      * A list of all directories referenced from info associated with this class, including info
      * detailing inline method ranges.
      */
     private final ConcurrentSkipListSet<DirEntry> dirs;
+    private final Map<DirEntry, Integer> indexedDirs = new HashMap<>();
 
     public ClassEntry(String typeName, int size, long classOffset, long typeSignature,
                     long compressedTypeSignature, long layoutTypeSignature,
@@ -85,8 +90,13 @@ public ClassEntry(String typeName, int size, long classOffset, long typeSignatur
     }
 
     private void addFile(FileEntry addFileEntry) {
-        files.add(addFileEntry);
-        dirs.add(addFileEntry.dirEntry());
+        if (addFileEntry != null && !addFileEntry.fileName().isEmpty()) {
+            files.add(addFileEntry);
+            DirEntry addDirEntry = addFileEntry.dirEntry();
+            if (addDirEntry != null && !addDirEntry.getPathString().isEmpty()) {
+                dirs.add(addDirEntry);
+            }
+        }
     }
 
     @Override
@@ -150,7 +160,17 @@ public int getFileIdx(FileEntry file) {
         if (file == null || files.isEmpty() || !files.contains(file)) {
             return 0;
         }
-        return (int) (files.stream().takeWhile(f -> f != file).count() + 1);
+
+        // Create a file index for all files in this class entry
+        if (indexedFiles.isEmpty()) {
+            int index = 1;
+            for (FileEntry f : getFiles()) {
+                indexedFiles.put(f, index);
+                index++;
+            }
+        }
+
+        return indexedFiles.get(file);
     }
 
     public DirEntry getDirEntry(FileEntry file) {
@@ -169,7 +189,17 @@ public int getDirIdx(DirEntry dir) {
         if (dir == null || dir.getPathString().isEmpty() || dirs.isEmpty() || !dirs.contains(dir)) {
             return 0;
         }
-        return (int) (dirs.stream().takeWhile(d -> d != dir).count() + 1);
+
+        // Create a dir index for all dirs in this class entry
+        if (indexedDirs.isEmpty()) {
+            int index = 1;
+            for (DirEntry d : getDirs()) {
+                indexedDirs.put(d, index);
+                index++;
+            }
+        }
+
+        return indexedDirs.get(dir);
     }
 
     public String getLoaderId() {
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/BFDNameProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/BFDNameProvider.java
index 36cae914abd4..9b0a2e95568a 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/BFDNameProvider.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/BFDNameProvider.java
@@ -732,7 +732,7 @@ private void mangleType(ResolvedJavaType type) {
             } else {
                 String loaderName = nameProvider.classLoaderNameAndId(type);
                 String className = type.toJavaName();
-                if (nameProvider.needsPointerPrefix(type)) {
+                if (needsPointerPrefix(type)) {
                     mangleClassPointer(loaderName, className);
                 } else {
                     mangleClassName(loaderName, className);
@@ -891,7 +891,7 @@ private void recordName(LookupName name) {
      * @param type The type to be checked.
      * @return true if the type needs to be encoded using pointer prefix P otherwise false.
      */
-    private boolean needsPointerPrefix(ResolvedJavaType type) {
+    private static boolean needsPointerPrefix(ResolvedJavaType type) {
         if (type instanceof SharedType sharedType) {
             /* Word types have the kind Object, but a primitive storageKind. */
             return sharedType.getJavaKind() == JavaKind.Object && sharedType.getStorageKind() == sharedType.getJavaKind();
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterface.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterface.java
index 1825d2cda99a..78e8cb319f73 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterface.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterface.java
@@ -27,20 +27,22 @@
 
 import java.util.Collections;
 import java.util.List;
+import java.util.function.BooleanSupplier;
 
-import org.graalvm.nativeimage.ImageSingletons;
+import org.graalvm.nativeimage.CurrentIsolate;
+import org.graalvm.nativeimage.IsolateThread;
 import org.graalvm.nativeimage.c.CContext;
 import org.graalvm.nativeimage.c.constant.CEnum;
 import org.graalvm.nativeimage.c.constant.CEnumValue;
-import org.graalvm.nativeimage.c.function.CFunction;
+import org.graalvm.nativeimage.c.function.CEntryPoint;
 import org.graalvm.nativeimage.c.struct.CField;
 import org.graalvm.nativeimage.c.struct.CStruct;
 import org.graalvm.nativeimage.c.type.CCharPointer;
 import org.graalvm.nativeimage.c.type.CUnsigned;
-import org.graalvm.nativeimage.impl.UnmanagedMemorySupport;
 import org.graalvm.word.PointerBase;
 import org.graalvm.word.WordFactory;
 
+import com.oracle.svm.core.NeverInline;
 import com.oracle.svm.core.SubstrateOptions;
 import com.oracle.svm.core.Uninterruptible;
 import com.oracle.svm.core.c.CGlobalData;
@@ -58,12 +60,14 @@ public boolean isInConfiguration() {
 
         @Override
         public List<String> getHeaderFiles() {
-            return Collections.singletonList(ProjectHeaderFile.resolve("com.oracle.svm.native.debug", "include/gdb_jit_compilation_interface.h"));
+            return Collections.singletonList(ProjectHeaderFile.resolve("", "include/gdb_jit_compilation_interface.h"));
         }
+    }
 
+    private static class IncludeForRuntimeDebugOnly implements BooleanSupplier {
         @Override
-        public List<String> getLibraries() {
-            return Collections.singletonList("debug");
+        public boolean getAsBoolean() {
+            return SubstrateOptions.RuntimeDebugInfo.getValue();
         }
     }
 
@@ -143,8 +147,10 @@ public interface JITDescriptor extends PointerBase {
         void setFirstEntry(JITCodeEntry jitCodeEntry);
     }
 
-    @CFunction(value = "__jit_debug_register_code", transition = CFunction.Transition.NO_TRANSITION)
-    private static native void jitDebugRegisterCode();
+    @NeverInline("Register JIT code stub for GDB.")
+    @CEntryPoint(name = "__jit_debug_register_code", include = IncludeForRuntimeDebugOnly.class, publishAs = CEntryPoint.Publish.SymbolOnly)
+    private static void jitDebugRegisterCode(@SuppressWarnings("unused") IsolateThread thread) {
+    }
 
     private static final CGlobalData<JITDescriptor> jitDebugDescriptor = CGlobalDataFactory.forSymbol("__jit_debug_descriptor");
 
@@ -166,10 +172,10 @@ public static void registerJITCode(CCharPointer addr, @CUnsigned long size, JITC
         jitDebugDescriptor.get().setActionFlag(JITActions.JIT_REGISTER.getCValue());
         jitDebugDescriptor.get().setFirstEntry(entry);
         jitDebugDescriptor.get().setRelevantEntry(entry);
-        jitDebugRegisterCode();
+        jitDebugRegisterCode(CurrentIsolate.getCurrentThread());
     }
 
-    @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
+    @Uninterruptible(reason = "Called with raw object pointer.", calleeMustBe = false)
     public static void unregisterJITCode(JITCodeEntry entry) {
         JITCodeEntry prevEntry = entry.getPrevEntry();
         JITCodeEntry nextEntry = entry.getNextEntry();
@@ -187,10 +193,8 @@ public static void unregisterJITCode(JITCodeEntry entry) {
         }
 
         /* Notify GDB. */
-        jitDebugDescriptor.get().setActionFlag(JITActions.JIT_REGISTER.ordinal());
+        jitDebugDescriptor.get().setActionFlag(JITActions.JIT_REGISTER.getCValue());
         jitDebugDescriptor.get().setRelevantEntry(entry);
-        jitDebugRegisterCode();
-
-        ImageSingletons.lookup(UnmanagedMemorySupport.class).free(entry);
+        jitDebugRegisterCode(CurrentIsolate.getCurrentThread());
     }
 }
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 cf48e3b85d75..ece44e6ad024 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
@@ -86,7 +86,6 @@
 import jdk.graal.compiler.core.target.Backend;
 import jdk.graal.compiler.debug.DebugContext;
 import jdk.graal.compiler.graph.NodeSourcePosition;
-import jdk.graal.compiler.java.StableMethodNameFormatter;
 import jdk.graal.compiler.util.Digest;
 import jdk.vm.ci.code.BytecodeFrame;
 import jdk.vm.ci.code.BytecodePosition;
@@ -335,11 +334,11 @@ public void installDebugInfo() {
         }
     }
 
-    protected void handleDataInfo(Object data) {
+    protected void handleDataInfo(@SuppressWarnings("unused") Object data) {
     }
 
     private void handleTypeInfo(SharedType type) {
-        TypeEntry typeEntry = lookupTypeEntry(type);
+        lookupTypeEntry(type);
     }
 
     private void handleCodeInfo(SharedMethod method, CompilationResult compilation) {
@@ -501,9 +500,9 @@ protected MethodEntry installMethodEntry(SharedMethod method) {
             String symbolName = getSymbolName(method);
             int vTableOffset = getVTableOffset(method);
 
-            boolean isDeopt = isDeopt(method, methodName);
             boolean isOverride = isOverride(method);
-            boolean isConstructor = isConstructor(method);
+            boolean isDeopt = method.isDeoptTarget();
+            boolean isConstructor = method.isConstructor();
 
             return methodIndex.computeIfAbsent(method, m -> new MethodEntry(fileEntry, line, methodName, ownerType,
                             valueType, modifiers, paramInfos, thisParam, symbolName, isDeopt, isOverride, isConstructor,
@@ -657,22 +656,11 @@ public String getSymbolName(SharedMethod method) {
         return UniqueShortNameProvider.singleton().uniqueShortName(null, method.getDeclaringClass(), method.getName(), method.getSignature(), method.isConstructor());
     }
 
-    public boolean isDeopt(SharedMethod method, String methodName) {
-        if (method instanceof SharedMethod sharedMethod) {
-            return sharedMethod.isDeoptTarget();
-        }
-        return methodName.endsWith(StableMethodNameFormatter.MULTI_METHOD_KEY_SEPARATOR);
-    }
-
-    public boolean isOverride(SharedMethod method) {
+    public boolean isOverride(@SuppressWarnings("unused") SharedMethod method) {
         return false;
     }
 
-    public boolean isConstructor(SharedMethod method) {
-        return method.isConstructor();
-    }
-
-    public boolean isVirtual(SharedMethod method) {
+    public boolean isVirtual(@SuppressWarnings("unused") SharedMethod method) {
         return false;
     }
 
@@ -726,10 +714,11 @@ public TypeEntry lookupTypeEntry(SharedType type) {
     }
 
     public LoaderEntry lookupLoaderEntry(SharedType type) {
+        SharedType targetType = type;
         if (type.isArray()) {
-            type = (SharedType) type.getElementalType();
+            targetType = (SharedType) type.getElementalType();
         }
-        return type.getHub().isLoaded() ? lookupLoaderEntry(UniqueShortNameProvider.singleton().uniqueShortLoaderName(type.getHub().getClassLoader())) : null;
+        return targetType.getHub().isLoaded() ? lookupLoaderEntry(UniqueShortNameProvider.singleton().uniqueShortLoaderName(targetType.getHub().getClassLoader())) : null;
     }
 
     public LoaderEntry lookupLoaderEntry(String loaderName) {
@@ -754,7 +743,7 @@ public FileEntry lookupFileEntry(ResolvedJavaField field) {
     }
 
     public FileEntry lookupFileEntry(Path fullFilePath) {
-        if (fullFilePath == null) {
+        if (fullFilePath == null || fullFilePath.getFileName() == null) {
             return null;
         }
 
@@ -770,11 +759,7 @@ public FileEntry lookupFileEntry(Path fullFilePath) {
     }
 
     public DirEntry lookupDirEntry(Path dirPath) {
-        if (dirPath == null) {
-            dirPath = EMPTY_PATH;
-        }
-
-        return dirIndex.computeIfAbsent(dirPath, DirEntry::new);
+        return dirIndex.computeIfAbsent(dirPath == null ? EMPTY_PATH : dirPath, DirEntry::new);
     }
 
     /* Other helper functions. */
@@ -821,7 +806,7 @@ protected boolean isForeignWordType(SharedType type) {
         return wordBaseType.isAssignableFrom(type);
     }
 
-    private int findMarkOffset(SubstrateBackend.SubstrateMarkId markId, CompilationResult compilation) {
+    private static int findMarkOffset(SubstrateBackend.SubstrateMarkId markId, CompilationResult compilation) {
         for (CompilationResult.CodeMark mark : compilation.getMarks()) {
             if (mark.id.equals(markId)) {
                 return mark.pcOffset;
@@ -1043,11 +1028,12 @@ public void apply(CompilationResultFrameTree.FrameNode node, Object... args) {
             }
         }
 
-        protected void handleCallNode(CompilationResultFrameTree.CallNode callNode, CallRange locationInfo) {
+        protected void handleCallNode(@SuppressWarnings("unused") CompilationResultFrameTree.CallNode callNode, @SuppressWarnings("unused") CallRange locationInfo) {
             // do nothing by default
         }
 
-        protected void handleNodeToEmbed(CompilationResultFrameTree.CallNode nodeToEmbed, CompilationResultFrameTree.FrameNode node, CallRange callerInfo, Object... args) {
+        protected void handleNodeToEmbed(@SuppressWarnings("unused") CompilationResultFrameTree.CallNode nodeToEmbed, @SuppressWarnings("unused") CompilationResultFrameTree.FrameNode node,
+                        @SuppressWarnings("unused") CallRange callerInfo, @SuppressWarnings("unused") Object... args) {
             // do nothing by default
         }
 
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoFeature.java
index 9f3776006ce1..d73e980f182c 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoFeature.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoFeature.java
@@ -55,6 +55,6 @@ public void registerCodeObserver(RuntimeConfiguration runtimeConfig) {
         // This is called at image build-time -> the factory then creates a RuntimeDebugInfoProvider
         // at runtime
         ImageSingletons.lookup(InstalledCodeObserverSupport.class).addObserverFactory(new SubstrateDebugInfoInstaller.Factory(runtimeConfig.getProviders().getMetaAccess(), runtimeConfig));
-        ImageSingletons.add(SubstrateDebugInfoInstaller.Accessor.class, new SubstrateDebugInfoInstaller.Accessor());
+        ImageSingletons.add(SubstrateDebugInfoInstaller.GDBJITAccessor.class, new SubstrateDebugInfoInstaller.GDBJITAccessor());
     }
 }
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
index ca8bd170f274..ee9cec051c03 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
@@ -38,7 +38,6 @@
 import org.graalvm.nativeimage.c.struct.RawStructure;
 import org.graalvm.nativeimage.c.struct.SizeOf;
 import org.graalvm.nativeimage.c.type.CCharPointer;
-import org.graalvm.nativeimage.impl.UnmanagedMemorySupport;
 import org.graalvm.word.Pointer;
 
 import com.oracle.objectfile.BasicNobitsSectionImpl;
@@ -49,6 +48,7 @@
 import com.oracle.svm.core.c.NonmovableArrays;
 import com.oracle.svm.core.code.InstalledCodeObserver;
 import com.oracle.svm.core.graal.meta.RuntimeConfiguration;
+import com.oracle.svm.core.memory.NativeMemory;
 import com.oracle.svm.core.meta.SharedMethod;
 import com.oracle.svm.core.nmt.NmtCategory;
 import com.oracle.svm.core.os.VirtualMemoryProvider;
@@ -140,12 +140,12 @@ private interface Handle extends InstalledCodeObserverHandle {
         void setState(int value);
     }
 
-    static final class Accessor implements InstalledCodeObserverHandleAccessor {
+    static final class GDBJITAccessor implements InstalledCodeObserverHandleAccessor {
 
         static Handle createHandle(NonmovableArray<Byte> debugInfoData) {
             Handle handle = UnmanagedMemory.malloc(SizeOf.get(Handle.class));
             GDBJITInterface.JITCodeEntry entry = UnmanagedMemory.calloc(SizeOf.get(GDBJITInterface.JITCodeEntry.class));
-            handle.setAccessor(ImageSingletons.lookup(Accessor.class));
+            handle.setAccessor(ImageSingletons.lookup(GDBJITAccessor.class));
             handle.setRawHandle(entry);
             handle.setDebugInfoData(debugInfoData);
             handle.setState(Handle.INITIALIZED);
@@ -167,17 +167,19 @@ public void activate(InstalledCodeObserverHandle installedCodeObserverHandle) {
         }
 
         @Override
+        @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
         public void release(InstalledCodeObserverHandle installedCodeObserverHandle) {
             Handle handle = (Handle) installedCodeObserverHandle;
             VMOperation.guaranteeInProgress("SubstrateDebugInfoInstaller.Accessor.release must run in a VMOperation");
-            VMError.guarantee(handle.getState() == Handle.ACTIVATED);
-
             GDBJITInterface.JITCodeEntry entry = handle.getRawHandle();
-            GDBJITInterface.unregisterJITCode(entry);
-
-            handle.setState(Handle.RELEASED);
+            // Handle may still be just initialized here, so it never got registered in GDB.
+            if (handle.getState() == Handle.ACTIVATED) {
+                GDBJITInterface.unregisterJITCode(entry);
+                handle.setState(Handle.RELEASED);
+            }
+            NativeMemory.free(entry);
             NonmovableArrays.releaseUnmanagedArray(handle.getDebugInfoData());
-            UnmanagedMemory.free(handle);
+            NativeMemory.free(handle);
         }
 
         @Override
@@ -195,22 +197,14 @@ public void attachToCurrentIsolate(InstalledCodeObserverHandle installedCodeObse
         @Override
         @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
         public void releaseOnTearDown(InstalledCodeObserverHandle installedCodeObserverHandle) {
-            Handle handle = (Handle) installedCodeObserverHandle;
-            if (handle.getState() == Handle.ACTIVATED) {
-                GDBJITInterface.JITCodeEntry entry = handle.getRawHandle();
-                GDBJITInterface.unregisterJITCode(entry);
-                handle.setState(Handle.RELEASED);
-            }
-            NonmovableArrays.releaseUnmanagedArray(handle.getDebugInfoData());
-            // UnmanagedMemory.free(handle); -> change because of Uninterruptible annotation
-            ImageSingletons.lookup(UnmanagedMemorySupport.class).free(handle);
+            release(installedCodeObserverHandle);
         }
     }
 
     @Override
     public InstalledCodeObserverHandle install() {
         NonmovableArray<Byte> debugInfoData = writeDebugInfoData();
-        Handle handle = Accessor.createHandle(debugInfoData);
+        Handle handle = GDBJITAccessor.createHandle(debugInfoData);
         System.out.println(toString(handle));
         return handle;
     }
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
index 2649ba4255d5..4cdb0adde339 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
@@ -52,14 +52,14 @@
 
 public class SubstrateDebugInfoProvider extends SharedDebugInfoProvider {
 
-    private final SharedMethod method;
+    private final SharedMethod sharedMethod;
     private final CompilationResult compilation;
     private final long codeAddress;
 
-    public SubstrateDebugInfoProvider(DebugContext debug, SharedMethod method, CompilationResult compilation, RuntimeConfiguration runtimeConfiguration, MetaAccessProvider metaAccess,
+    public SubstrateDebugInfoProvider(DebugContext debug, SharedMethod sharedMethod, CompilationResult compilation, RuntimeConfiguration runtimeConfiguration, MetaAccessProvider metaAccess,
                     long codeAddress) {
         super(debug, runtimeConfiguration, metaAccess, SubstrateOptions.getRuntimeSourceDestDir());
-        this.method = method;
+        this.sharedMethod = sharedMethod;
         this.compilation = compilation;
         this.codeAddress = codeAddress;
     }
@@ -69,8 +69,8 @@ public String getCompilationName() {
         if (compilation != null) {
             name = compilation.getName();
         }
-        if ((name == null || name.isEmpty()) && method != null) {
-            name = method.format("%h.%n");
+        if ((name == null || name.isEmpty()) && sharedMethod != null) {
+            name = sharedMethod.format("%h.%n");
         }
         if (name == null || name.isEmpty()) {
             name = "UnnamedCompilation";
@@ -91,7 +91,7 @@ protected Stream<SharedType> typeInfo() {
 
     @Override
     protected Stream<Pair<SharedMethod, CompilationResult>> codeInfo() {
-        return Stream.of(Pair.create(method, compilation));
+        return Stream.of(Pair.create(sharedMethod, compilation));
     }
 
     @Override
@@ -101,7 +101,7 @@ protected Stream<Object> dataInfo() {
     }
 
     @Override
-    protected long getCodeOffset(SharedMethod method) {
+    protected long getCodeOffset(@SuppressWarnings("unused") SharedMethod method) {
         return codeAddress;
     }
 
@@ -118,7 +118,7 @@ public FileEntry lookupFileEntry(ResolvedJavaType type) {
         return super.lookupFileEntry(type);
     }
 
-    private int getTypeSize(SharedType type) {
+    private static int getTypeSize(SharedType type) {
         if (type.isPrimitive()) {
             JavaKind javaKind = type.getStorageKind();
             return (javaKind == JavaKind.Void ? 0 : javaKind.getByteCount());
@@ -169,12 +169,12 @@ protected TypeEntry createTypeEntry(SharedType type) {
     }
 
     @Override
-    protected void processTypeEntry(SharedType type, TypeEntry typeEntry) {
+    protected void processTypeEntry(@SuppressWarnings("unused") SharedType type, @SuppressWarnings("unused") TypeEntry typeEntry) {
         // nothing to do here
     }
 
     @Override
-    public long objectOffset(JavaConstant constant) {
+    public long objectOffset(@SuppressWarnings("unused") JavaConstant constant) {
         return 0;
     }
 }
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 0e9bb09b1adb..187da090fcba 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
@@ -49,7 +49,6 @@
 import com.oracle.graal.pointsto.infrastructure.WrappedJavaType;
 import com.oracle.objectfile.debugentry.ArrayTypeEntry;
 import com.oracle.objectfile.debugentry.ClassEntry;
-import com.oracle.objectfile.debugentry.CompiledMethodEntry;
 import com.oracle.objectfile.debugentry.EnumClassEntry;
 import com.oracle.objectfile.debugentry.FieldEntry;
 import com.oracle.objectfile.debugentry.FileEntry;
@@ -140,27 +139,31 @@ class NativeImageDebugInfoProvider extends SharedDebugInfoProvider {
                         .collect(Collectors.toSet());
     }
 
+    @SuppressWarnings("unused")
     private static ResolvedJavaType getOriginal(ResolvedJavaType type) {
         /*
          * unwrap then traverse through substitutions to the original. We don't want to get the
          * original type of LambdaSubstitutionType to keep the stable name
          */
-        while (type instanceof WrappedJavaType wrappedJavaType) {
-            type = wrappedJavaType.getWrapped();
+        ResolvedJavaType targetType = type;
+        while (targetType instanceof WrappedJavaType wrappedJavaType) {
+            targetType = wrappedJavaType.getWrapped();
         }
 
-        if (type instanceof SubstitutionType substitutionType) {
-            type = substitutionType.getOriginal();
-        } else if (type instanceof InjectedFieldsType injectedFieldsType) {
-            type = injectedFieldsType.getOriginal();
+        if (targetType instanceof SubstitutionType substitutionType) {
+            targetType = substitutionType.getOriginal();
+        } else if (targetType instanceof InjectedFieldsType injectedFieldsType) {
+            targetType = injectedFieldsType.getOriginal();
         }
 
-        return type;
+        return targetType;
     }
 
+    @SuppressWarnings("unused")
     private static ResolvedJavaMethod getAnnotatedOrOriginal(ResolvedJavaMethod method) {
-        while (method instanceof WrappedJavaMethod wrappedJavaMethod) {
-            method = wrappedJavaMethod.getWrapped();
+        ResolvedJavaMethod targetMethod = method;
+        while (targetMethod instanceof WrappedJavaMethod wrappedJavaMethod) {
+            targetMethod = wrappedJavaMethod.getWrapped();
         }
         // This method is only used when identifying the modifiers or the declaring class
         // of a HostedMethod. Normally the method unwraps to the underlying JVMCI method
@@ -177,11 +180,11 @@ private static ResolvedJavaMethod getAnnotatedOrOriginal(ResolvedJavaMethod meth
         // reference to the bytecode of the original. Hence, there is no associated file and the
         // permissions need to be determined from the custom substitution method itself.
 
-        if (method instanceof SubstitutionMethod substitutionMethod) {
-            method = substitutionMethod.getAnnotated();
+        if (targetMethod instanceof SubstitutionMethod substitutionMethod) {
+            targetMethod = substitutionMethod.getAnnotated();
         }
 
-        return method;
+        return targetMethod;
     }
 
     @Override
@@ -347,18 +350,11 @@ protected long getCodeOffset(SharedMethod method) {
 
     @Override
     public MethodEntry lookupMethodEntry(SharedMethod method) {
-        if (method instanceof HostedMethod hostedMethod && !hostedMethod.isOriginalMethod()) {
-            method = hostedMethod.getMultiMethod(MultiMethod.ORIGINAL_METHOD);
+        SharedMethod targetMethod = method;
+        if (targetMethod instanceof HostedMethod hostedMethod && !hostedMethod.isOriginalMethod()) {
+            targetMethod = hostedMethod.getMultiMethod(MultiMethod.ORIGINAL_METHOD);
         }
-        return super.lookupMethodEntry(method);
-    }
-
-    @Override
-    public CompiledMethodEntry lookupCompiledMethodEntry(MethodEntry methodEntry, SharedMethod method, CompilationResult compilation) {
-        if (method instanceof HostedMethod hostedMethod && !hostedMethod.isOriginalMethod()) {
-            // method = hostedMethod.getMultiMethod(MultiMethod.ORIGINAL_METHOD);
-        }
-        return super.lookupCompiledMethodEntry(methodEntry, method, compilation);
+        return super.lookupMethodEntry(targetMethod);
     }
 
     @Override
@@ -526,7 +522,7 @@ protected TypeEntry createTypeEntry(SharedType type) {
         assert type instanceof HostedType;
         HostedType hostedType = (HostedType) type;
 
-        String typeName = hostedType.toJavaName(); // stringTable.uniqueDebugString(idType.toJavaName());
+        String typeName = hostedType.toJavaName();
         int size = getTypeSize(hostedType);
         long classOffset = getClassOffset(hostedType);
         LoaderEntry loaderEntry = lookupLoaderEntry(hostedType);
@@ -786,14 +782,15 @@ public FileEntry lookupFileEntry(ResolvedJavaType type) {
 
     @Override
     public FileEntry lookupFileEntry(ResolvedJavaMethod method) {
-        if (method instanceof HostedMethod hostedMethod && hostedMethod.getWrapped().getWrapped() instanceof SubstitutionMethod substitutionMethod) {
+        ResolvedJavaMethod targetMethod = method;
+        if (targetMethod instanceof HostedMethod hostedMethod && hostedMethod.getWrapped().getWrapped() instanceof SubstitutionMethod substitutionMethod) {
             // we always want to look up the file of the annotated method
-            method = substitutionMethod.getAnnotated();
+            targetMethod = substitutionMethod.getAnnotated();
         }
-        return super.lookupFileEntry(method);
+        return super.lookupFileEntry(targetMethod);
     }
 
-    private int getTypeSize(HostedType type) {
+    private static int getTypeSize(HostedType type) {
         switch (type) {
             case HostedInstanceClass hostedInstanceClass -> {
                 /* We know the actual instance size in bytes. */
@@ -840,6 +837,7 @@ private long getClassOffset(HostedType type) {
      * @return the offset into the initial heap at which the object identified by constant is
      *         located or -1 if the object is not present in the initial heap.
      */
+    @Override
     public long objectOffset(JavaConstant constant) {
         assert constant.getJavaKind() == JavaKind.Object && !constant.isNull() : "invalid constant for object offset lookup";
         NativeImageHeap.ObjectInfo objectInfo = heap.getConstantInfo(constant);
diff --git a/substratevm/src/com.oracle.svm.native.debug/src/gdb_jit_compilation_interface.c b/substratevm/src/com.oracle.svm.native.debug/src/gdb_jit_compilation_interface.c
deleted file mode 100644
index 6a8cdfea014c..000000000000
--- a/substratevm/src/com.oracle.svm.native.debug/src/gdb_jit_compilation_interface.c
+++ /dev/null
@@ -1,7 +0,0 @@
-/*
- * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
- * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
- */
-
-// dummy include
-#include <gdb_jit_compilation_interface.h>

From fa9cd5715b5e01c1ec7f8d1de50ab029e2759d25 Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Tue, 7 Jan 2025 11:07:08 +0100
Subject: [PATCH 17/34] Bugfix in handle release for GDB JIT compilation
 interface

---
 .../src/com/oracle/svm/core/debug/GDBJITInterface.java   | 2 +-
 .../svm/core/debug/SubstrateDebugInfoInstaller.java      | 9 +++++----
 2 files changed, 6 insertions(+), 5 deletions(-)

diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterface.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterface.java
index 78e8cb319f73..ff2c28483bbe 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterface.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterface.java
@@ -193,7 +193,7 @@ public static void unregisterJITCode(JITCodeEntry entry) {
         }
 
         /* Notify GDB. */
-        jitDebugDescriptor.get().setActionFlag(JITActions.JIT_REGISTER.getCValue());
+        jitDebugDescriptor.get().setActionFlag(JITActions.JIT_UNREGISTER.getCValue());
         jitDebugDescriptor.get().setRelevantEntry(entry);
         jitDebugRegisterCode(CurrentIsolate.getCurrentThread());
     }
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
index ee9cec051c03..ba4c26e48e64 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
@@ -38,6 +38,7 @@
 import org.graalvm.nativeimage.c.struct.RawStructure;
 import org.graalvm.nativeimage.c.struct.SizeOf;
 import org.graalvm.nativeimage.c.type.CCharPointer;
+import org.graalvm.nativeimage.impl.UnmanagedMemorySupport;
 import org.graalvm.word.Pointer;
 
 import com.oracle.objectfile.BasicNobitsSectionImpl;
@@ -48,7 +49,6 @@
 import com.oracle.svm.core.c.NonmovableArrays;
 import com.oracle.svm.core.code.InstalledCodeObserver;
 import com.oracle.svm.core.graal.meta.RuntimeConfiguration;
-import com.oracle.svm.core.memory.NativeMemory;
 import com.oracle.svm.core.meta.SharedMethod;
 import com.oracle.svm.core.nmt.NmtCategory;
 import com.oracle.svm.core.os.VirtualMemoryProvider;
@@ -170,16 +170,17 @@ public void activate(InstalledCodeObserverHandle installedCodeObserverHandle) {
         @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
         public void release(InstalledCodeObserverHandle installedCodeObserverHandle) {
             Handle handle = (Handle) installedCodeObserverHandle;
-            VMOperation.guaranteeInProgress("SubstrateDebugInfoInstaller.Accessor.release must run in a VMOperation");
+            // VMOperation.guaranteeInProgress("SubstrateDebugInfoInstaller.Accessor.release must
+            // run in a VMOperation");
             GDBJITInterface.JITCodeEntry entry = handle.getRawHandle();
             // Handle may still be just initialized here, so it never got registered in GDB.
             if (handle.getState() == Handle.ACTIVATED) {
                 GDBJITInterface.unregisterJITCode(entry);
                 handle.setState(Handle.RELEASED);
             }
-            NativeMemory.free(entry);
+            ImageSingletons.lookup(UnmanagedMemorySupport.class).free(entry);
             NonmovableArrays.releaseUnmanagedArray(handle.getDebugInfoData());
-            NativeMemory.free(handle);
+            ImageSingletons.lookup(UnmanagedMemorySupport.class).free(handle);
         }
 
         @Override

From d6866e598931e9f86f59b9bd765a27353fd7dfb5 Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Fri, 10 Jan 2025 20:31:23 +0100
Subject: [PATCH 18/34] Updates and fixes of gdb-debughelpers for shared
 library supports

---
 substratevm/debug/gdbpy/gdb-debughelpers.py   | 959 +++++++++---------
 substratevm/mx.substratevm/mx_substratevm.py  |   4 +-
 .../svm/test/debug/helper/gdb_helper.py       |   4 +-
 .../svm/test/debug/helper/test_cinterface.py  |  16 +-
 .../test/debug/helper/test_class_loader.py    |   5 +-
 .../svm/test/debug/helper/test_svm_util.py    |  52 +-
 6 files changed, 535 insertions(+), 505 deletions(-)

diff --git a/substratevm/debug/gdbpy/gdb-debughelpers.py b/substratevm/debug/gdbpy/gdb-debughelpers.py
index f4366c3fee32..cd4e3b6c2aa1 100644
--- a/substratevm/debug/gdbpy/gdb-debughelpers.py
+++ b/substratevm/debug/gdbpy/gdb-debughelpers.py
@@ -83,29 +83,12 @@ def __init__(self, static: bool, name: str, gdb_sym: gdb.Symbol):
 
 
 class SVMUtil:
+    # class fields
     pretty_printer_name = "SubstrateVM"
 
     hub_field_name = "hub"
     compressed_ref_prefix = '_z_.'
 
-    use_heap_base = try_or_else(lambda: bool(gdb.parse_and_eval('(int)__svm_use_heap_base')), True, gdb.error)
-    compression_shift = try_or_else(lambda: int(gdb.parse_and_eval('(int)__svm_compression_shift')), 0, gdb.error)
-    reserved_bits_mask = try_or_else(lambda: int(gdb.parse_and_eval('(int)__svm_reserved_bits_mask')), 0, gdb.error)
-    object_alignment = try_or_else(lambda: int(gdb.parse_and_eval('(int)__svm_object_alignment')), 0, gdb.error)
-    heap_base_regnum = try_or_else(lambda: int(gdb.parse_and_eval('(int)__svm_heap_base_regnum')), 0, gdb.error)
-    frame_size_status_mask = try_or_else(lambda: int(gdb.parse_and_eval('(int)__svm_frame_size_status_mask')), 0, gdb.error)
-
-    stack_type = gdb.lookup_type("long")
-    string_type = gdb.lookup_type("java.lang.String")
-    enum_type = gdb.lookup_type("java.lang.Enum")
-    object_type = gdb.lookup_type("java.lang.Object")
-    hub_type = gdb.lookup_type("java.lang.Class")
-    null = gdb.Value(0).cast(object_type.pointer())
-    classloader_type = gdb.lookup_type("java.lang.ClassLoader")
-    wrapper_types = [gdb.lookup_type(f'java.lang.{x}') for x in
-                     ["Byte", "Short", "Integer", "Long", "Float", "Double", "Boolean", "Character"]
-                     if gdb.lookup_global_symbol(f'java.lang.{x}') is not None]
-
     pretty_print_objfiles = set()
 
     current_print_depth = 0
@@ -113,7 +96,6 @@ class SVMUtil:
     selfref_cycles = set()
 
     hlreps = dict()
-    deopt_stub_adr = 0
 
     # AMD64 registers
     AMD64_RSP = 7
@@ -123,13 +105,43 @@ class SVMUtil:
     AARCH64_RSP = 30
     AARCH64_RIP = 31
 
+    # static methods
+    @staticmethod
+    def get_deopt_stub_adr() -> int:
+        return gdb.lookup_global_symbol('com.oracle.svm.core.deopt.Deoptimizer::deoptStub',
+                                        gdb.SYMBOL_VAR_DOMAIN).value().address
+
+    @staticmethod
+    def get_unqualified_type_name(qualified_type_name: str) -> str:
+        result = qualified_type_name.split('.')[-1]
+        result = result.split('$')[-1]
+        trace(f'<SVMUtil> - get_unqualified_type_name({qualified_type_name}) = {result}')
+        return result
+
+    @staticmethod
+    def get_symbol_adr(symbol: str) -> int:
+        trace(f'<SVMUtil> - get_symbol_adr({symbol})')
+        return gdb.parse_and_eval(symbol).address
+
+    @staticmethod
+    def execout(cmd: str) -> str:
+        trace(f'<SVMUtil> - execout({cmd})')
+        return gdb.execute(cmd, False, True)
+
+    @staticmethod
+    def get_basic_type(t: gdb.Type) -> gdb.Type:
+        trace(f'<SVMUtil> - get_basic_type({t})')
+        while t.code == gdb.TYPE_CODE_PTR:
+            t = t.target()
+        return t
+
+    # class methods
     @classmethod
-    def get_heap_base(cls) -> gdb.Value:
-        try:
-            return gdb.selected_frame().read_register(cls.heap_base_regnum)
-        except gdb.error:
-            # no frame available
-            return cls.null
+    def prompt_hook(cls, current_prompt: str = None):
+        cls.current_print_depth = 0
+        cls.parents.clear()
+        cls.selfref_cycles.clear()
+        SVMCommandPrint.cache.clear()
 
     @classmethod
     def get_rsp(cls) -> int:
@@ -150,79 +162,145 @@ def get_rip(cls) -> int:
         return 0
 
     @classmethod
-    def get_adr(cls, obj: gdb.Value) -> int:
-        # use null as fallback if we cannot find the address value or obj is null
-        adr_val = 0
-        if obj.type.code == gdb.TYPE_CODE_PTR:
-            if int(obj) == 0 or (cls.use_heap_base and int(obj) == int(cls.get_heap_base())):
-                # obj is null
-                pass
-            else:
-                adr_val = int(obj.dereference().address)
-        elif obj.address is not None:
-            adr_val = int(obj.address)
-        return adr_val
+    # checks if node this is reachable from node other (this node is parent of other node)
+    def is_reachable(cls, this: hex, other: hex) -> bool:
+        test_nodes = [other]
+        trace(f'<SVMUtil> - is_reachable(this={this}, other={other}')
+        while True:
+            if len(test_nodes) == 0:
+                return False
+            if any(this == node for node in test_nodes):
+                return True
+            # create a flat list of all ancestors of each tested node
+            test_nodes = [parent for node in test_nodes for parent in cls.parents.get(node, [])]
 
     @classmethod
-    def is_null(cls, obj: gdb.Value) -> bool:
-        return cls.get_adr(obj) == 0
+    def is_compressed(cls, t: gdb.Type) -> bool:
+        type_name = cls.get_basic_type(t).name
+        if type_name is None:
+            # fallback to the GDB type printer for t
+            type_name = str(t)
+        # compressed types from a different classLoader have the format <loader_name>::_z_.<type_name>
+        result = type_name.startswith(cls.compressed_ref_prefix) or ('::' + cls.compressed_ref_prefix) in type_name
+        trace(f'<SVMUtil> - is_compressed({type_name}) = {result}')
+        return result
 
     @classmethod
-    def get_uncompressed_type(cls, t: gdb.Type) -> gdb.Type:
-        # compressed types only exist for java type which are either struct or union
-        if t.code != gdb.TYPE_CODE_STRUCT and t.code != gdb.TYPE_CODE_UNION:
-            return t
-        result = cls.get_base_class(t) if cls.is_compressed(t) else t
-        trace(f'<SVMUtil> - get_uncompressed_type({t}) = {result}')
+    def is_primitive(cls, t: gdb.Type) -> bool:
+        result = cls.get_basic_type(t).is_scalar
+        trace(f'<SVMUtil> - is_primitive({t}) = {result}')
         return result
 
-    # returns the compressed variant of t if available, otherwise returns the basic type of t (without pointers)
     @classmethod
-    def get_compressed_type(cls, t: gdb.Type) -> gdb.Type:
+    def get_all_fields(cls, t: gdb.Type, include_static: bool) -> list:  # list[gdb.Field]:
         t = cls.get_basic_type(t)
-        # compressed types only exist for java types which are either struct or union
-        # do not compress types that already have the compressed prefix
-        if not cls.is_java_type(t) or cls.is_compressed(t):
-            return t
+        while t.code == gdb.TYPE_CODE_TYPEDEF:
+            t = t.target()
+            t = cls.get_basic_type(t)
+        if t.code != gdb.TYPE_CODE_STRUCT and t.code != gdb.TYPE_CODE_UNION:
+            return []
+        for f in t.fields():
+            if not include_static:
+                try:
+                    f.bitpos  # bitpos attribute is not available for static fields
+                except AttributeError:  # use bitpos access exception to skip static fields
+                    continue
+            if f.is_base_class:
+                yield from cls.get_all_fields(f.type, include_static)
+            else:
+                yield f
 
-        type_name = t.name
-        # java types only contain '::' if there is a classloader namespace
-        if '::' in type_name:
-            loader_namespace, _, type_name = type_name.partition('::')
-            type_name = loader_namespace + '::' + cls.compressed_ref_prefix + type_name
-        else:
-            type_name = cls.compressed_ref_prefix + type_name
+    @classmethod
+    def get_all_member_functions(cls, t: gdb.Type, include_static: bool, include_constructor: bool) -> set:  # set[Function]:
+        syms = set()
+        try:
+            basic_type = cls.get_basic_type(t)
+            type_name = basic_type.name
+            members = cls.execout(f"ptype '{type_name}'")
+            for member in members.split('\n'):
+                parts = member.strip().split(' ')
+                is_static = parts[0] == 'static'
+                if not include_static and is_static:
+                    continue
+                for part in parts:
+                    if '(' in part:
+                        func_name = part[:part.find('(')]
+                        if include_constructor or func_name != cls.get_unqualified_type_name(type_name):
+                            sym = gdb.lookup_global_symbol(f"{type_name}::{func_name}")
+                            # check if symbol exists and is a function
+                            if sym is not None and sym.type.code == gdb.TYPE_CODE_FUNC:
+                                syms.add(Function(is_static, func_name, sym))
+                        break
+            for f in basic_type.fields():
+                if f.is_base_class:
+                    syms = syms.union(cls.get_all_member_functions(f.type, include_static, include_constructor))
+        except Exception as ex:
+            trace(f'<SVMUtil> - get_all_member_function_names({t}) exception: {ex}')
+        return syms
 
+    # instance initializer
+    # there will be one instance of SVMUtil per objfile that is registered
+    # each objfile has its own types, thus this is necessary to compare against the correct types in memory
+    # when reloading e.g. a shared library, the addresses of debug info in the relocatable objfile might change
+    def __init__(self):
+        self.use_heap_base = try_or_else(lambda: bool(gdb.parse_and_eval('(int)__svm_use_heap_base')), True, gdb.error)
+        self.compression_shift = try_or_else(lambda: int(gdb.parse_and_eval('(int)__svm_compression_shift')), 0, gdb.error)
+        self.reserved_bits_mask = try_or_else(lambda: int(gdb.parse_and_eval('(int)__svm_reserved_bits_mask')), 0, gdb.error)
+        self.object_alignment = try_or_else(lambda: int(gdb.parse_and_eval('(int)__svm_object_alignment')), 0, gdb.error)
+        self.heap_base_regnum = try_or_else(lambda: int(gdb.parse_and_eval('(int)__svm_heap_base_regnum')), 0, gdb.error)
+        self.frame_size_status_mask = try_or_else(lambda: int(gdb.parse_and_eval('(int)__svm_frame_size_status_mask')), 0, gdb.error)
+        self.object_type = gdb.lookup_type("java.lang.Object")
+        self.stack_type = gdb.lookup_type("long")
+        self.hub_type = gdb.lookup_type("java.lang.Class")
+        self.classloader_type = gdb.lookup_type("java.lang.ClassLoader")
+        self.wrapper_types = [gdb.lookup_type(f'java.lang.{x}') for x in
+                              ["Byte", "Short", "Integer", "Long", "Float", "Double", "Boolean", "Character"]
+                              if gdb.lookup_global_symbol(f'java.lang.{x}') is not None]
+        self.null = gdb.Value(0).cast(self.object_type.pointer())
+
+    # instance methods
+    def get_heap_base(self) -> gdb.Value:
         try:
-            result_type = gdb.lookup_type(type_name)
-            trace(f'<SVMUtil> - could not find compressed type "{type_name}" using uncompressed type')
-        except gdb.error as ex:
-            trace(ex)
-            result_type = t
+            return gdb.selected_frame().read_register(self.heap_base_regnum)
+        except gdb.error:
+            # no frame available, return 0
+            return 0
 
-        trace(f'<SVMUtil> - get_compressed_type({t}) = {t.name}')
-        return result_type
+    def get_adr(self, obj: gdb.Value) -> int:
+        # use null as fallback if we cannot find the address value or obj is null
+        adr_val = 0
+        if obj.type.code == gdb.TYPE_CODE_PTR:
+            if int(obj) == 0 or (self.use_heap_base and int(obj) == int(self.get_heap_base())):
+                # obj is null
+                pass
+            else:
+                adr_val = int(obj.dereference().address)
+        elif obj.address is not None:
+            adr_val = int(obj.address)
+        return adr_val
 
-    @classmethod
-    def get_compressed_oop(cls, obj: gdb.Value) -> int:
+    def is_null(self, obj: gdb.Value) -> bool:
+        return self.get_adr(obj) == 0
+
+    def get_compressed_oop(self, obj: gdb.Value) -> int:
         # use compressed ref if available - only compute it if necessary
-        if obj.type.code == gdb.TYPE_CODE_PTR and cls.is_compressed(obj.type):
+        if obj.type.code == gdb.TYPE_CODE_PTR and SVMUtil.is_compressed(obj.type):
             return int(obj)
 
-        obj_adr = cls.get_adr(obj)
+        obj_adr = self.get_adr(obj)
         if obj_adr == 0:
             return obj_adr
 
         # recreate compressed oop from the object address
         # this reverses the uncompress expression from
         # com.oracle.objectfile.elf.dwarf.DwarfInfoSectionImpl#writeIndirectOopConversionExpression
-        is_hub = cls.get_rtt(obj) == cls.hub_type
-        compression_shift = cls.compression_shift
-        num_reserved_bits = int.bit_count(cls.reserved_bits_mask)
-        num_alignment_bits = int.bit_count(cls.object_alignment - 1)
+        is_hub = self.get_rtt(obj) == self.hub_type
+        compression_shift = self.compression_shift
+        num_reserved_bits = int.bit_count(self.reserved_bits_mask)
+        num_alignment_bits = int.bit_count(self.object_alignment - 1)
         compressed_oop = obj_adr
-        if cls.use_heap_base:
-            compressed_oop -= int(cls.get_heap_base())
+        if self.use_heap_base:
+            compressed_oop -= int(self.get_heap_base())
             assert compression_shift >= 0
             compressed_oop = compressed_oop >> compression_shift
         if is_hub and num_reserved_bits != 0:
@@ -235,88 +313,46 @@ def get_compressed_oop(cls, obj: gdb.Value) -> int:
 
         return compressed_oop
 
-    @classmethod
-    def get_unqualified_type_name(cls, qualified_type_name: str) -> str:
-        result = qualified_type_name.split('.')[-1]
-        result = result.split('$')[-1]
-        trace(f'<SVMUtil> - get_unqualified_type_name({qualified_type_name}) = {result}')
-        return result
-
-    @classmethod
-    def is_compressed(cls, t: gdb.Type) -> bool:
-        type_name = cls.get_basic_type(t).name
-        if type_name is None:
-            # fallback to the GDB type printer for t
-            type_name = str(t)
-        # compressed types from a different classLoader have the format <loader_name>::_z_.<type_name>
-        result = type_name.startswith(cls.compressed_ref_prefix) or ('::' + cls.compressed_ref_prefix) in type_name
-        trace(f'<SVMUtil> - is_compressed({type_name}) = {result}')
-        return result
-
-    @classmethod
-    def adr_str(cls, obj: gdb.Value) -> str:
-        if not svm_print_address.absolute_adr and cls.is_compressed(obj.type):
-            result = f' @z({hex(cls.get_compressed_oop(obj))})'
+    def adr_str(self, obj: gdb.Value) -> str:
+        if not svm_print_address.absolute_adr and SVMUtil.is_compressed(obj.type):
+            result = f' @z({hex(self.get_compressed_oop(obj))})'
         else:
-            result = f' @({hex(cls.get_adr(obj))})'
-        trace(f'<SVMUtil> - adr_str({hex(cls.get_adr(obj))}) = {result}')
+            result = f' @({hex(self.get_adr(obj))})'
+        trace(f'<SVMUtil> - adr_str({hex(self.get_adr(obj))}) = {result}')
         return result
 
-    @classmethod
-    def prompt_hook(cls, current_prompt: str = None):
-        cls.current_print_depth = 0
-        cls.parents.clear()
-        cls.selfref_cycles.clear()
-        SVMCommandPrint.cache.clear()
-
-    @classmethod
-    def is_selfref(cls, obj: gdb.Value) -> bool:
+    def is_selfref(self, obj: gdb.Value) -> bool:
         result = (svm_check_selfref.value and
-                  not cls.is_primitive(obj.type) and
-                  cls.get_adr(obj) in cls.selfref_cycles)
-        trace(f'<SVMUtil> - is_selfref({hex(cls.get_adr(obj))}) = {result}')
+                  not SVMUtil.is_primitive(obj.type) and
+                  self.get_adr(obj) in SVMUtil.selfref_cycles)
+        trace(f'<SVMUtil> - is_selfref({hex(self.get_adr(obj))}) = {result}')
         return result
 
-    @classmethod
-    def add_selfref(cls, parent: gdb.Value, child: gdb.Value) -> gdb.Value:
+    def add_selfref(self, parent: gdb.Value, child: gdb.Value) -> gdb.Value:
         # filter out null references and primitives
-        if (child.type.code == gdb.TYPE_CODE_PTR and cls.is_null(child)) or cls.is_primitive(child.type):
+        if (child.type.code == gdb.TYPE_CODE_PTR and self.is_null(child)) or SVMUtil.is_primitive(child.type):
             return child
 
-        child_adr = cls.get_adr(child)
-        parent_adr = cls.get_adr(parent)
+        child_adr = self.get_adr(child)
+        parent_adr = self.get_adr(parent)
         trace(f'<SVMUtil> - add_selfref(parent={hex(parent_adr)}, child={hex(child_adr)})')
-        if svm_check_selfref.value and cls.is_reachable(child_adr, parent_adr):
+        if svm_check_selfref.value and SVMUtil.is_reachable(child_adr, parent_adr):
             trace(f' <add selfref {child_adr}>')
-            cls.selfref_cycles.add(child_adr)
+            SVMUtil.selfref_cycles.add(child_adr)
         else:
             trace(f' <add {hex(child_adr)} --> {hex(parent_adr)}>')
-            if child_adr in cls.parents:
-                cls.parents[child_adr].append(parent_adr)
+            if child_adr in SVMUtil.parents:
+                SVMUtil.parents[child_adr].append(parent_adr)
             else:
-                cls.parents[child_adr] = [parent_adr]
+                SVMUtil.parents[child_adr] = [parent_adr]
         return child
 
-    @classmethod
-    # checks if node this is reachable from node other (this node is parent of other node)
-    def is_reachable(cls, this: hex, other: hex) -> bool:
-        test_nodes = [other]
-        trace(f'<SVMUtil> - is_reachable(this={this}, other={other}')
-        while True:
-            if len(test_nodes) == 0:
-                return False
-            if any(this == node for node in test_nodes):
-                return True
-            # create a flat list of all ancestors of each tested node
-            test_nodes = [parent for node in test_nodes for parent in cls.parents.get(node, [])]
-
-    @classmethod
-    def get_java_string(cls, obj: gdb.Value, gdb_output_string: bool = False) -> str:
-        if cls.is_null(obj):
+    def get_java_string(self, obj: gdb.Value, gdb_output_string: bool = False) -> str:
+        if self.is_null(obj):
             return ""
 
-        trace(f'<SVMUtil> - get_java_string({hex(cls.get_adr(obj))})')
-        coder = cls.get_int_field(obj, 'coder', None)
+        trace(f'<SVMUtil> - get_java_string({hex(self.get_adr(obj))})')
+        coder = self.get_int_field(obj, 'coder', None)
         if coder is None:
             codec = 'utf-16'  # Java 8 has a char[] with utf-16
             bytes_per_char = 2
@@ -329,13 +365,13 @@ def get_java_string(cls, obj: gdb.Value, gdb_output_string: bool = False) -> str
             }.get(coder)
             bytes_per_char = 1
 
-        value = cls.get_obj_field(obj, 'value')
-        if cls.is_null(value):
+        value = self.get_obj_field(obj, 'value')
+        if self.is_null(value):
             return ""
 
-        value_content = cls.get_obj_field(value, 'data')
-        value_length = cls.get_int_field(value, 'len')
-        if cls.is_null(value_content) or value_length == 0:
+        value_content = self.get_obj_field(value, 'data')
+        value_length = self.get_int_field(value, 'len')
+        if self.is_null(value_content) or value_length == 0:
             return ""
 
         string_data = bytearray()
@@ -349,72 +385,68 @@ def get_java_string(cls, obj: gdb.Value, gdb_output_string: bool = False) -> str
         if gdb_output_string and 0 < svm_print_string_limit.value < value_length:
             result += "..."
 
-        trace(f'<SVMUtil> - get_java_string({hex(cls.get_adr(obj))}) = {result}')
+        trace(f'<SVMUtil> - get_java_string({hex(self.get_adr(obj))}) = {result}')
         return result
 
-    @classmethod
-    def get_obj_field(cls, obj: gdb.Value, field_name: str, default: gdb.Value = null) -> gdb.Value:
+    def get_obj_field(self, obj: gdb.Value, field_name: str, default: gdb.Value = None) -> gdb.Value:
         try:
             return obj[field_name]
         except gdb.error:
-            return default
+            return self.null if default is None else default
 
-    @classmethod
-    def get_int_field(cls, obj: gdb.Value, field_name: str, default: int = 0) -> int:
-        field = cls.get_obj_field(obj, field_name)
+    def get_int_field(self, obj: gdb.Value, field_name: str, default: int = 0) -> int:
+        field = self.get_obj_field(obj, field_name)
         try:
             return int(field)
         except (gdb.error, TypeError):  # TypeError if field is None already
             return default
 
-    @classmethod
-    def get_classloader_namespace(cls, obj: gdb.Value) -> str:
+    def get_classloader_namespace(self, obj: gdb.Value) -> str:
         try:
-            hub = cls.get_obj_field(obj, cls.hub_field_name)
-            if cls.is_null(hub):
+            hub = self.get_obj_field(obj, SVMUtil.hub_field_name)
+            if self.is_null(hub):
                 return ""
 
-            hub_companion = cls.get_obj_field(hub, 'companion')
-            if cls.is_null(hub_companion):
+            hub_companion = self.get_obj_field(hub, 'companion')
+            if self.is_null(hub_companion):
                 return ""
 
-            loader = cls.get_obj_field(hub_companion, 'classLoader')
-            if cls.is_null(loader):
+            loader = self.get_obj_field(hub_companion, 'classLoader')
+            if self.is_null(loader):
                 return ""
-            loader = cls.cast_to(loader, cls.classloader_type)
+            loader = self.cast_to(loader, self.classloader_type)
 
-            loader_name = cls.get_obj_field(loader, 'nameAndId')
-            if cls.is_null(loader_name):
+            loader_name = self.get_obj_field(loader, 'nameAndId')
+            if self.is_null(loader_name):
                 return ""
 
-            loader_namespace = cls.get_java_string(loader_name)
+            loader_namespace = self.get_java_string(loader_name)
             trace(f'<SVMUtil> - get_classloader_namespace loader_namespace: {loader_namespace}')
             # replicate steps in 'com.oracle.svm.hosted.image.NativeImageBFDNameProvider::uniqueShortLoaderName'
             # for recreating the loader name stored in the DWARF debuginfo
-            loader_namespace = cls.get_unqualified_type_name(loader_namespace)
+            loader_namespace = SVMUtil.get_unqualified_type_name(loader_namespace)
             loader_namespace = loader_namespace.replace(' @', '_').replace("'", '').replace('"', '')
             return loader_namespace
         except gdb.error:
             pass  # ignore gdb errors here and try to continue with no classLoader
         return ""
 
-    @classmethod
-    def get_rtt(cls, obj: gdb.Value) -> gdb.Type:
-        static_type = cls.get_basic_type(obj.type)
+    def get_rtt(self, obj: gdb.Value) -> gdb.Type:
+        static_type = SVMUtil.get_basic_type(obj.type)
 
         # check for interfaces and cast them to Object to make the hub accessible
-        if cls.get_uncompressed_type(cls.get_basic_type(obj.type)).code == gdb.TYPE_CODE_UNION:
-            obj = cls.cast_to(obj, cls.object_type)
+        if self.get_uncompressed_type(SVMUtil.get_basic_type(obj.type)).code == gdb.TYPE_CODE_UNION:
+            obj = self.cast_to(obj, self.object_type)
 
-        hub = cls.get_obj_field(obj, cls.hub_field_name)
-        if cls.is_null(hub):
+        hub = self.get_obj_field(obj, SVMUtil.hub_field_name)
+        if self.is_null(hub):
             return static_type
 
-        name_field = cls.get_obj_field(hub, 'name')
-        if cls.is_null(name_field):
+        name_field = self.get_obj_field(hub, 'name')
+        if self.is_null(name_field):
             return static_type
 
-        rtt_name = cls.get_java_string(name_field)
+        rtt_name = self.get_java_string(name_field)
         if rtt_name.startswith('['):
             array_dimension = rtt_name.count('[')
             if array_dimension > 0:
@@ -436,7 +468,7 @@ def get_rtt(cls, obj: gdb.Value) -> gdb.Type:
             for _ in range(array_dimension):
                 rtt_name += '[]'
 
-        loader_namespace = cls.get_classloader_namespace(obj)
+        loader_namespace = self.get_classloader_namespace(obj)
         if loader_namespace != "":
             try:
                 # try to apply loader namespace
@@ -446,182 +478,139 @@ def get_rtt(cls, obj: gdb.Value) -> gdb.Type:
         else:
             rtt = gdb.lookup_type(rtt_name)
 
-        if cls.is_compressed(obj.type) and not cls.is_compressed(rtt):
-            rtt = cls.get_compressed_type(rtt)
+        if SVMUtil.is_compressed(obj.type) and not SVMUtil.is_compressed(rtt):
+            rtt = self.get_compressed_type(rtt)
 
-        trace(f'<SVMUtil> - get_rtt({hex(cls.get_adr(obj))}) = {rtt_name}')
+        trace(f'<SVMUtil> - get_rtt({hex(self.get_adr(obj))}) = {rtt_name}')
         return rtt
 
-    @classmethod
-    def cast_to(cls, obj: gdb.Value, t: gdb.Type) -> gdb.Value:
+    def cast_to(self, obj: gdb.Value, t: gdb.Type) -> gdb.Value:
         if t is None:
             return obj
 
         # get objects address, take care of compressed oops
-        if cls.is_compressed(t):
-            obj_oop = cls.get_compressed_oop(obj)
+        if SVMUtil.is_compressed(t):
+            obj_oop = self.get_compressed_oop(obj)
         else:
-            obj_oop = cls.get_adr(obj)
+            obj_oop = self.get_adr(obj)
 
-        trace(f'<SVMUtil> - cast_to({hex(cls.get_adr(obj))}, {t})')
+        trace(f'<SVMUtil> - cast_to({hex(self.get_adr(obj))}, {t})')
         if t.code != gdb.TYPE_CODE_PTR:
             t = t.pointer()
 
-        trace(f'<SVMUtil> - cast_to({hex(cls.get_adr(obj))}, {t}) returned')
+        trace(f'<SVMUtil> - cast_to({hex(self.get_adr(obj))}, {t}) returned')
         # just use the raw pointer value and cast it instead the obj
         # casting the obj directly results in issues with compressed oops
         return obj if t == obj.type else gdb.Value(obj_oop).cast(t)
 
-    @classmethod
-    def get_symbol_adr(cls, symbol: str) -> int:
-        trace(f'<SVMUtil> - get_symbol_adr({symbol})')
-        return gdb.parse_and_eval(symbol).address
-
-    @classmethod
-    def execout(cls, cmd: str) -> str:
-        trace(f'<SVMUtil> - execout({cmd})')
-        return gdb.execute(cmd, False, True)
-
-    @classmethod
-    def get_basic_type(cls, t: gdb.Type) -> gdb.Type:
-        trace(f'<SVMUtil> - get_basic_type({t})')
-        while t.code == gdb.TYPE_CODE_PTR:
-            t = t.target()
-        return t
-
-    @classmethod
-    def is_primitive(cls, t: gdb.Type) -> bool:
-        result = cls.get_basic_type(t).is_scalar
-        trace(f'<SVMUtil> - is_primitive({t}) = {result}')
+    def get_uncompressed_type(self, t: gdb.Type) -> gdb.Type:
+        # compressed types only exist for java type which are either struct or union
+        if t.code != gdb.TYPE_CODE_STRUCT and t.code != gdb.TYPE_CODE_UNION:
+            return t
+        result = self.get_base_class(t) if SVMUtil.is_compressed(t) else t
+        trace(f'<SVMUtil> - get_uncompressed_type({t}) = {result}')
         return result
 
-    @classmethod
-    def is_primitive_wrapper(cls, t: gdb.Type) -> bool:
-        result = t in cls.wrapper_types
+    def is_primitive_wrapper(self, t: gdb.Type) -> bool:
+        result = t in self.wrapper_types
         trace(f'<SVMUtil> - is_primitive_wrapper({t}) = {result}')
         return result
 
-    @classmethod
-    def is_enum_type(cls, t: gdb.Type) -> bool:
-        return cls.get_base_class(t) == cls.enum_type
+    def get_base_class(self, t: gdb.Type) -> gdb.Type:
+        return t if t == self.object_type else \
+            next((f.type for f in t.fields() if f.is_base_class), self.object_type)
 
-    @classmethod
-    def get_base_class(cls, t: gdb.Type) -> gdb.Type:
-        return t if t == cls.object_type else \
-            next((f.type for f in t.fields() if f.is_base_class), cls.object_type)
-
-    @classmethod
-    def find_shared_types(cls, type_list: list, t: gdb.Type) -> list:  # list[gdb.Type]
+    def find_shared_types(self, type_list: list, t: gdb.Type) -> list:  # list[gdb.Type]
         if len(type_list) == 0:
-            while t != cls.object_type:
+            while t != self.object_type:
                 type_list += [t]
-                t = cls.get_base_class(t)
+                t = self.get_base_class(t)
             return type_list
         else:
-            while t != cls.object_type:
+            while t != self.object_type:
                 if t in type_list:
                     return type_list[type_list.index(t):]
-                t = cls.get_base_class(t)
-            return [cls.object_type]
-
-    @classmethod
-    def get_all_fields(cls, t: gdb.Type, include_static: bool) -> list:  # list[gdb.Field]:
-        t = cls.get_basic_type(t)
-        while t.code == gdb.TYPE_CODE_TYPEDEF:
-            t = t.target()
-            t = cls.get_basic_type(t)
-        if t.code != gdb.TYPE_CODE_STRUCT and t.code != gdb.TYPE_CODE_UNION:
-            return []
-        for f in t.fields():
-            if not include_static:
-                try:
-                    f.bitpos  # bitpos attribute is not available for static fields
-                except AttributeError:  # use bitpos access exception to skip static fields
-                    continue
-            if f.is_base_class:
-                yield from cls.get_all_fields(f.type, include_static)
-            else:
-                yield f
-
-    @classmethod
-    def get_all_member_functions(cls, t: gdb.Type, include_static: bool, include_constructor: bool) -> set:  # set[Function]:
-        syms = set()
-        try:
-            basic_type = cls.get_basic_type(t)
-            type_name = basic_type.name
-            members = cls.execout(f"ptype '{type_name}'")
-            for member in members.split('\n'):
-                parts = member.strip().split(' ')
-                is_static = parts[0] == 'static'
-                if not include_static and is_static:
-                    continue
-                for part in parts:
-                    if '(' in part:
-                        func_name = part[:part.find('(')]
-                        if include_constructor or func_name != cls.get_unqualified_type_name(type_name):
-                            sym = gdb.lookup_global_symbol(f"{type_name}::{func_name}")
-                            # check if symbol exists and is a function
-                            if sym is not None and sym.type.code == gdb.TYPE_CODE_FUNC:
-                                syms.add(Function(is_static, func_name, sym))
-                        break
-            for f in basic_type.fields():
-                if f.is_base_class:
-                    syms = syms.union(cls.get_all_member_functions(f.type, include_static, include_constructor))
-        except Exception as ex:
-            trace(f'<SVMUtil> - get_all_member_function_names({t}) exception: {ex}')
-        return syms
+                t = self.get_base_class(t)
+            return [self.object_type]
 
-    @classmethod
-    def is_java_type(cls, t: gdb.Type) -> bool:
-        t = cls.get_uncompressed_type(cls.get_basic_type(t))
+    def is_java_type(self, t: gdb.Type) -> bool:
+        t = self.get_uncompressed_type(SVMUtil.get_basic_type(t))
         # Check for existing ".class" symbol (which exists for every java type in a native image)
         # a java class is represented by a struct, interfaces are represented by a union
         # only structs contain a "hub" field, thus just checking for a hub field does not work for interfaces
         result = ((t.code == gdb.TYPE_CODE_UNION and gdb.lookup_global_symbol(t.name + '.class', gdb.SYMBOL_VAR_DOMAIN) is not None) or
-                  (t.code == gdb.TYPE_CODE_STRUCT and gdb.types.has_field(t, cls.hub_field_name)))
+                  (t.code == gdb.TYPE_CODE_STRUCT and gdb.types.has_field(t, SVMUtil.hub_field_name)))
 
         trace(f'<SVMUtil> - is_java_obj({t}) = {result}')
         return result
 
+    # returns the compressed variant of t if available, otherwise returns the basic type of t (without pointers)
+    def get_compressed_type(self, t: gdb.Type) -> gdb.Type:
+        t = SVMUtil.get_basic_type(t)
+        # compressed types only exist for java types which are either struct or union
+        # do not compress types that already have the compressed prefix
+        if not self.is_java_type(t) or SVMUtil.is_compressed(t):
+            return t
+
+        type_name = t.name
+        # java types only contain '::' if there is a classloader namespace
+        if '::' in type_name:
+            loader_namespace, _, type_name = type_name.partition('::')
+            type_name = loader_namespace + '::' + SVMUtil.compressed_ref_prefix + type_name
+        else:
+            type_name = SVMUtil.compressed_ref_prefix + type_name
+
+        try:
+            result_type = gdb.lookup_type(type_name)
+            trace(f'<SVMUtil> - could not find compressed type "{type_name}" using uncompressed type')
+        except gdb.error as ex:
+            trace(ex)
+            result_type = t
+
+        trace(f'<SVMUtil> - get_compressed_type({t}) = {t.name}')
+        return result_type
+
 
 class SVMPPString:
-    def __init__(self, obj: gdb.Value, java: bool = True):
-        trace(f'<SVMPPString> - __init__({hex(SVMUtil.get_adr(obj))})')
+    def __init__(self, svm_util: SVMUtil, obj: gdb.Value, java: bool = True):
+        trace(f'<SVMPPString> - __init__({hex(svm_util.get_adr(obj))})')
         self.__obj = obj
         self.__java = java
+        self.__svm_util = svm_util
 
     def to_string(self) -> str:
         trace('<SVMPPString> - to_string')
         if self.__java:
             try:
-                value = '"' + SVMUtil.get_java_string(self.__obj, True) + '"'
+                value = '"' + self.__svm_util.get_java_string(self.__obj, True) + '"'
             except gdb.error:
                 return SVMPPConst(None)
         else:
             value = str(self.__obj)
             value = value[value.index('"'):]
         if svm_print_address.with_adr:
-            value += SVMUtil.adr_str(self.__obj)
+            value += self.__svm_util.adr_str(self.__obj)
         trace(f'<SVMPPString> - to_string = {value}')
         return value
 
 
 class SVMPPArray:
-    def __init__(self, obj: gdb.Value, java_array: bool = True):
-        trace(f'<SVMPPArray> - __init__(obj={obj.type} @ {hex(SVMUtil.get_adr(obj))}, java_array={java_array})')
+    def __init__(self, svm_util: SVMUtil, obj: gdb.Value, java_array: bool = True):
+        trace(f'<SVMPPArray> - __init__(obj={obj.type} @ {hex(svm_util.get_adr(obj))}, java_array={java_array})')
         if java_array:
-            self.__length = SVMUtil.get_int_field(obj, 'len')
-            self.__array = SVMUtil.get_obj_field(obj, 'data', None)
-            if SVMUtil.is_null(self.__array):
+            self.__length = svm_util.get_int_field(obj, 'len')
+            self.__array = svm_util.get_obj_field(obj, 'data', None)
+            if svm_util.is_null(self.__array):
                 self.__array = None
         else:
             self.__length = obj.type.range()[-1] + 1
             self.__array = obj
         self.__obj = obj
         self.__java_array = java_array
-        self.__skip_children = SVMUtil.is_selfref(obj) or 0 <= svm_print_depth_limit.value <= SVMUtil.current_print_depth
+        self.__skip_children = svm_util.is_selfref(obj) or 0 <= svm_print_depth_limit.value <= SVMUtil.current_print_depth
         if not self.__skip_children:
             SVMUtil.current_print_depth += 1
+        self.__svm_util = svm_util
 
     def display_hint(self) -> str:
         trace('<SVMPPArray> - display_hint = array')
@@ -630,15 +619,15 @@ def display_hint(self) -> str:
     def to_string(self) -> str:
         trace('<SVMPPArray> - to_string')
         if self.__java_array:
-            rtt = SVMUtil.get_rtt(self.__obj)
-            value = str(SVMUtil.get_uncompressed_type(rtt))
+            rtt = self.__svm_util.get_rtt(self.__obj)
+            value = str(self.__svm_util.get_uncompressed_type(rtt))
             value = value.replace('[]', f'[{self.__length}]')
         else:
             value = str(self.__obj.type)
         if self.__skip_children:
             value += ' = {...}'
         if svm_print_address.with_adr:
-            value += SVMUtil.adr_str(self.__obj)
+            value += self.__svm_util.adr_str(self.__obj)
         trace(f'<SVMPPArray> - to_string = {value}')
         return value
 
@@ -658,22 +647,23 @@ def children(self) -> Iterable[object]:
                 yield str(index), '...'
                 return
             trace(f'<SVMPPArray> - children[{index}]')
-            yield str(index), SVMUtil.add_selfref(self.__obj, elem)
+            yield str(index), self.__svm_util.add_selfref(self.__obj, elem)
         SVMUtil.current_print_depth -= 1
 
 
 class SVMPPClass:
-    def __init__(self, obj: gdb.Value, java_class: bool = True):
-        trace(f'<SVMPPClass> - __init__({obj.type} @ {hex(SVMUtil.get_adr(obj))})')
+    def __init__(self, svm_util: SVMUtil, obj: gdb.Value, java_class: bool = True):
+        trace(f'<SVMPPClass> - __init__({obj.type} @ {hex(svm_util.get_adr(obj))})')
         self.__obj = obj
         self.__java_class = java_class
-        self.__skip_children = SVMUtil.is_selfref(obj) or 0 <= svm_print_depth_limit.value <= SVMUtil.current_print_depth
+        self.__skip_children = svm_util.is_selfref(obj) or 0 <= svm_print_depth_limit.value <= SVMUtil.current_print_depth
         if not self.__skip_children:
             SVMUtil.current_print_depth += 1
+        self.__svm_util = svm_util
 
     def __getitem__(self, key: str) -> gdb.Value:
         trace(f'<SVMPPClass> - __getitem__({key})')
-        item = SVMUtil.get_obj_field(self.__obj, key, None)
+        item = self.__svm_util.get_obj_field(self.__obj, key, None)
         if item is None:
             return None
         pp_item = gdb.default_visualizer(item)
@@ -683,14 +673,14 @@ def to_string(self) -> str:
         trace('<SVMPPClass> - to_string')
         try:
             if self.__java_class:
-                rtt = SVMUtil.get_rtt(self.__obj)
-                result = SVMUtil.get_uncompressed_type(rtt).name
+                rtt = self.__svm_util.get_rtt(self.__obj)
+                result = self.__svm_util.get_uncompressed_type(rtt).name
             else:
                 result = "object" if self.__obj.type.name is None else self.__obj.type.name
             if self.__skip_children:
                 result += ' = {...}'
             if svm_print_address.with_adr:
-                result += SVMUtil.adr_str(self.__obj)
+                result += self.__svm_util.adr_str(self.__obj)
             trace(f'<SVMPPClass> - to_string = {result}')
             return result
         except gdb.error as ex:
@@ -708,35 +698,37 @@ def children(self) -> Iterable[object]:
             if self.__java_class and 0 <= svm_print_field_limit.value <= index:
                 yield f, '...'
                 return
-            yield f, SVMUtil.add_selfref(self.__obj, self.__obj[f])
+            yield f, self.__svm_util.add_selfref(self.__obj, self.__obj[f])
         SVMUtil.current_print_depth -= 1
 
 
 class SVMPPEnum:
-    def __init__(self, obj: gdb.Value):
-        trace(f'<SVMPPEnum> - __init__({hex(SVMUtil.get_adr(obj))})')
+    def __init__(self, svm_util: SVMUtil, obj: gdb.Value):
+        trace(f'<SVMPPEnum> - __init__({hex(svm_util.get_adr(obj))})')
         self.__obj = obj
-        self.__name = SVMUtil.get_obj_field(self.__obj, 'name', "")
-        self.__ordinal = SVMUtil.get_int_field(self.__obj, 'ordinal', None)
+        self.__name = svm_util.get_obj_field(self.__obj, 'name', "")
+        self.__ordinal = svm_util.get_int_field(self.__obj, 'ordinal', None)
+        self.__svm_util = svm_util
 
     def to_string(self) -> str:
-        result = SVMUtil.get_java_string(self.__name) + f"({self.__ordinal})"
+        result = self.__svm_util.get_java_string(self.__name) + f"({self.__ordinal})"
         if svm_print_address.with_adr:
-            result += SVMUtil.adr_str(self.__obj)
+            result += self.__svm_util.adr_str(self.__obj)
         trace(f'<SVMPPEnum> - to_string = {result}')
         return result
 
 
 class SVMPPBoxedPrimitive:
-    def __init__(self, obj: gdb.Value):
-        trace(f'<SVMPPBoxedPrimitive> - __init__({obj.type} @ {hex(SVMUtil.get_adr(obj))})')
+    def __init__(self, svm_util: SVMUtil, obj: gdb.Value):
+        trace(f'<SVMPPBoxedPrimitive> - __init__({obj.type} @ {hex(svm_util.get_adr(obj))})')
         self.__obj = obj
-        self.__value = SVMUtil.get_obj_field(self.__obj, 'value', obj.type.name)
+        self.__value = svm_util.get_obj_field(self.__obj, 'value', obj.type.name)
+        self.__svm_util = svm_util
 
     def to_string(self) -> str:
         result = str(self.__value)
         if svm_print_address.with_adr:
-            result += SVMUtil.adr_str(self.__obj)
+            result += self.__svm_util.adr_str(self.__obj)
         trace(f'<SVMPPBoxedPrimitive> - to_string = {result}')
         return result
 
@@ -753,52 +745,55 @@ def to_string(self) -> str:
 
 
 class SVMPrettyPrinter(gdb.printing.PrettyPrinter):
-    def __init__(self):
+    def __init__(self, svm_util: SVMUtil):
         super().__init__(SVMUtil.pretty_printer_name)
+        self.enum_type = gdb.lookup_type("java.lang.Enum")
+        self.string_type = gdb.lookup_type("java.lang.String")
+        self.svm_util = svm_util
 
     def __call__(self, obj: gdb.Value):
-        trace(f'<SVMPrettyPrinter> - __call__({obj.type} @ {hex(SVMUtil.get_adr(obj))})')
+        trace(f'<SVMPrettyPrinter> - __call__({obj.type} @ {hex(self.svm_util.get_adr(obj))})')
 
-        if not SVMUtil.is_primitive(obj.type) and SVMUtil.is_java_type(obj.type):
+        if not SVMUtil.is_primitive(obj.type) and self.svm_util.is_java_type(obj.type):
             # Filter out references to the null literal
-            if SVMUtil.is_null(obj):
+            if self.svm_util.is_null(obj):
                 return SVMPPConst(None)
 
-            rtt = SVMUtil.get_rtt(obj)
-            uncompressed_rtt = SVMUtil.get_uncompressed_type(rtt)
-            obj = SVMUtil.cast_to(obj, rtt)
+            rtt = self.svm_util.get_rtt(obj)
+            uncompressed_rtt = self.svm_util.get_uncompressed_type(rtt)
+            obj = self.svm_util.cast_to(obj, rtt)
 
             # filter for primitive wrappers
-            if SVMUtil.is_primitive_wrapper(uncompressed_rtt):
-                return SVMPPBoxedPrimitive(obj)
+            if self.svm_util.is_primitive_wrapper(uncompressed_rtt):
+                return SVMPPBoxedPrimitive(self.svm_util, obj)
 
             # filter for strings
-            if uncompressed_rtt == SVMUtil.string_type:
-                return SVMPPString(obj)
+            if uncompressed_rtt == self.string_type:
+                return SVMPPString(self.svm_util, obj)
 
             # filter for arrays
             if uncompressed_rtt.name.endswith("[]"):
-                return SVMPPArray(obj)
+                return SVMPPArray(self.svm_util, obj)
 
             # filter for enum values
-            if SVMUtil.is_enum_type(uncompressed_rtt):
-                return SVMPPEnum(obj)
+            if self.svm_util.get_base_class(uncompressed_rtt) == self.enum_type:
+                return SVMPPEnum(self.svm_util, obj)
 
             # Any other Class ...
             if svm_use_hlrep.value:
-                pp = make_high_level_object(obj, uncompressed_rtt.name)
+                pp = make_high_level_object(self.svm_util, obj, uncompressed_rtt.name)
             else:
-                pp = SVMPPClass(obj)
+                pp = SVMPPClass(self.svm_util, obj)
             return pp
 
         # no complex java type -> handle foreign types for selfref checks
         elif obj.type.code == gdb.TYPE_CODE_PTR and obj.type.target().code != gdb.TYPE_CODE_VOID:
             # Filter out references to the null literal
-            if SVMUtil.is_null(obj):
+            if self.svm_util.is_null(obj):
                 return SVMPPConst(None)
             return self.__call__(obj.dereference())
         elif obj.type.code == gdb.TYPE_CODE_ARRAY:
-            return SVMPPArray(obj, False)
+            return SVMPPArray(self.svm_util, obj, False)
         elif obj.type.code == gdb.TYPE_CODE_TYPEDEF:
             # try to expand foreign c structs
             try:
@@ -807,7 +802,7 @@ def __call__(self, obj: gdb.Value):
             except gdb.error as err:
                 return None
         elif obj.type.code == gdb.TYPE_CODE_STRUCT:
-            return SVMPPClass(obj, False)
+            return SVMPPClass(self.svm_util, obj, False)
         elif SVMUtil.is_primitive(obj.type):
             if obj.type.name == "char" and obj.type.sizeof == 2:
                 return SVMPPConst(repr(chr(obj)))
@@ -831,20 +826,21 @@ def HLRep(original_class):
 class ArrayList:
     target_type = 'java.util.ArrayList'
 
-    def __init__(self, obj: gdb.Value):
-        trace(f'<ArrayList> - __init__({obj.type} @ {hex(SVMUtil.get_adr(obj))})')
-        self.__size = SVMUtil.get_int_field(obj, 'size')
-        element_data = SVMUtil.get_obj_field(obj, 'elementData')
-        if SVMUtil.is_null(element_data):
+    def __init__(self, svm_util: SVMUtil, obj: gdb.Value):
+        trace(f'<ArrayList> - __init__({obj.type} @ {hex(svm_util.get_adr(obj))})')
+        self.__size = svm_util.get_int_field(obj, 'size')
+        element_data = svm_util.get_obj_field(obj, 'elementData')
+        if svm_util.is_null(element_data):
             self.__data = None
         else:
-            self.__data = SVMUtil.get_obj_field(element_data, 'data', None)
-            if self.__data is not None and SVMUtil.is_null(self.__data):
+            self.__data = svm_util.get_obj_field(element_data, 'data', None)
+            if self.__data is not None and svm_util.is_null(self.__data):
                 self.__data = None
         self.__obj = obj
-        self.__skip_children = SVMUtil.is_selfref(obj) or 0 <= svm_print_depth_limit.value <= SVMUtil.current_print_depth
+        self.__skip_children = svm_util.is_selfref(obj) or 0 <= svm_print_depth_limit.value <= SVMUtil.current_print_depth
         if not self.__skip_children:
             SVMUtil.current_print_depth += 1
+        self.__svm_util = svm_util
 
     def to_string(self) -> str:
         trace('<ArrayList> - to_string')
@@ -857,7 +853,7 @@ def to_string(self) -> str:
         if self.__skip_children:
             res += ' = {...}'
         if svm_print_address.with_adr:
-            res += SVMUtil.adr_str(self.__obj)
+            res += self.__svm_util.adr_str(self.__obj)
         trace(f'<ArrayList> - to_string = {res}')
         return res
 
@@ -865,9 +861,9 @@ def infer_generic_types(self) -> str:
         elem_type: list = []  # list[gdb.Type]
 
         for i, elem in enumerate(self, 1):
-            if not SVMUtil.is_null(elem):  # check for null values
-                elem_type = SVMUtil.find_shared_types(elem_type, SVMUtil.get_rtt(elem))
-            if (len(elem_type) > 0 and elem_type[0] == SVMUtil.object_type) or (0 <= svm_infer_generics.value <= i):
+            if not self.__svm_util.is_null(elem):  # check for null values
+                elem_type = self.__svm_util.find_shared_types(elem_type, self.__svm_util.get_rtt(elem))
+            if (len(elem_type) > 0 and elem_type[0] == self.__svm_util.object_type) or (0 <= svm_infer_generics.value <= i):
                 break
 
         return None if len(elem_type) == 0 else SVMUtil.get_unqualified_type_name(elem_type[0].name)
@@ -883,15 +879,15 @@ def __iter__(self) -> gdb.Value:
                 yield self.__data[i]
 
     def children(self) -> Iterable[object]:
-        trace(f'<ArrayList> - children({self.__obj.type} @ {hex(SVMUtil.get_adr(self.__obj))})')
+        trace(f'<ArrayList> - children({self.__obj.type} @ {hex(self.__svm_util.get_adr(self.__obj))})')
         if self.__skip_children:
             return
         for index, elem in enumerate(self):
             if 0 <= svm_print_element_limit.value <= index:
                 yield str(index), '...'
                 return
-            trace(f'<ArrayList> - children({self.__obj.type} @ {hex(SVMUtil.get_adr(self.__obj))})[{index}]')
-            yield str(index), SVMUtil.add_selfref(self.__obj, elem)
+            trace(f'<ArrayList> - children({self.__obj.type} @ {hex(self.__svm_util.get_adr(self.__obj))})[{index}]')
+            yield str(index), self.__svm_util.add_selfref(self.__obj, elem)
         SVMUtil.current_print_depth -= 1
 
 
@@ -899,24 +895,25 @@ def children(self) -> Iterable[object]:
 class HashMap:
     target_type = 'java.util.HashMap'
 
-    def __init__(self, obj: gdb.Value):
-        trace(f'<HashMap> - __init__({obj.type} @ {hex(SVMUtil.get_adr(obj))})')
+    def __init__(self, svm_util: SVMUtil, obj: gdb.Value):
+        trace(f'<HashMap> - __init__({obj.type} @ {hex(svm_util.get_adr(obj))})')
 
-        self.__size = SVMUtil.get_int_field(obj, 'size')
-        table = SVMUtil.get_obj_field(obj, 'table')
-        if SVMUtil.is_null(table):
+        self.__size = svm_util.get_int_field(obj, 'size')
+        table = svm_util.get_obj_field(obj, 'table')
+        if svm_util.is_null(table):
             self.__data = None
             self.__table_len = 0
         else:
-            self.__data = SVMUtil.get_obj_field(table, 'data', None)
-            if self.__data is not None and SVMUtil.is_null(self.__data):
+            self.__data = svm_util.get_obj_field(table, 'data', None)
+            if self.__data is not None and svm_util.is_null(self.__data):
                 self.__data = None
-            self.__table_len = SVMUtil.get_int_field(table, 'len')
+            self.__table_len = svm_util.get_int_field(table, 'len')
 
         self.__obj = obj
-        self.__skip_children = SVMUtil.is_selfref(obj) or 0 <= svm_print_depth_limit.value <= SVMUtil.current_print_depth
+        self.__skip_children = svm_util.is_selfref(obj) or 0 <= svm_print_depth_limit.value <= SVMUtil.current_print_depth
         if not self.__skip_children:
             SVMUtil.current_print_depth += 1
+        self.__svm_util = svm_util
 
     def to_string(self) -> str:
         trace('<HashMap> - to_string')
@@ -928,7 +925,7 @@ def to_string(self) -> str:
         if self.__skip_children:
             res += ' = {...}'
         if svm_print_address.with_adr:
-            res += SVMUtil.adr_str(self.__obj)
+            res += self.__svm_util.adr_str(self.__obj)
         trace(f'<HashMap> - to_string = {res}')
         return res
 
@@ -939,12 +936,12 @@ def infer_generic_types(self) -> tuple:  # (str, str):
         for i, kv in enumerate(self, 1):
             key, value = kv
             # if len(*_type) = 1 we could just infer the type java.lang.Object, ignore null values
-            if not SVMUtil.is_null(key) and (len(key_type) == 0 or key_type[0] != SVMUtil.object_type):
-                key_type = SVMUtil.find_shared_types(key_type, SVMUtil.get_rtt(key))
-            if not SVMUtil.is_null(value) and (len(value_type) == 0 or value_type[0] != SVMUtil.object_type):
-                value_type = SVMUtil.find_shared_types(value_type, SVMUtil.get_rtt(value))
-            if (0 <= svm_infer_generics.value <= i) or (len(key_type) > 0 and key_type[0] == SVMUtil.object_type and
-                                                       len(value_type) > 0 and value_type[0] == SVMUtil.object_type):
+            if not self.__svm_util.is_null(key) and (len(key_type) == 0 or key_type[0] != self.__svm_util.object_type):
+                key_type = self.__svm_util.find_shared_types(key_type, self.__svm_util.get_rtt(key))
+            if not self.__svm_util.is_null(value) and (len(value_type) == 0 or value_type[0] != self.__svm_util.object_type):
+                value_type = self.__svm_util.find_shared_types(value_type, self.__svm_util.get_rtt(value))
+            if (0 <= svm_infer_generics.value <= i) or (len(key_type) > 0 and key_type[0] == self.__svm_util.object_type and
+                                                       len(value_type) > 0 and value_type[0] == self.__svm_util.object_type):
                 break
 
         key_type_name = '?' if len(key_type) == 0 else SVMUtil.get_unqualified_type_name(key_type[0].name)
@@ -960,23 +957,23 @@ def __iter__(self) -> tuple:  # (gdb.Value, gdb.Value):
         trace('<HashMap> - __iter__')
         for i in range(self.__table_len):
             obj = self.__data[i]
-            while not SVMUtil.is_null(obj):
-                key = SVMUtil.get_obj_field(obj, 'key')
-                value = SVMUtil.get_obj_field(obj, 'value')
+            while not self.__svm_util.is_null(obj):
+                key = self.__svm_util.get_obj_field(obj, 'key')
+                value = self.__svm_util.get_obj_field(obj, 'value')
                 yield key, value
-                obj = SVMUtil.get_obj_field(obj, 'next')
+                obj = self.__svm_util.get_obj_field(obj, 'next')
 
     def children(self) -> Iterable[object]:
-        trace(f'<HashMap> - children({self.__obj.type} @ {hex(SVMUtil.get_adr(self.__obj))})')
+        trace(f'<HashMap> - children({self.__obj.type} @ {hex(self.__svm_util.get_adr(self.__obj))})')
         if self.__skip_children:
             return
         for index, (key, value) in enumerate(self):
             if 0 <= svm_print_element_limit.value <= index:
                 yield str(index), '...'
                 return
-            trace(f'<HashMap> - children({self.__obj.type} @ {hex(SVMUtil.get_adr(self.__obj))})[{index}]')
-            yield f"key{index}", SVMUtil.add_selfref(self.__obj, key)
-            yield f"value{index}", SVMUtil.add_selfref(self.__obj, value)
+            trace(f'<HashMap> - children({self.__obj.type} @ {hex(self.__svm_util.get_adr(self.__obj))})[{index}]')
+            yield f"key{index}", self.__svm_util.add_selfref(self.__obj, key)
+            yield f"value{index}", self.__svm_util.add_selfref(self.__obj, value)
         SVMUtil.current_print_depth -= 1
 
 
@@ -984,24 +981,25 @@ def children(self) -> Iterable[object]:
 class EconomicMapImpl:
     target_type = 'org.graalvm.collections.EconomicMapImpl'
 
-    def __init__(self, obj: gdb.Value):
-        trace(f'<EconomicMapImpl> - __init__({obj.type} @ {hex(SVMUtil.get_adr(obj))})')
+    def __init__(self, svm_util: SVMUtil, obj: gdb.Value):
+        trace(f'<EconomicMapImpl> - __init__({obj.type} @ {hex(svm_util.get_adr(obj))})')
 
-        self.__size = SVMUtil.get_int_field(obj, 'totalEntries') - SVMUtil.get_int_field(obj, 'deletedEntries')
-        entries = SVMUtil.get_obj_field(obj, 'entries')
-        if SVMUtil.is_null(entries):
+        self.__size = svm_util.get_int_field(obj, 'totalEntries') - svm_util.get_int_field(obj, 'deletedEntries')
+        entries = svm_util.get_obj_field(obj, 'entries')
+        if svm_util.is_null(entries):
             self.__data = None
             self.__array_len = 0
         else:
-            self.__data = SVMUtil.get_obj_field(entries, 'data', None)
-            if self.__data is not None and SVMUtil.is_null(self.__data):
+            self.__data = svm_util.get_obj_field(entries, 'data', None)
+            if self.__data is not None and svm_util.is_null(self.__data):
                 self.__data = None
-            self.__array_len = SVMUtil.get_int_field(entries, 'len')
+            self.__array_len = svm_util.get_int_field(entries, 'len')
 
         self.__obj = obj
-        self.__skip_children = SVMUtil.is_selfref(obj) or 0 <= svm_print_depth_limit.value <= SVMUtil.current_print_depth
+        self.__skip_children = svm_util.is_selfref(obj) or 0 <= svm_print_depth_limit.value <= SVMUtil.current_print_depth
         if not self.__skip_children:
             SVMUtil.current_print_depth += 1
+        self.__svm_util = svm_util
 
     def to_string(self) -> str:
         trace('<EconomicMapImpl> - to_string')
@@ -1013,7 +1011,7 @@ def to_string(self) -> str:
         if self.__skip_children:
             res += ' = {...}'
         if svm_print_address.with_adr:
-            res += SVMUtil.adr_str(self.__obj)
+            res += self.__svm_util.adr_str(self.__obj)
         trace(f'<EconomicMapImpl> - to_string = {res}')
         return res
 
@@ -1024,12 +1022,12 @@ def infer_generic_types(self) -> tuple:  # (str, str):
         for i, kv in enumerate(self, 1):
             key, value = kv
             # if len(*_type) = 1 we could just infer the type java.lang.Object, ignore null values
-            if not SVMUtil.is_null(key) and (len(key_type) == 0 or key_type[0] != SVMUtil.object_type):
-                key_type = SVMUtil.find_shared_types(key_type, SVMUtil.get_rtt(key))
-            if not SVMUtil.is_null(value) and (len(value_type) == 0 or value_type[0] != SVMUtil.object_type):
-                value_type = SVMUtil.find_shared_types(value_type, SVMUtil.get_rtt(value))
-            if (0 <= svm_infer_generics.value <= i) or (len(key_type) > 0 and key_type[0] == SVMUtil.object_type and
-                                                        len(value_type) > 0 and value_type[0] == SVMUtil.object_type):
+            if not self.__svm_util.is_null(key) and (len(key_type) == 0 or key_type[0] != self.__svm_util.object_type):
+                key_type = self.__svm_util.find_shared_types(key_type, self.__svm_util.get_rtt(key))
+            if not self.__svm_util.is_null(value) and (len(value_type) == 0 or value_type[0] != self.__svm_util.object_type):
+                value_type = self.__svm_util.find_shared_types(value_type, self.__svm_util.get_rtt(value))
+            if (0 <= svm_infer_generics.value <= i) or (len(key_type) > 0 and key_type[0] == self.__svm_util.object_type and
+                                                        len(value_type) > 0 and value_type[0] == self.__svm_util.object_type):
                 break
 
         key_type_name = '?' if len(key_type) == 0 else SVMUtil.get_unqualified_type_name(key_type[0].name)
@@ -1043,10 +1041,10 @@ def display_hint(self) -> str:
 
     def __iter__(self) -> tuple:  # (gdb.Value, gdb.Value):
         trace('<EconomicMapImpl> - __iter__')
-        key = SVMUtil.null
+        key = 0
         for i in range(self.__array_len):
             if i % 2 == 0:
-                if SVMUtil.is_null(self.__data[i]):
+                if self.__svm_util.is_null(self.__data[i]):
                     break
                 key = self.__data[i]
             else:
@@ -1054,27 +1052,27 @@ def __iter__(self) -> tuple:  # (gdb.Value, gdb.Value):
                 yield key, value
 
     def children(self) -> Iterable[object]:
-        trace(f'<EconomicMapImpl> - children({self.__obj.type} @ {hex(SVMUtil.get_adr(self.__obj))})')
+        trace(f'<EconomicMapImpl> - children({self.__obj.type} @ {hex(self.__svm_util.get_adr(self.__obj))})')
         if self.__skip_children:
             return
         for index, (key, value) in enumerate(self):
             if 0 <= svm_print_element_limit.value <= index:
                 yield str(index), '...'
                 return
-            trace(f'<EconomicMapImpl> - children({self.__obj.type} @ {hex(SVMUtil.get_adr(self.__obj))})[{index}]')
-            yield f"key{index}", SVMUtil.add_selfref(self.__obj, key)
-            yield f"value{index}", SVMUtil.add_selfref(self.__obj, value)
+            trace(f'<EconomicMapImpl> - children({self.__obj.type} @ {hex(self.__svm_util.get_adr(self.__obj))})[{index}]')
+            yield f"key{index}", self.__svm_util.add_selfref(self.__obj, key)
+            yield f"value{index}", self.__svm_util.add_selfref(self.__obj, value)
         SVMUtil.current_print_depth -= 1
 
 
-def make_high_level_object(obj: gdb.Value, rtt_name: str) -> gdb.Value:
+def make_high_level_object(svm_util: SVMUtil, obj: gdb.Value, rtt_name: str) -> gdb.Value:
     try:
         trace(f'try makeHighLevelObject for {rtt_name}')
         hl_rep_class = SVMUtil.hlreps[rtt_name]
-        return hl_rep_class(obj)
+        return hl_rep_class(svm_util, obj)
     except Exception as ex:
         trace(f'<makeHighLevelObject> exception: {ex}')
-    return SVMPPClass(obj)
+    return SVMPPClass(svm_util, obj)
 
 
 class SVMPrintParam(gdb.Parameter):
@@ -1333,16 +1331,16 @@ def __init__(self, complete):  # complete: list[str] | int
 
     def __init__(self):
         super().__init__('p', gdb.COMMAND_DATA)
+        self.svm_util = SVMUtil()
 
-    @staticmethod
-    def cast_to_rtt(obj: gdb.Value, obj_str: str) -> tuple:  # tuple[gdb.Value, str]:
+    def cast_to_rtt(self, obj: gdb.Value, obj_str: str) -> tuple:  # tuple[gdb.Value, str]:
         static_type = SVMUtil.get_basic_type(obj.type)
-        rtt = SVMUtil.get_rtt(obj)
-        obj = SVMUtil.cast_to(obj, rtt)
+        rtt = self.svm_util.get_rtt(obj)
+        obj = self.svm_util.cast_to(obj, rtt)
         if static_type.name == rtt.name:
             return obj, obj_str
         else:
-            obj_oop = SVMUtil.get_compressed_oop(obj) if SVMUtil.is_compressed(rtt) else SVMUtil.get_adr(obj)
+            obj_oop = self.svm_util.get_compressed_oop(obj) if SVMUtil.is_compressed(rtt) else self.svm_util.get_adr(obj)
             return obj, f"(('{rtt.name}' *)({obj_oop}))"
 
     # Define the token specifications
@@ -1421,8 +1419,8 @@ def object(self, completion: bool = False) -> str:
                 # could not parse obj_str as obj -> let gdb deal with it later
                 return self.t.val
             base_obj_str = obj_str
-            if not SVMUtil.is_primitive(obj.type) and SVMUtil.is_java_type(obj.type):
-                obj, obj_str = SVMCommandPrint.cast_to_rtt(obj, obj_str)
+            if not SVMUtil.is_primitive(obj.type) and self.svm_util.is_java_type(obj.type):
+                obj, obj_str = self.cast_to_rtt(obj, obj_str)
             self.cache[base_obj_str] = (obj, obj_str)
 
         while self.sym == "FA" or self.sym == "LPAREN" or self.sym == "LBRACK":
@@ -1445,8 +1443,8 @@ def object(self, completion: bool = False) -> str:
                 obj = obj[self.t.val]
                 obj_str += "." + self.t.val
                 base_obj_str = obj_str
-                if not SVMUtil.is_primitive(obj.type) and SVMUtil.is_java_type(obj.type):
-                    obj, obj_str = SVMCommandPrint.cast_to_rtt(obj, obj_str)
+                if not SVMUtil.is_primitive(obj.type) and self.svm_util.is_java_type(obj.type):
+                    obj, obj_str = self.cast_to_rtt(obj, obj_str)
                 self.cache[base_obj_str] = (obj, obj_str)
             elif self.sym == "LPAREN":
                 if obj.type.code != gdb.TYPE_CODE_METHOD:
@@ -1464,8 +1462,8 @@ def object(self, completion: bool = False) -> str:
                 obj_str += f"({param_str})"
                 obj = gdb.parse_and_eval(obj_str)
                 base_obj_str = obj_str
-                if not SVMUtil.is_primitive(obj.type) and SVMUtil.is_java_type(obj.type):
-                    obj, obj_str = SVMCommandPrint.cast_to_rtt(obj, obj_str)
+                if not SVMUtil.is_primitive(obj.type) and self.svm_util.is_java_type(obj.type):
+                    obj, obj_str = self.cast_to_rtt(obj, obj_str)
                 self.cache[base_obj_str] = (obj, obj_str)
             elif self.sym == "LBRACK":
                 is_array = obj.type.is_array_like or isinstance(gdb.default_visualizer(obj), SVMPPArray)
@@ -1477,9 +1475,9 @@ def object(self, completion: bool = False) -> str:
                 i_obj_str = self.array_index(completion)
                 if self.sym == "" and completion:
                     # handle autocompletion for array index
-                    if SVMUtil.is_java_type(obj.type) and (i_obj_str == '' or i_obj_str.isnumeric()):
+                    if self.svm_util.is_java_type(obj.type) and (i_obj_str == '' or i_obj_str.isnumeric()):
                         index = 0 if i_obj_str == '' else int(i_obj_str)
-                        length = SVMUtil.get_int_field(obj, 'len')
+                        length = self.svm_util.get_int_field(obj, 'len')
                         complete = []
                         if index < length:
                             complete.append(f'{index}]')
@@ -1495,19 +1493,19 @@ def object(self, completion: bool = False) -> str:
                 else:
                     i_obj = gdb.parse_and_eval(i_obj_str)
                 self.check('RBRACK')
-                if is_array and SVMUtil.is_java_type(obj.type):
+                if is_array and self.svm_util.is_java_type(obj.type):
                     obj_str += ".data"
-                    obj = SVMUtil.get_obj_field(obj, 'data', obj)
+                    obj = self.svm_util.get_obj_field(obj, 'data', obj)
                 if isinstance(gdb.default_visualizer(i_obj), SVMPPBoxedPrimitive) or SVMUtil.is_primitive(i_obj.type):
                     if isinstance(gdb.default_visualizer(i_obj), SVMPPBoxedPrimitive):
-                        index = SVMUtil.get_int_field(i_obj, 'value')
+                        index = self.svm_util.get_int_field(i_obj, 'value')
                     else:
                         index = int(i_obj)
                     obj_str += f"[{index}]"
                     obj = obj[index]
                     base_obj_str = obj_str
-                    if not SVMUtil.is_primitive(obj.type) and SVMUtil.is_java_type(obj.type):
-                        obj, obj_str = SVMCommandPrint.cast_to_rtt(obj, obj_str)
+                    if not SVMUtil.is_primitive(obj.type) and self.svm_util.is_java_type(obj.type):
+                        obj, obj_str = self.cast_to_rtt(obj, obj_str)
                     self.cache[base_obj_str] = (obj, obj_str)
                 else:
                     # let gdb figure out what to do
@@ -1517,8 +1515,8 @@ def object(self, completion: bool = False) -> str:
                     else:
                         obj = gdb.parse_and_eval(obj_str)
                         base_obj_str = obj_str
-                        if not SVMUtil.is_primitive(obj.type) and SVMUtil.is_java_type(obj.type):
-                            obj, obj_str = SVMCommandPrint.cast_to_rtt(obj, obj_str)
+                        if not SVMUtil.is_primitive(obj.type) and self.svm_util.is_java_type(obj.type):
+                            obj, obj_str = self.cast_to_rtt(obj, obj_str)
                         self.cache[base_obj_str] = (obj, obj_str)
 
         if isinstance(gdb.default_visualizer(obj), SVMPPBoxedPrimitive):
@@ -1538,9 +1536,9 @@ def params(self, completion: bool = False) -> str:
                     obj_str += self.t.val
 
             obj = gdb.parse_and_eval(obj_str)  # check if gdb can handle the current param
-            if SVMUtil.is_java_type(obj.type) and SVMUtil.is_compressed(obj.type):
+            if self.svm_util.is_java_type(obj.type) and SVMUtil.is_compressed(obj.type):
                 # uncompress compressed java params
-                obj_str = f"(('{SVMUtil.get_uncompressed_type(SVMUtil.get_basic_type(obj.type)).name}' *)({SVMUtil.get_adr(obj)}))"
+                obj_str = f"(('{self.svm_util.get_uncompressed_type(SVMUtil.get_basic_type(obj.type)).name}' *)({self.svm_util.get_adr(obj)}))"
             param_str += obj_str
             if self.sym == "COMMA":
                 self.scan()
@@ -1564,6 +1562,8 @@ def array_index(self, completion: bool = False) -> str:
         return i_obj_str
 
     def complete(self, text: str, word: str):  # -> list[str] | int:
+        self.svm_util = SVMUtil()
+
         if not svm_print.value:
             return gdb.COMPLETE_EXPRESSION
 
@@ -1577,6 +1577,8 @@ def complete(self, text: str, word: str):  # -> list[str] | int:
         return gdb.COMPLETE_NONE
 
     def invoke(self, arg: str, from_tty: bool) -> None:
+        self.svm_util = SVMUtil()
+
         if not svm_print.value:
             gdb.execute(f"print {arg}")
             return
@@ -1615,31 +1617,31 @@ def invoke(self, arg: str, from_tty: bool) -> None:
 
 class SVMFrameUnwinder(gdb.unwinder.Unwinder):
 
-    def __init__(self):
+    def __init__(self, svm_util: SVMUtil):
         super().__init__('SubstrateVM FrameUnwinder')
+        self.deopt_stub_adr = 0
+        self.svm_util = svm_util
 
     def __call__(self, pending_frame: gdb.Frame):
-        if SVMUtil.deopt_stub_adr == 0:
-            # find deopt stub after its properly loaded
-            SVMUtil.deopt_stub_adr = gdb.lookup_global_symbol('com.oracle.svm.core.deopt.Deoptimizer::deoptStub',
-                                                              gdb.SYMBOL_VAR_DOMAIN).value().address
+        if self.deopt_stub_adr == 0:
+            self.deopt_stub_adr = SVMUtil.get_deopt_stub_adr()
 
         rsp = 0
         try:
             rsp = pending_frame.read_register('sp')
             rip = pending_frame.read_register('pc')
-            if int(rip) == SVMUtil.deopt_stub_adr:
-                deopt_frame_stack_slot = rsp.cast(SVMUtil.stack_type.pointer()).dereference()
-                deopt_frame = deopt_frame_stack_slot.cast(SVMUtil.get_compressed_type(SVMUtil.object_type).pointer())
-                rtt = SVMUtil.get_rtt(deopt_frame)
-                deopt_frame = SVMUtil.cast_to(deopt_frame, rtt)
-                encoded_frame_size = SVMUtil.get_int_field(deopt_frame, 'sourceEncodedFrameSize')
-                source_frame_size = encoded_frame_size & ~SVMUtil.frame_size_status_mask
+            if int(rip) == self.deopt_stub_adr:
+                deopt_frame_stack_slot = rsp.cast(self.svm_util.stack_type.pointer()).dereference()
+                deopt_frame = deopt_frame_stack_slot.cast(self.svm_util.get_compressed_type(self.svm_util.object_type).pointer())
+                rtt = self.svm_util.get_rtt(deopt_frame)
+                deopt_frame = self.svm_util.cast_to(deopt_frame, rtt)
+                encoded_frame_size = self.svm_util.get_int_field(deopt_frame, 'sourceEncodedFrameSize')
+                source_frame_size = encoded_frame_size & ~self.svm_util.frame_size_status_mask
                 # Now find the register-values for the caller frame
                 unwind_info = pending_frame.create_unwind_info(gdb.unwinder.FrameId(rsp, rip))
                 caller_rsp = rsp + int(source_frame_size)
                 unwind_info.add_saved_register(SVMUtil.get_rsp(), gdb.Value(caller_rsp))
-                caller_rip = gdb.Value(caller_rsp - 8).cast(SVMUtil.stack_type.pointer()).dereference()
+                caller_rip = gdb.Value(caller_rsp - 8).cast(self.svm_util.stack_type.pointer()).dereference()
                 unwind_info.add_saved_register(SVMUtil.get_rip(), gdb.Value(caller_rip))
                 return unwind_info
         except Exception as ex:
@@ -1650,17 +1652,22 @@ def __call__(self, pending_frame: gdb.Frame):
         return None
 
 
-class SVMFrameFilter():
-    def __init__(self):
+class SVMFrameFilter:
+    def __init__(self, svm_util: SVMUtil, deopt_stub_available: bool):
         self.name = "SubstrateVM FrameFilter"
         self.priority = 100
         self.enabled = True
+        self.deopt_stub_adr = 0 if deopt_stub_available else None
+        self.svm_util = svm_util
 
     def filter(self, frame_iter: Iterable) -> FrameDecorator:
+        if self.deopt_stub_adr == 0:
+            self.deopt_stub_adr = SVMUtil.get_deopt_stub_adr()
+
         for frame in frame_iter:
             frame = frame.inferior_frame()
-            if SVMUtil.deopt_stub_adr and frame.pc() == SVMUtil.deopt_stub_adr:
-                yield SVMFrameDeopt(frame)
+            if self.deopt_stub_adr and frame.pc() == self.deopt_stub_adr:
+                yield SVMFrameDeopt(self.svm_util, frame, self.deopt_stub_adr)
             else:
                 yield SVMFrame(frame)
 
@@ -1686,7 +1693,7 @@ def function(self) -> str:
         return func_name + eclipse_filename
 
 
-class SymValueWrapper():
+class SymValueWrapper:
 
     def __init__(self, symbol, value):
         self.sym = symbol
@@ -1701,30 +1708,31 @@ def symbol(self):
 
 class SVMFrameDeopt(SVMFrame):
 
-    def __init__(self, frame: gdb.Frame):
+    def __init__(self, svm_util: SVMUtil, frame: gdb.Frame, deopt_stub_adr: int):
         super().__init__(frame)
 
         # fetch deoptimized frame from stack
         rsp = frame.read_register('sp')
-        assert SVMUtil.deopt_stub_adr and frame.pc() == SVMUtil.deopt_stub_adr
-        deopt_frame_stack_slot = rsp.cast(SVMUtil.stack_type.pointer()).dereference()
-        deopt_frame = deopt_frame_stack_slot.cast(SVMUtil.get_compressed_type(SVMUtil.object_type).pointer())
-        rtt = SVMUtil.get_rtt(deopt_frame)
-        deopt_frame = SVMUtil.cast_to(deopt_frame, rtt)
-        self.__virtual_frame = SVMUtil.get_obj_field(deopt_frame, 'topFrame')
-        self.__frame_info = SVMUtil.get_obj_field(self.__virtual_frame, 'frameInfo')
+        assert deopt_stub_adr and frame.pc() == deopt_stub_adr
+        deopt_frame_stack_slot = rsp.cast(svm_util.stack_type.pointer()).dereference()
+        deopt_frame = deopt_frame_stack_slot.cast(svm_util.get_compressed_type(svm_util.object_type).pointer())
+        rtt = svm_util.get_rtt(deopt_frame)
+        deopt_frame = svm_util.cast_to(deopt_frame, rtt)
+        self.__virtual_frame = svm_util.get_obj_field(deopt_frame, 'topFrame')
+        self.__frame_info = svm_util.get_obj_field(self.__virtual_frame, 'frameInfo')
+        self.__svm_util = svm_util
 
     def function(self) -> str:
-        if self.__frame_info is None or SVMUtil.is_null(self.__frame_info):
+        if self.__frame_info is None or self.__svm_util.is_null(self.__frame_info):
             # we have no more information about the frame
             return '[DEOPT FRAME ...]'
 
         # read from deoptimized frame
-        source_class = SVMUtil.get_obj_field(self.__frame_info, 'sourceClass')
-        if SVMUtil.is_null(source_class):
+        source_class = self.__svm_util.get_obj_field(self.__frame_info, 'sourceClass')
+        if self.__svm_util.is_null(source_class):
             source_class_name = ''
         else:
-            source_class_name = str(SVMUtil.get_obj_field(source_class, 'name'))[1:-1]
+            source_class_name = str(self.__svm_util.get_obj_field(source_class, 'name'))[1:-1]
             if len(source_class_name) > 0:
                 source_class_name = source_class_name + '::'
 
@@ -1737,44 +1745,44 @@ def function(self) -> str:
                 source_file_name = source_file_name + ':' + str(line)
             source_file_name = '(' + source_file_name + ')'
 
-        func_name = str(SVMUtil.get_obj_field(self.__frame_info, 'sourceMethodName'))[1:-1]
+        func_name = str(self.__svm_util.get_obj_field(self.__frame_info, 'sourceMethodName'))[1:-1]
 
         return '[DEOPT FRAME] ' + source_class_name + func_name + source_file_name
 
     def filename(self):
-        if self.__frame_info is None or SVMUtil.is_null(self.__frame_info):
+        if self.__frame_info is None or self.__svm_util.is_null(self.__frame_info):
             return None
 
-        source_class = SVMUtil.get_obj_field(self.__frame_info, 'sourceClass')
+        source_class = self.__svm_util.get_obj_field(self.__frame_info, 'sourceClass')
 
-        if SVMUtil.is_null(source_class):
+        if self.__svm_util.is_null(source_class):
             source_file_name = ''
         else:
-            source_file_name = str(SVMUtil.get_obj_field(source_class, 'sourceFileName'))[1:-1]
+            source_file_name = str(self.__svm_util.get_obj_field(source_class, 'sourceFileName'))[1:-1]
 
         return source_file_name
 
     def line(self):
-        if self.__frame_info is None or SVMUtil.is_null(self.__frame_info):
+        if self.__frame_info is None or self.__svm_util.is_null(self.__frame_info):
             return None
-        return SVMUtil.get_int_field(self.__frame_info, 'sourceLineNumber')
+        return self.__svm_util.get_int_field(self.__frame_info, 'sourceLineNumber')
 
     def frame_args(self):
-        if self.__frame_info is None or SVMUtil.is_null(self.__frame_info):
+        if self.__frame_info is None or self.__svm_util.is_null(self.__frame_info):
             return None
 
-        values = SVMUtil.get_obj_field(self.__virtual_frame, 'values')
-        data = SVMUtil.get_obj_field(values, 'data')
-        length = SVMUtil.get_int_field(values, 'len')
+        values = self.__svm_util.get_obj_field(self.__virtual_frame, 'values')
+        data = self.__svm_util.get_obj_field(values, 'data')
+        length = self.__svm_util.get_int_field(values, 'len')
         args = [SymValueWrapper('deoptFrameValues', length)]
-        if SVMUtil.is_null(data) or length == 0:
+        if self.__svm_util.is_null(data) or length == 0:
             return args
 
         for i in range(length):
             elem = data[i]
-            rtt = SVMUtil.get_rtt(elem)
-            elem = SVMUtil.cast_to(elem, rtt)
-            value = SVMUtil.get_obj_field(elem, 'value')
+            rtt = self.__svm_util.get_rtt(elem)
+            elem = self.__svm_util.cast_to(elem, rtt)
+            value = self.__svm_util.get_obj_field(elem, 'value')
             args.append(SymValueWrapper(f'__{i}', value))
 
         return args
@@ -1790,35 +1798,40 @@ def frame_locals(self):
 except Exception as e:
     trace(f'<exception in svminitfile execution: {e}>')
 
+
+def register_objfile(objfile: gdb.Objfile):
+    svm_util = SVMUtil()
+    gdb.printing.register_pretty_printer(objfile, SVMPrettyPrinter(svm_util), True)
+
+    # deopt stub points to the wrong address at first -> set dummy value to fill later (0 from SVMUtil)
+    deopt_stub_available = gdb.lookup_global_symbol('com.oracle.svm.core.deopt.Deoptimizer::deoptStub',
+                                                    gdb.SYMBOL_VAR_DOMAIN) is not None
+
+    if deopt_stub_available:
+        gdb.unwinder.register_unwinder(objfile, SVMFrameUnwinder(svm_util))
+
+    frame_filter = SVMFrameFilter(svm_util, deopt_stub_available)
+    objfile.frame_filters[frame_filter.name] = frame_filter
+
+
 try:
     gdb.prompt_hook = SVMUtil.prompt_hook
     svm_objfile = gdb.current_objfile()
     # Only if we have an objfile and an SVM specific symbol we consider this an SVM objfile
     if svm_objfile and svm_objfile.lookup_global_symbol("com.oracle.svm.core.Isolates"):
-        gdb.printing.register_pretty_printer(svm_objfile, SVMPrettyPrinter(), True)
-
-        # deopt stub points to the wrong address at first -> set dummy value to fill later (0 from SVMUtil)
-        deopt_stub_available = gdb.lookup_global_symbol('com.oracle.svm.core.deopt.Deoptimizer::deoptStub',
-                                                        gdb.SYMBOL_VAR_DOMAIN)
-
-        if deopt_stub_available:
-            SVMUtil.frame_unwinder = SVMFrameUnwinder()
-            gdb.unwinder.register_unwinder(svm_objfile, SVMUtil.frame_unwinder)
-
-        SVMUtil.frame_filter = SVMFrameFilter()
-        svm_objfile.frame_filters[SVMUtil.frame_filter.name] = SVMUtil.frame_filter
+        register_objfile(svm_objfile)
     else:
         print(f'Warning: Load {os.path.basename(__file__)} only in the context of an SVM objfile')
         # fallback (e.g. if loaded manually -> look through all objfiles and attach pretty printer)
-        for of in gdb.objfiles():
-            if of.lookup_global_symbol("com.oracle.svm.core.Isolates"):
-                gdb.printing.register_pretty_printer(of, SVMPrettyPrinter(), True)
+        for objfile in gdb.objfiles():
+            if objfile.lookup_global_symbol("com.oracle.svm.core.Isolates"):
+                register_objfile(objfile)
 
     # save and restore SVM pretty printer for reloaded objfiles (e.g. shared libraries)
     def new_objectfile(new_objfile_event):
         objfile = new_objfile_event.new_objfile
         if objfile.filename in SVMUtil.pretty_print_objfiles:
-            gdb.printing.register_pretty_printer(objfile, SVMPrettyPrinter(), True)
+            register_objfile(objfile)
 
     def free_objectfile(free_objfile_event):
         objfile = free_objfile_event.objfile
diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py
index 6f214450a97b..b371ca618cdb 100644
--- a/substratevm/mx.substratevm/mx_substratevm.py
+++ b/substratevm/mx.substratevm/mx_substratevm.py
@@ -1165,14 +1165,14 @@ def run_debug_test(image_name: str, testfile: str, source_path: str, with_isolat
                 mx.run(c_command, cwd=build_dir)
         if mx.get_os() == 'linux':
             logfile = join(path, pathlib.Path(testfile).stem + ('' if with_isolates else '_no_isolates') + '.log')
-            os.environ.update({'gdbdebughelperstest_logfile': logfile})
+            os.environ.update({'gdb_logfile': logfile})
             gdb_command = gdb_args + [
                 '-iex', f"set logging file {logfile}",
                 '-iex', 'set logging enabled on',
                 '-iex', f"set auto-load safe-path {join(build_dir, 'gdb-debughelpers.py')}",
                 '-x', testfile, join(build_dir, image_name)
             ]
-            mx.log(' '.join(gdb_command))
+            mx.log(' '.join([(f"'{c}'" if ' ' in c else c) for c in gdb_command]))
             # unittest may result in different exit code, nonZeroIsFatal ensures that we can go on with other test
             return mx.run(gdb_command, cwd=build_dir, nonZeroIsFatal=False)
         return 0
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/src/com/oracle/svm/test/debug/helper/gdb_helper.py
index 3754c24380c9..3666c1f1050a 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/gdb_helper.py
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/gdb_helper.py
@@ -32,7 +32,7 @@
 
 import gdb
 
-logfile = os.environ.get('gdbdebughelperstest_logfile', 'debug_helper.log')
+logfile = os.environ.get('gdb_logfile', 'debug_helper.log')
 logging.basicConfig(filename=logfile,
                     format='%(name)s %(levelname)s: %(message)s', level=logging.DEBUG)
 logger = logging.getLogger('[DebugTest]')
@@ -100,7 +100,7 @@ def gdb_advanced_print(var: str, output_format: str = None) -> str:
 
 def gdb_set_breakpoint(location: str) -> None:
     logger.info(f"Setting breakpoint at: {location}")
-    gdb_execute(f"break {location}")
+    gdb.Breakpoint(location)
 
 
 def gdb_set_param(name: str, value: str) -> None:
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/src/com/oracle/svm/test/debug/helper/test_cinterface.py
index e0db933104e7..b736fbcd326c 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_cinterface.py
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_cinterface.py
@@ -53,6 +53,10 @@ def test_auto_load(self):
         self.assertIn("SubstrateVM", exec_string, 'pretty-printer was not loaded')
         # assume that there are no other pretty-printers were attached to an objfile
         self.assertIn("objfile", exec_string, 'pretty-printer was not attached to an objfile')
+        # check frame filter
+        exec_string = gdb_execute('info frame-filter')
+        self.assertIn('libcinterfacetutorial.so.debug frame-filters:', exec_string)
+        self.assertIn('SubstrateVM FrameFilter', exec_string)
 
     def test_manual_load(self):
         backup_auto_load_param = gdb_get_param("auto-load python-scripts")
@@ -65,6 +69,10 @@ def test_manual_load(self):
             exec_string = gdb_execute('info pretty-printer')
             self.assertIn("objfile", exec_string)  # check if any objfile has a pretty-printer
             self.assertIn("SubstrateVM", exec_string)
+            # check frame filter
+            exec_string = gdb_execute('info frame-filter')
+            self.assertIn('libcinterfacetutorial.so.debug frame-filters:', exec_string)
+            self.assertIn('SubstrateVM FrameFilter', exec_string)
         finally:
             # make sure auto-loading is re-enabled for other tests
             gdb_set_param("auto-load python-scripts", backup_auto_load_param)
@@ -80,10 +88,16 @@ def test_manual_load_without_executable(self):
 
     def test_auto_reload(self):
         gdb_start()
-        gdb_start()  # all loaded shared libraries get freed (their pretty printers are removed) and newly attached
+        # all loaded shared libraries get freed and newly attached
+        # pretty printers, frame filters and frame unwinders should also be reattached
+        gdb_start()
         exec_string = gdb_execute('info pretty-printer')
         self.assertIn("SubstrateVM", exec_string, 'pretty-printer was not loaded')
         self.assertIn("objfile", exec_string, 'pretty-printer was not attached to an objfile')
+        # check frame filter
+        exec_string = gdb_execute('info frame-filter')
+        self.assertIn('libcinterfacetutorial.so.debug frame-filters:', exec_string)
+        self.assertIn('SubstrateVM FrameFilter', exec_string)
 
 
 class TestCInterface(unittest.TestCase):
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/src/com/oracle/svm/test/debug/helper/test_class_loader.py
index 38d06d71d756..577763f9bf8e 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/src/com/oracle/svm/test/debug/helper/test_class_loader.py
@@ -72,6 +72,7 @@ def setUp(self):
         self.maxDiff = None
         set_up_test()
         set_up_gdb_debughelpers()
+        self.svm_util = SVMUtil()
 
     def tearDown(self):
         gdb_delete_breakpoints()
@@ -82,8 +83,8 @@ def test_get_classloader_namespace(self):
         gdb_run()
         this = gdb.parse_and_eval('this')  # type = com.oracle.svm.test.missing.classes.TestClass -> testClassLoader
         field = gdb.parse_and_eval('this.instanceField')  # instanceField is null
-        self.assertRegex(SVMUtil.get_classloader_namespace(this), f'testClassLoader_{hex_rexp.pattern}')
-        self.assertEqual(SVMUtil.get_classloader_namespace(field), "")
+        self.assertRegex(self.svm_util.get_classloader_namespace(this), f'testClassLoader_{hex_rexp.pattern}')
+        self.assertEqual(self.svm_util.get_classloader_namespace(field), "")
 
 
 # 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/src/com/oracle/svm/test/debug/helper/test_svm_util.py
index 0502745ac7e6..b6dd53fbec39 100644
--- 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/src/com/oracle/svm/test/debug/helper/test_svm_util.py
@@ -44,6 +44,7 @@ def setUp(self):
         self.maxDiff = None
         set_up_test()
         set_up_gdb_debughelpers()
+        self.svm_util = SVMUtil()
 
     def tearDown(self):
         gdb_delete_breakpoints()
@@ -51,27 +52,27 @@ def tearDown(self):
 
     def test_compressed_check(self):
         gdb_start()
-        self.assertTrue(SVMUtil.is_compressed(self.compressed_type))
-        self.assertFalse(SVMUtil.is_compressed(self.uncompressed_type))
+        self.assertTrue(self.svm_util.is_compressed(self.compressed_type))
+        self.assertFalse(self.svm_util.is_compressed(self.uncompressed_type))
 
     def test_uncompress(self):
         gdb_start()
-        self.assertEqual(SVMUtil.get_uncompressed_type(self.compressed_type), self.uncompressed_type)
+        self.assertEqual(self.svm_util.get_uncompressed_type(self.compressed_type), self.uncompressed_type)
 
     def test_uncompress_uncompressed_type(self):
         gdb_start()
-        self.assertEqual(SVMUtil.get_uncompressed_type(self.uncompressed_type), self.uncompressed_type)
+        self.assertEqual(self.svm_util.get_uncompressed_type(self.uncompressed_type), self.uncompressed_type)
 
     def test_compress(self):
         gdb_start()
-        self.assertEqual(SVMUtil.get_compressed_type(self.uncompressed_type), self.compressed_type)
+        self.assertEqual(self.svm_util.get_compressed_type(self.uncompressed_type), self.compressed_type)
 
     def test_compress_compressed_type(self):
         gdb_start()
-        self.assertEqual(SVMUtil.get_compressed_type(self.compressed_type), self.compressed_type)
+        self.assertEqual(self.svm_util.get_compressed_type(self.compressed_type), self.compressed_type)
 
     def test_get_unqualified_type_name(self):
-        self.assertEqual(SVMUtil.get_unqualified_type_name("classloader_name::package.name.Parent$Inner"), "Inner")
+        self.assertEqual(self.svm_util.get_unqualified_type_name("classloader_name::package.name.Parent$Inner"), "Inner")
 
 
 class TestObjUtils(unittest.TestCase):
@@ -84,6 +85,7 @@ def setUp(self):
         set_up_test()
         set_up_gdb_debughelpers()
         gdb_start()
+        self.svm_util = SVMUtil()
 
     def tearDown(self):
         gdb_delete_breakpoints()
@@ -95,12 +97,12 @@ def test_compressed_hub_oop(self):
         # get a compressed hub
         z_hub = gdb.parse_and_eval('strList.hub')
         self.assertNotEqual(int(z_hub), 0)
-        self.assertTrue(SVMUtil.is_compressed(z_hub.type))
+        self.assertTrue(self.svm_util.is_compressed(z_hub.type))
         # get the uncompressed value for the hub
         hub = z_hub.dereference()
-        hub = hub.cast(SVMUtil.get_uncompressed_type(hub.type))
-        self.assertFalse(SVMUtil.is_compressed(hub.type))
-        self.assertEqual(SVMUtil.get_compressed_oop(hub), int(z_hub))
+        hub = hub.cast(self.svm_util.get_uncompressed_type(hub.type))
+        self.assertFalse(self.svm_util.is_compressed(hub.type))
+        self.assertEqual(self.svm_util.get_compressed_oop(hub), int(z_hub))
 
         hub_str = str(hub)
         SVMUtil.prompt_hook()
@@ -114,12 +116,12 @@ def test_compressed_oop(self):
         # get a compressed object
         z_name = gdb.parse_and_eval('strList.hub.name')
         self.assertNotEqual(int(z_name), 0)
-        self.assertTrue(SVMUtil.is_compressed(z_name.type))
+        self.assertTrue(self.svm_util.is_compressed(z_name.type))
         # get the uncompressed value for the hub name
         name = z_name.dereference()
-        name = name.cast(SVMUtil.get_uncompressed_type(name.type))
-        self.assertFalse(SVMUtil.is_compressed(name.type))
-        self.assertEqual(SVMUtil.get_compressed_oop(name), int(z_name))
+        name = name.cast(self.svm_util.get_uncompressed_type(name.type))
+        self.assertFalse(self.svm_util.is_compressed(name.type))
+        self.assertEqual(self.svm_util.get_compressed_oop(name), int(z_name))
 
         name_str = str(name)
         SVMUtil.prompt_hook()
@@ -130,35 +132,35 @@ def test_compressed_oop(self):
     def test_adr_str(self):
         null = gdb.Value(0)
         val = gdb.Value(int(0xCAFE))
-        self.assertEqual(SVMUtil.adr_str(null.cast(self.compressed_type.pointer())), ' @z(0x0)')
-        self.assertEqual(SVMUtil.adr_str(val.cast(self.compressed_type.pointer())), ' @z(0xcafe)')
-        self.assertEqual(SVMUtil.adr_str(null.cast(self.uncompressed_type.pointer())), ' @(0x0)')
-        self.assertEqual(SVMUtil.adr_str(val.cast(self.uncompressed_type.pointer())), ' @(0xcafe)')
+        self.assertEqual(self.svm_util.adr_str(null.cast(self.compressed_type.pointer())), ' @z(0x0)')
+        self.assertEqual(self.svm_util.adr_str(val.cast(self.compressed_type.pointer())), ' @z(0xcafe)')
+        self.assertEqual(self.svm_util.adr_str(null.cast(self.uncompressed_type.pointer())), ' @(0x0)')
+        self.assertEqual(self.svm_util.adr_str(val.cast(self.uncompressed_type.pointer())), ' @(0xcafe)')
 
     def test_java_string(self):
         gdb_set_breakpoint("com.oracle.svm.test.debug.helper.PrettyPrinterTest::testString")
         gdb_continue()
         string = gdb.parse_and_eval("str")
         null = gdb.Value(0).cast(string.type)
-        self.assertEqual(SVMUtil.get_java_string(string), 'string')
-        self.assertEqual(SVMUtil.get_java_string(null), "")
+        self.assertEqual(self.svm_util.get_java_string(string), 'string')
+        self.assertEqual(self.svm_util.get_java_string(null), "")
 
     def test_java_string_as_gdb_output_string(self):
         gdb_set_breakpoint("com.oracle.svm.test.debug.helper.PrettyPrinterTest::testString")
         gdb_continue()
         string = gdb.parse_and_eval("str")
         gdb_set_param("svm-print-string-limit", "2")
-        self.assertEqual(SVMUtil.get_java_string(string, True), 'st...')
+        self.assertEqual(self.svm_util.get_java_string(string, True), 'st...')
         gdb_set_param("svm-print-string-limit", "unlimited")
-        self.assertEqual(SVMUtil.get_java_string(string, True), 'string')
+        self.assertEqual(self.svm_util.get_java_string(string, True), 'string')
 
     def test_get_rtt(self):
         gdb_set_breakpoint("com.oracle.svm.test.debug.helper.PrettyPrinterTest::testArrayList")
         gdb_continue()
         mixed_list = gdb.parse_and_eval("mixedList")  # static type is List
         str_list = gdb.parse_and_eval("strList")  # static type is ArrayList
-        self.assertEqual(SVMUtil.get_rtt(str_list), SVMUtil.get_rtt(mixed_list))  # both are of rtt ArrayList
-        self.assertEqual(SVMUtil.get_rtt(str_list['elementData']['data'][0]), gdb.lookup_type('_z_.java.lang.String'))
+        self.assertEqual(self.svm_util.get_rtt(str_list), self.svm_util.get_rtt(mixed_list))  # both are of rtt ArrayList
+        self.assertEqual(self.svm_util.get_rtt(str_list['elementData']['data'][0]), gdb.lookup_type('_z_.java.lang.String'))
 
 
 # redirect unittest output to terminal

From 0a72aff3174ddcfe42b9b04df470185fa4958173 Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Fri, 10 Jan 2025 20:32:26 +0100
Subject: [PATCH 19/34] Add testing for runtime compilations

---
 substratevm/mx.substratevm/mx_substratevm.py  |  51 +++-
 substratevm/mx.substratevm/testdeopt.js       |  22 ++
 .../debug/SubstrateDebugInfoInstaller.java    |  14 +-
 .../RuntimeCompileDebugInfoTest.java          | 229 +++++++++++----
 .../debug/helper/test_runtime_compilation.py  | 265 ++++++++++++++++++
 .../test/debug/helper/test_runtime_deopt.py   | 125 +++++++++
 6 files changed, 643 insertions(+), 63 deletions(-)
 create mode 100644 substratevm/mx.substratevm/testdeopt.js
 rename substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/{ => helper}/RuntimeCompileDebugInfoTest.java (61%)
 create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_runtime_compilation.py
 create mode 100644 substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_runtime_deopt.py

diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py
index b371ca618cdb..3a695b4735f4 100644
--- a/substratevm/mx.substratevm/mx_substratevm.py
+++ b/substratevm/mx.substratevm/mx_substratevm.py
@@ -862,13 +862,29 @@ def _runtimedebuginfotest(native_image, args=None):
     args = [] if args is None else args
     build_dir = join(svmbuild_dir(), 'runtimedebuginfotest')
 
+    test_proj = mx.dependency('com.oracle.svm.test')
+    test_source_path = test_proj.source_dirs()[0]
+
+    test_python_source_dir = join(test_source_path, 'com', 'oracle', 'svm', 'test', 'debug', 'helper')
+    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')
+
+    gdb_args = [
+        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
+        '-iex', "set logging redirect on",
+        '-iex', "set logging overwrite off",
+    ]
+
     # clean / create output directory
     if exists(build_dir):
         mx.rmtree(build_dir)
     mx_util.ensure_dir_exists(build_dir)
 
     # Build the native image from Java code
-    native_image([
+    runtime_compile_binary = native_image([
                      '-g', '-O0', '--no-fallback',
                      '-o', join(build_dir, 'runtimedebuginfotest'),
                      '-cp', classpath('com.oracle.svm.test'),
@@ -878,8 +894,39 @@ def _runtimedebuginfotest(native_image, args=None):
         '-H:DebugInfoSourceSearchPath=' + mx.project('com.oracle.svm.test').source_dirs()[0],
         ]) + args)
 
+    logfile = join(build_dir, 'test_runtime_compilation.log')
+    os.environ.update({'gdb_logfile': logfile})
+    gdb_command = gdb_args + [
+        '-iex', f"set logging file {logfile}",
+        '-iex', "set logging enabled on",
+        '-iex', f"set auto-load safe-path {join(build_dir, 'gdb-debughelpers.py')}",
+        '-x', test_runtime_compilation_py, runtime_compile_binary
+    ]
+    mx.log(' '.join([(f"'{c}'" if ' ' in c else c) for c in gdb_command]))
+    # unittest may result in different exit code, nonZeroIsFatal ensures that we can go on with other test
+    status = mx.run(gdb_command, cwd=build_dir, nonZeroIsFatal=False)
+
     # Start the native image
-    mx.run([join(build_dir, 'runtimedebuginfotest')])
+    # mx.run([join(build_dir, 'runtimedebuginfotest')])
+
+    jslib = mx.add_lib_suffix(native_image(['-g', '-O0', '-J-Xmx16g', '--macro:jsvm-library']))
+    js_launcher = get_js_launcher(jslib)
+
+    testdeopt_js = join(suite.dir, 'mx.substratevm', 'testdeopt.js')
+
+    logfile = join(build_dir, 'test_runtime_deopt.log')
+    os.environ.update({'gdb_logfile': logfile})
+    gdb_command = gdb_args + [
+        '-iex', f"set logging file {logfile}",
+        '-iex', "set logging enabled on",
+        '-iex', f"set auto-load safe-path {join(build_dir, 'gdb-debughelpers.py')}",
+        '-x', test_runtime_deopt_py, '--args', js_launcher, testdeopt_js
+    ]
+    mx.log(' '.join([(f"'{c}'" if ' ' in c else c) for c in gdb_command]))
+    # unittest may result in different exit code, nonZeroIsFatal ensures that we can go on with other test
+    status |= mx.run(gdb_command, cwd=build_dir, nonZeroIsFatal=False)
+
+    return status
 
 
 _helloworld_variants = {
diff --git a/substratevm/mx.substratevm/testdeopt.js b/substratevm/mx.substratevm/testdeopt.js
new file mode 100644
index 000000000000..9f3cbaf8fb96
--- /dev/null
+++ b/substratevm/mx.substratevm/testdeopt.js
@@ -0,0 +1,22 @@
+function add(a, b, test) {
+    if (test) {
+        a += b;
+    }
+    return a + b;
+}
+
+// trigger compilation add for ints and test = true
+for (let i = 0; i < 1000 * 1000; i++) {
+    add(i, i, true);
+}
+
+// deoptimize with failed assumption in compiled method
+// then trigger compilation again
+console.log("deopt1")
+for (let i = 0; i < 1000 * 1000; i++) {
+    add(i, i, false);
+}
+
+// deoptimize with different parameter types
+console.log("deopt2");
+add({f1: "test1", f2: 2}, {x: "x", y: {test: 42}}, false);
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
index ba4c26e48e64..fefe91b6cd51 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
@@ -62,6 +62,7 @@
 
 public final class SubstrateDebugInfoInstaller implements InstalledCodeObserver {
 
+    private final DebugContext debug;
     private final SubstrateDebugInfoProvider substrateDebugInfoProvider;
     private final ObjectFile objectFile;
     private final ArrayList<ObjectFile.Element> sortedObjectFileElements;
@@ -89,6 +90,7 @@ public InstalledCodeObserver create(DebugContext debugContext, SharedMethod meth
 
     private SubstrateDebugInfoInstaller(DebugContext debugContext, SharedMethod method, CompilationResult compilation, MetaAccessProvider metaAccess, RuntimeConfiguration runtimeConfiguration,
                     Pointer code, int codeSize) {
+        debug = debugContext;
         substrateDebugInfoProvider = new SubstrateDebugInfoProvider(debugContext, method, compilation, runtimeConfiguration, metaAccess, code.rawValue());
 
         int pageSize = NumUtil.safeToInt(ImageSingletons.lookup(VirtualMemoryProvider.class).getGranularity().rawValue());
@@ -99,11 +101,11 @@ private SubstrateDebugInfoInstaller(DebugContext debugContext, SharedMethod meth
         debugInfoSize = objectFile.bake(sortedObjectFileElements);
 
         if (debugContext.isLogEnabled()) {
-            dumpObjectFile(debugContext);
+            dumpObjectFile();
         }
     }
 
-    private void dumpObjectFile(DebugContext debugContext) {
+    private void dumpObjectFile() {
         StringBuilder sb = new StringBuilder(substrateDebugInfoProvider.getCompilationName()).append(".debug");
         try (FileChannel dumpFile = FileChannel.open(Paths.get(sb.toString()),
                         StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING,
@@ -111,7 +113,7 @@ private void dumpObjectFile(DebugContext debugContext) {
             ByteBuffer buffer = dumpFile.map(FileChannel.MapMode.READ_WRITE, 0, debugInfoSize);
             objectFile.writeBuffer(sortedObjectFileElements, buffer);
         } catch (IOException e) {
-            debugContext.log("Failed to dump %s", sb);
+            debug.log("Failed to dump %s", sb);
         }
     }
 
@@ -170,8 +172,6 @@ public void activate(InstalledCodeObserverHandle installedCodeObserverHandle) {
         @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
         public void release(InstalledCodeObserverHandle installedCodeObserverHandle) {
             Handle handle = (Handle) installedCodeObserverHandle;
-            // VMOperation.guaranteeInProgress("SubstrateDebugInfoInstaller.Accessor.release must
-            // run in a VMOperation");
             GDBJITInterface.JITCodeEntry entry = handle.getRawHandle();
             // Handle may still be just initialized here, so it never got registered in GDB.
             if (handle.getState() == Handle.ACTIVATED) {
@@ -206,7 +206,9 @@ public void releaseOnTearDown(InstalledCodeObserverHandle installedCodeObserverH
     public InstalledCodeObserverHandle install() {
         NonmovableArray<Byte> debugInfoData = writeDebugInfoData();
         Handle handle = GDBJITAccessor.createHandle(debugInfoData);
-        System.out.println(toString(handle));
+        try (DebugContext.Scope s = debug.scope("RuntimeCompilation")) {
+            debug.log(toString(handle));
+        }
         return handle;
     }
 
diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/RuntimeCompileDebugInfoTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/RuntimeCompileDebugInfoTest.java
similarity index 61%
rename from substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/RuntimeCompileDebugInfoTest.java
rename to substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/RuntimeCompileDebugInfoTest.java
index 8031d1d22228..a690987621d6 100644
--- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/RuntimeCompileDebugInfoTest.java
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/RuntimeCompileDebugInfoTest.java
@@ -1,13 +1,35 @@
-package com.oracle.svm.test.debug;
+/*
+ * Copyright (c) 2024, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.svm.test.debug.helper;
+
+import static com.oracle.svm.core.util.VMError.shouldNotReachHere;
+
+import java.util.ArrayList;
+import java.util.List;
 
-import com.oracle.svm.core.NeverInline;
-import com.oracle.svm.core.c.InvokeJavaFunctionPointer;
-import com.oracle.svm.graal.SubstrateGraalUtils;
-import com.oracle.svm.graal.hosted.runtimecompilation.RuntimeCompilationFeature;
-import com.oracle.svm.graal.meta.SubstrateMethod;
-import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl;
-import com.oracle.svm.util.ModuleSupport;
-import jdk.vm.ci.code.InstalledCode;
 import org.graalvm.nativeimage.ImageSingletons;
 import org.graalvm.nativeimage.VMRuntime;
 import org.graalvm.nativeimage.c.function.CFunctionPointer;
@@ -15,10 +37,16 @@
 import org.graalvm.nativeimage.hosted.RuntimeClassInitialization;
 import org.graalvm.word.WordFactory;
 
-import java.util.ArrayList;
-import java.util.List;
+import com.oracle.svm.core.AlwaysInline;
+import com.oracle.svm.core.NeverInline;
+import com.oracle.svm.core.c.InvokeJavaFunctionPointer;
+import com.oracle.svm.graal.SubstrateGraalUtils;
+import com.oracle.svm.graal.hosted.runtimecompilation.RuntimeCompilationFeature;
+import com.oracle.svm.graal.meta.SubstrateMethod;
+import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl;
+import com.oracle.svm.util.ModuleSupport;
 
-import static com.oracle.svm.core.util.VMError.shouldNotReachHere;
+import jdk.vm.ci.code.InstalledCode;
 
 class RuntimeCompilations {
 
@@ -28,25 +56,32 @@ class RuntimeCompilations {
     Object d;
 
     static Integer sa = 42;
-    static String sb;
-    static Object sc;
+    static int sb;
+    static String sc;
+    static Object sd;
 
     RuntimeCompilations(int a) {
         this.a = a;
     }
 
     @NeverInline("For testing")
-    private void breakHere() {}
+    private void breakHere() {
+    }
 
     @NeverInline("For testing")
-    public Integer paramMethod(int param1, String param2, Object param3) {
-        breakHere();
-        b = param1;
+    private void breakHere(@SuppressWarnings("unused") Object... pin) {
+    }
+
+    @NeverInline("For testing")
+    public Integer paramMethod(Integer param1, int param2, String param3, Object param4) {
+        a = param1;
         breakHere();
-        c = param2;
+        b = param2;
         breakHere();
-        d = param3;
+        c = param3;
         breakHere();
+        d = param4;
+        breakHere(param1);
         return a;
     }
 
@@ -58,34 +93,90 @@ public Integer noParamMethod() {
     @NeverInline("For testing")
     public void voidMethod() {
         a *= 2;
+        breakHere();
     }
 
     @NeverInline("For testing")
     public int primitiveReturnMethod(int param1) {
-        a = param1;
-        return param1;
+        b += param1;
+        breakHere();
+        return b;
     }
 
     @NeverInline("For testing")
-    public Integer[] arrayMethod(int[] param1, String[] param2) {
+    public Integer[] arrayMethod(int[] param1, @SuppressWarnings("unused") String[] param2) {
         a = param1[0];
-        return new Integer[] { a };
+        breakHere();
+        return new Integer[]{a};
     }
 
     @NeverInline("For testing")
+    @SuppressWarnings("unused")
     public float localsMethod(String param1) {
         float f = 1.5f;
         String x = param1;
+        breakHere();
         byte[] bytes = x.getBytes();
-        return f;
+        breakHere();
+        return f + bytes.length;
     }
 
     @NeverInline("For testing")
-    public static Integer staticMethod(int param1, String param2, Object param3) {
+    public static Integer staticMethod(Integer param1, int param2, String param3, Object param4) {
         sa = param1;
         sb = param2;
         sc = param3;
-        return sa;
+        sd = param4;
+        return sa + sb;
+    }
+
+    @NeverInline("For testing")
+    public void inlineTest(String param1) {
+        inlineMethod1(param1);
+        breakHere();
+        inlineMethod2(param1);
+        breakHere();
+        noInlineMethod1(param1);
+        breakHere();
+        noInlineMethod2(param1);
+    }
+
+    @AlwaysInline("For testing")
+    private void inlineMethod1(String param1) {
+        String inlineParam = param1;
+        breakHere(inlineParam);
+    }
+
+    @AlwaysInline("For testing")
+    protected void inlineMethod2(@SuppressWarnings("unused") String param1) {
+        String p1 = "1";
+        inlineMethod1(p1);
+        breakHere();
+
+        String p2 = "2";
+        noInlineMethod1(p2);
+        breakHere();
+    }
+
+    @NeverInline("For testing")
+    private void noInlineMethod1(String param1) {
+        String noInlineParam = param1;
+        breakHere(noInlineParam);
+    }
+
+    @NeverInline("For testing")
+    protected void noInlineMethod2(@SuppressWarnings("unused") String param1) {
+        String p1 = "a";
+        noInlineMethod1(p1);
+        breakHere();
+
+        String p2 = "b";
+        inlineMethod1(p2);
+        breakHere();
+
+        String p3 = "c";
+        inlineMethod2(p3);
+        breakHere();
     }
 }
 
@@ -98,19 +189,20 @@ static class RuntimeHolder {
         SubstrateMethod testMethod5;
         SubstrateMethod testMethod6;
         SubstrateMethod testMethod7;
+        SubstrateMethod testMethod8;
     }
 
     public static class RegisterMethodsFeature implements Feature {
         RegisterMethodsFeature() {
             ModuleSupport.accessPackagesToClass(ModuleSupport.Access.EXPORT, RegisterMethodsFeature.class, false,
-                    "org.graalvm.nativeimage.builder",
-                    "com.oracle.svm.graal", "com.oracle.svm.graal.hosted.runtimecompilation", "com.oracle.svm.hosted");
+                            "org.graalvm.nativeimage.builder",
+                            "com.oracle.svm.graal", "com.oracle.svm.graal.hosted.runtimecompilation", "com.oracle.svm.hosted");
             ModuleSupport.accessPackagesToClass(ModuleSupport.Access.EXPORT, RegisterMethodsFeature.class, false,
-                    "jdk.internal.vm.ci",
-                    "jdk.vm.ci.code");
+                            "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",
+                            "jdk.graal.compiler.api.directives");
         }
 
         @Override
@@ -129,16 +221,18 @@ public void beforeAnalysis(BeforeAnalysisAccess a) {
             RuntimeCompilationFeature runtimeCompilationFeature = RuntimeCompilationFeature.singleton();
             runtimeCompilationFeature.initializeRuntimeCompilationForTesting(config);
 
-            holder.testMethod1 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "paramMethod", int.class, String.class, Object.class);
+            holder.testMethod1 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "paramMethod", Integer.class, int.class, String.class, Object.class);
             holder.testMethod2 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "noParamMethod");
             holder.testMethod3 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "voidMethod");
             holder.testMethod4 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "primitiveReturnMethod", int.class);
             holder.testMethod5 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "arrayMethod", int[].class, String[].class);
             holder.testMethod6 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "localsMethod", String.class);
-            holder.testMethod7 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "staticMethod", int.class, String.class, Object.class);
+            holder.testMethod7 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "staticMethod", Integer.class, int.class, String.class, Object.class);
+            holder.testMethod8 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "inlineTest", String.class);
         }
 
-        private SubstrateMethod prepareMethodForRuntimeCompilation(BeforeAnalysisAccessImpl config, RuntimeCompilationFeature runtimeCompilationFeature, Class<?> holder, String methodName, Class<?>... signature) {
+        private static SubstrateMethod prepareMethodForRuntimeCompilation(BeforeAnalysisAccessImpl config, RuntimeCompilationFeature runtimeCompilationFeature, Class<?> holder, String methodName,
+                        Class<?>... signature) {
             RuntimeClassInitialization.initializeAtBuildTime(holder);
             try {
                 return runtimeCompilationFeature.prepareMethodForRuntimeCompilation(holder.getMethod(methodName, signature), config);
@@ -148,28 +242,32 @@ private SubstrateMethod prepareMethodForRuntimeCompilation(BeforeAnalysisAccessI
         }
     }
 
-
     interface TestFunctionPointer extends CFunctionPointer {
         @InvokeJavaFunctionPointer
-        Integer invoke(Object receiver, int arg1, String arg2, Object arg3);
+        Integer invoke(Object receiver, Integer arg1, int arg2, String arg3, Object arg4);
+
         @InvokeJavaFunctionPointer
         Object invoke(Object receiver);
+
         @InvokeJavaFunctionPointer
         int invoke(Object receiver, int arg1);
+
         @InvokeJavaFunctionPointer
         Integer[] invoke(Object receiver, int[] arg1, String[] arg2);
+
         @InvokeJavaFunctionPointer
         float invoke(Object receiver, String arg1);
+
         @InvokeJavaFunctionPointer
-        Integer invoke(int arg1, String arg2, Object arg3);
+        Integer invoke(Integer arg1, int arg2, String arg3, Object arg4);
     }
 
     private static RuntimeHolder getHolder() {
         return ImageSingletons.lookup(RuntimeHolder.class);
     }
 
-    private static Integer invoke(TestFunctionPointer functionPointer, Object receiver, int arg1, String arg2, Object arg3) {
-        return functionPointer.invoke(receiver, arg1, arg2, arg3);
+    private static Integer invoke(TestFunctionPointer functionPointer, Object receiver, Integer arg1, int arg2, String arg3, Object arg4) {
+        return functionPointer.invoke(receiver, arg1, arg2, arg3, arg4);
     }
 
     private static Object invoke(TestFunctionPointer functionPointer, Object receiver) {
@@ -188,31 +286,34 @@ private static float invoke(TestFunctionPointer functionPointer, Object receiver
         return functionPointer.invoke(receiver, arg1);
     }
 
-    private static Integer invoke(TestFunctionPointer functionPointer, Integer arg1, String arg2, Object arg3) {
-        return functionPointer.invoke(arg1, arg2, arg3);
+    private static Integer invoke(TestFunctionPointer functionPointer, Integer arg1, int arg2, String arg3, Object arg4) {
+        return functionPointer.invoke(arg1, arg2, arg3, arg4);
     }
 
     private static TestFunctionPointer getFunctionPointer(InstalledCode installedCode) {
         return WordFactory.pointer(installedCode.getEntryPoint());
     }
 
+    @SuppressWarnings("unused")
     public static void testParams() {
 
         InstalledCode installedCode = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod1);
         InstalledCode installedCodeStatic = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod7);
 
         RuntimeCompilations x = new RuntimeCompilations(11);
-        int param1 = 27;
-        String param2 = "test";
-        Object param3 = new ArrayList<>();
+        Integer param1 = 42;
+        int param2 = 27;
+        String param3 = "test";
+        Object param4 = new ArrayList<>();
 
-        Integer result = invoke(getFunctionPointer(installedCode), x, param1, param2, param3);
-        Integer resultStatic = invoke(getFunctionPointer(installedCodeStatic), param1, param2, param3);
+        Integer result = invoke(getFunctionPointer(installedCode), x, param1, param2, param3, param4);
+        Integer resultStatic = invoke(getFunctionPointer(installedCodeStatic), param1, param2, param3, param4);
 
         installedCode.invalidate();
         installedCodeStatic.invalidate();
     }
 
+    @SuppressWarnings("unused")
     public static void testNoParam() {
 
         InstalledCode installedCode = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod2);
@@ -235,6 +336,7 @@ public static void testVoid() {
         installedCode.invalidate();
     }
 
+    @SuppressWarnings("unused")
     public static void testPrimitiveReturn() {
 
         InstalledCode installedCode = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod4);
@@ -247,19 +349,21 @@ public static void testPrimitiveReturn() {
         installedCode.invalidate();
     }
 
+    @SuppressWarnings("unused")
     public static void testArray() {
 
         InstalledCode installedCode = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod5);
 
         RuntimeCompilations x = new RuntimeCompilations(11);
-        int[] param1 = new int[] {1,2,3,4};
-        String[] param2 = new String[] {"this", "is", "an", "array"};
+        int[] param1 = new int[]{1, 2, 3, 4};
+        String[] param2 = new String[]{"this", "is", "an", "array"};
 
         Integer[] result = invoke(getFunctionPointer(installedCode), x, param1, param2);
 
         installedCode.invalidate();
     }
 
+    @SuppressWarnings("unused")
     public static void testLocals() {
 
         InstalledCode installedCode = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod6);
@@ -272,15 +376,29 @@ public static void testLocals() {
         installedCode.invalidate();
     }
 
+    public static void testInlining() {
+
+        InstalledCode installedCode = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod8);
+
+        RuntimeCompilations x = new RuntimeCompilations(11);
+        String param1 = "test";
+
+        invoke(getFunctionPointer(installedCode), x, param1);
+
+        installedCode.invalidate();
+    }
+
+    @SuppressWarnings("unused")
     public static void testAOTCompiled() {
 
         RuntimeCompilations x = new RuntimeCompilations(11);
-        int param1 = 27;
-        String param2 = "test";
-        Object param3 = new ArrayList<>();
+        Integer param1 = 42;
+        int param2 = 27;
+        String param3 = "test";
+        Object param4 = new ArrayList<>();
 
-        Integer result = x.paramMethod(param1, param2, param3);
-        Integer result2 = RuntimeCompilations.staticMethod(param1, param2, param3);
+        Integer result = x.paramMethod(param1, param2, param3, param4);
+        Integer result2 = RuntimeCompilations.staticMethod(param1, param2, param3, param4);
         Integer result3 = x.noParamMethod();
     }
 
@@ -298,5 +416,6 @@ public static void main(String[] args) {
         testPrimitiveReturn();
         testArray();
         testLocals();
+        testInlining();
     }
 }
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/src/com/oracle/svm/test/debug/helper/test_runtime_compilation.py
new file mode 100644
index 000000000000..dbebb4ce555e
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_runtime_compilation.py
@@ -0,0 +1,265 @@
+#
+# Copyright (c) 2024, 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.  Oracle designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Oracle in the LICENSE file that accompanied this code.
+#
+# 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.
+#
+
+import os
+import sys
+import unittest
+
+import gdb
+
+# add test directory to path to allow import of gdb_helper.py
+sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__))))
+
+from gdb_helper import *
+
+
+# doesn't require the gdb patch to be available,
+# this just tests the jit compilation interface, not the generated debug info
+# however still requires symbols to be available for setting a breakpoint
+class TestJITCompilationInterface(unittest.TestCase):
+    @classmethod
+    def setUp(cls):
+        set_up_test()
+        gdb_delete_breakpoints()
+        gdb_start()
+
+    @classmethod
+    def tearDown(cls):
+        gdb_delete_breakpoints()
+        gdb_kill()
+
+    # this test requires gdb to automatically add a breakpoint when a runtime compilation occurs
+    # if a breakpoint for the compiled method existed before
+    def test_update_breakpoint(self):
+        # set breakpoint in runtime compiled function and stop there
+        # store initial breakpoint info for comparison
+        gdb_set_breakpoint("com.oracle.svm.test.debug.runtime.RuntimeCompilations::inlineTest")
+        breakpoint_info_before = gdb_execute('info breakpoints')
+        gdb_continue()
+
+        # get current breakpoint info and do checks
+        breakpoint_info_after = gdb_execute('info breakpoints')
+        # check if we got more breakpoints than before
+        self.assertGreater(len(breakpoint_info_after), len(breakpoint_info_before))
+        # check if old breakpoints still exist, code address must be the same
+        # split at code address as multi-breakpoints are printed very different to single breakpoints
+        self.assertIn(breakpoint_info_before.split('0x')[-1], breakpoint_info_after)
+        # check if exactly one new correct breakpoint was added
+        # new breakpoint address is always added after
+        self.assertEqual(breakpoint_info_after.split(breakpoint_info_before.split('0x')[-1])[-1].count('com.oracle.svm.test.debug.runtime.RuntimeCompilations::inlineTest'), 1)
+
+        # run until the runtime compilation is invalidated and check if the breakpoint is removed
+        gdb_set_breakpoint('com.oracle.svm.graal.meta.SubstrateInstalledCodeImpl::invalidate')
+        gdb_continue()  # run until invalidation
+        gdb_finish()  # finish invalidation - this should trigger an unregister call to gdb
+        breakpoint_info_after_invalidation = gdb_execute('info breakpoints')
+        # check if additional breakpoint is cleared after invalidate
+        # breakpoint info is still printed as multi-breakpoint, thus we check if exactly one valid breakpoint is remaining
+        self.assertEqual(breakpoint_info_after_invalidation.count('com.oracle.svm.test.debug.runtime.RuntimeCompilations::inlineTest'), 1)
+        # breakpoint info must change after invalidation
+        self.assertNotEquals(breakpoint_info_after, breakpoint_info_after_invalidation)
+
+    # this test requires gdb to first load a new objfile at runtime and then remove it as the compilation is invalidated
+    def test_load_objfile(self):
+        # sanity check, we should not have in-memory objfiles at this point (to avoid false-positives later)
+        objfiles = gdb.objfiles()
+        self.assertFalse(any([o.filename.startswith('<in-memory@') for o in objfiles]))
+
+        # set breakpoint in runtime compiled function and stop there
+        gdb_set_breakpoint("com.oracle.svm.test.debug.runtime.RuntimeCompilations::inlineTest")
+        gdb_continue()
+        # we are at the breakpoint, check if the objfile got registered in gdb
+        objfiles = gdb.objfiles()
+        self.assertTrue(any([o.filename.startswith('<in-memory@') for o in objfiles]))
+
+        # run until the runtime compilation is invalidated and check if the objfile is removed
+        gdb_set_breakpoint('com.oracle.svm.graal.meta.SubstrateInstalledCodeImpl::invalidate')
+        gdb_continue()  # run until invalidation
+        gdb_finish()  # finish invalidation - this should trigger an unregister call to gdb
+        # compilation is invalidated, check if objfile was removed
+        objfiles = gdb.objfiles()
+        self.assertFalse(any([o.filename.startswith('<in-memory@') for o in objfiles]))
+
+    # this test checks if symbols in the runtime compilation are correct
+    # a runtime compilation should not add any new function symbols
+    def test_method_signature(self):
+        # check initially
+        function_info_before = gdb_execute('info function com.oracle.svm.test.debug.runtime.RuntimeCompilations::paramMethod')
+        # one in search term, one function symbol, one deoptimized function symbol
+        self.assertEqual(function_info_before.count('paramMethod'), 3)
+        self.assertIn('java.lang.Integer *com.oracle.svm.test.debug.runtime.RuntimeCompilations::paramMethod(java.lang.Integer*, int, java.lang.String*, java.lang.Object*);', function_info_before)
+
+        # set breakpoint in runtime compiled function and stop there
+        gdb_set_breakpoint("com.oracle.svm.test.debug.runtime.RuntimeCompilations::paramMethod")
+        gdb_continue()  # first stops once for the AOT compiled variant
+        gdb_continue()
+        # ensure we did not register an extra symbol
+        function_info_after = gdb_execute('info function com.oracle.svm.test.debug.runtime.RuntimeCompilations::paramMethod')
+        self.assertEqual(function_info_before, function_info_after)
+
+        # run until the runtime compilation is invalidated and check if the symbols still exist
+        gdb_set_breakpoint('com.oracle.svm.graal.meta.SubstrateInstalledCodeImpl::invalidate')
+        gdb_continue()  # run until invalidation
+        gdb_finish()  # finish invalidation - this should trigger an unregister call to gdb
+        function_info_after = gdb_execute('info function com.oracle.svm.test.debug.runtime.RuntimeCompilations::paramMethod')
+        self.assertEqual(function_info_before, function_info_after)
+
+
+# this tests mostly require the gdb patch to work out, as otherwise no type information would be available
+class TestRuntimeDebugInfo(unittest.TestCase):
+    @classmethod
+    def setUp(cls):
+        cls.maxDiff = None
+        set_up_test()
+        gdb_delete_breakpoints()
+        gdb_start()
+        set_up_gdb_debughelpers()
+        gdb_execute("maintenance set dwarf type-signature-fallback main")
+
+    @classmethod
+    def tearDown(cls):
+        gdb_delete_breakpoints()
+        gdb_kill()
+
+    # check if initial parameter values are correct
+    def test_params_method_initial(self):
+        gdb_set_breakpoint("com.oracle.svm.test.debug.runtime.RuntimeCompilations::paramMethod")
+        gdb_continue()  # first stops once for the AOT compiled variant
+        gdb_continue()
+        self.assertEqual(gdb_output('param1'), '42')
+        self.assertEqual(gdb_output('param2'), '27')
+        self.assertEqual(gdb_output('param3'), '"test"')
+        self.assertEqual(gdb_output('param4'), 'java.util.ArrayList(0)')
+        this = gdb_output('this')
+        self.assertTrue(this.startswith('com.oracle.svm.test.debug.runtime.RuntimeCompilations = {'))
+        self.assertIn('a = 11', this)
+        self.assertIn('b = 0', this)
+        self.assertIn('c = null', this)
+        self.assertIn('d = null', this)
+
+    # checks if parameter types are resolved correctly from AOT debug info
+    def test_param_types(self):
+        gdb_set_breakpoint("com.oracle.svm.test.debug.runtime.RuntimeCompilations::paramMethod")
+        gdb_continue()  # first stops once for the AOT compiled variant
+        gdb_continue()
+        self.assertTrue(gdb_print_type('param1').startswith('type = class java.lang.Integer : public java.lang.Number {'))
+        self.assertEquals(gdb_print_type('param2').strip(), 'type = int')  # printed type may contain newline at the end
+        self.assertTrue(gdb_print_type('param3').startswith('type = class java.lang.String : public java.lang.Object {'))
+        self.assertTrue(gdb_print_type('param4').startswith('type = class java.lang.Object : public _objhdr {'))
+        self.assertTrue(gdb_print_type('this').startswith('type = class com.oracle.svm.test.debug.runtime.RuntimeCompilations : public java.lang.Object {'))
+
+    # run through paramMethod and check params after forced breakpoints
+    def test_params_method(self):
+        gdb_set_breakpoint("com.oracle.svm.test.debug.runtime.RuntimeCompilations::paramMethod")
+        gdb_continue()  # first stops once for the AOT compiled variant
+        gdb_continue()
+        gdb_set_breakpoint("com.oracle.svm.test.debug.runtime.RuntimeCompilations::breakHere")
+        # step 1 set a to param1 (param1 is pinned, so it is not optimized out yet)
+        gdb_continue()
+        gdb_finish()
+        self.assertEqual(gdb_output('param1'), '42')
+        self.assertEqual(gdb_output('param2'), '27')
+        self.assertEqual(gdb_output('param3'), '"test"')
+        self.assertEqual(gdb_output('param4'), 'java.util.ArrayList(0)')
+        this = gdb_output('this')
+        self.assertTrue(this.startswith('com.oracle.svm.test.debug.runtime.RuntimeCompilations = {'))
+        self.assertIn('a = 42', this)
+        self.assertIn('b = 0', this)
+        self.assertIn('c = null', this)
+        self.assertIn('d = null', this)
+        # step 2 set b to param2
+        gdb_continue()
+        gdb_finish()
+        self.assertEqual(gdb_output('param1'), '42')
+        self.assertEqual(gdb_output('param2'), '<optimized out>')
+        self.assertEqual(gdb_output('param3'), '"test"')
+        self.assertEqual(gdb_output('param4'), 'java.util.ArrayList(0)')
+        this = gdb_output('this')
+        self.assertTrue(this.startswith('com.oracle.svm.test.debug.runtime.RuntimeCompilations = {'))
+        self.assertIn('a = 42', this)
+        self.assertIn('b = 27', this)
+        self.assertIn('c = null', this)
+        self.assertIn('d = null', this)
+        # step 3 set c to param3
+        gdb_continue()
+        gdb_finish()
+        self.assertEqual(gdb_output('param1'), '42')
+        self.assertEqual(gdb_output('param2'), '<optimized out>')
+        self.assertEqual(gdb_output('param3'), '<optimized out>')
+        self.assertEqual(gdb_output('param4'), 'java.util.ArrayList(0)')
+        this = gdb_output('this')
+        self.assertTrue(this.startswith('com.oracle.svm.test.debug.runtime.RuntimeCompilations = {'))
+        self.assertIn('a = 42', this)
+        self.assertIn('b = 27', this)
+        self.assertIn('c = "test"', this)
+        self.assertIn('d = null', this)
+        # step 4 set d to param4  (pin of param1 ends here)
+        gdb_continue()
+        gdb_finish()
+        self.assertEqual(gdb_output('param1'), '<optimized out>')
+        self.assertEqual(gdb_output('param2'), '<optimized out>')
+        self.assertEqual(gdb_output('param3'), '<optimized out>')
+        self.assertEqual(gdb_output('param4'), '<optimized out>')
+        this = gdb_output('this')
+        self.assertTrue(this.startswith('com.oracle.svm.test.debug.runtime.RuntimeCompilations = {'))
+        self.assertIn('a = 42', this)
+        self.assertIn('b = 27', this)
+        self.assertIn('c = "test"', this)
+        self.assertIn('d = java.util.ArrayList(0)', this)
+
+    # compares params and param types of AOT and JIT compiled method
+    def test_compare_AOT_to_JIT(self):
+        gdb_set_breakpoint("com.oracle.svm.test.debug.runtime.RuntimeCompilations::paramMethod")
+        # first stop for the AOT compiled variant
+        gdb_continue()
+        this = gdb_output('this')
+        this_t = gdb_print_type('this')
+        param1 = gdb_output('param1')
+        param1_t = gdb_print_type('param1')
+        param2 = gdb_output('param2')
+        param2_t = gdb_print_type('param2')
+        param3 = gdb_output('param3')
+        param3_t = gdb_print_type('param3')
+        param4 = gdb_output('param4')
+        param4_t = gdb_print_type('param4')
+
+        # now stop for runtime compiled variant and check if equal
+        gdb_continue()
+        self.assertEqual(gdb_output('this'), this)
+        self.assertEqual(gdb_print_type('this'), this_t)
+        self.assertEqual(gdb_output('param1'), param1)
+        self.assertEqual(gdb_print_type('param1'), param1_t)
+        self.assertEqual(gdb_output('param2'), param2)
+        self.assertEqual(gdb_print_type('param2'), param2_t)
+        self.assertEqual(gdb_output('param3'), param3)
+        self.assertEqual(gdb_print_type('param3'), param3_t)
+        self.assertEqual(gdb_output('param4'), param4)
+        self.assertEqual(gdb_print_type('param4'), param4_t)
+
+
+# redirect unittest output to terminal
+result = unittest.main(testRunner=unittest.TextTestRunner(stream=sys.__stdout__), exit=False)
+# close gdb
+gdb_quit(0 if result.result.wasSuccessful() else 1)
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/src/com/oracle/svm/test/debug/helper/test_runtime_deopt.py
new file mode 100644
index 000000000000..1069b2a1a70c
--- /dev/null
+++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_runtime_deopt.py
@@ -0,0 +1,125 @@
+#
+# Copyright (c) 2024, 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.  Oracle designates this
+# particular file as subject to the "Classpath" exception as provided
+# by Oracle in the LICENSE file that accompanied this code.
+#
+# 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.
+#
+
+import os
+import sys
+import unittest
+
+import gdb
+
+# add test directory to path to allow import of gdb_helper.py
+sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__))))
+
+from gdb_helper import *
+
+
+# requires the gdb patch to be available
+class TestRuntimeDeopt(unittest.TestCase):
+    @classmethod
+    def setUp(cls):
+        cls.maxDiff = None
+        set_up_test()
+        gdb_start()
+
+    @classmethod
+    def tearDown(cls):
+        gdb_delete_breakpoints()
+        gdb_kill()
+
+    def test_frame_unwinder_registration(self):
+        # run to a method where the frame unwinder is registered
+        gdb_set_breakpoint('com.oracle.truffle.runtime.OptimizedCallTarget::profiledPERoot')
+        gdb_continue()
+        set_up_gdb_debughelpers()  # setup debughelpers after we know it exists
+        # check frame unwinder
+        unwinder_info = gdb_execute('info unwinder')
+        self.assertIn('libjsvm.so.debug:', unwinder_info)
+        self.assertIn('SubstrateVM FrameUnwinder', unwinder_info)
+
+    # for shared libraries, the frame unwinder is removed when the shared library is unloaded
+    # when it is loaded again, the gdb script is not run again
+    # the gdb-debughelpers should still be able to reload the frame unwinder
+    def test_frame_unwinder_reload(self):
+        # run to a method where the frame unwinder is registered
+        gdb_set_breakpoint('com.oracle.truffle.runtime.OptimizedCallTarget::profiledPERoot')
+        gdb_continue()
+        set_up_gdb_debughelpers()  # setup debughelpers after we know it exists
+
+        # stops previous execution and reloads the shared library
+        # gdb-debughelpers should then reload the frame unwinder for the shared library
+        gdb_run()
+        # check frame unwinder
+        unwinder_info = gdb_execute('info unwinder')
+        self.assertIn('libjsvm.so.debug:', unwinder_info)
+        self.assertIn('SubstrateVM FrameUnwinder', unwinder_info)
+
+    def test_backtrace_with_deopt(self):
+        # run until method is deoptimized
+        gdb_set_breakpoint("com.oracle.svm.core.deopt.Deoptimizer::invalidateMethodOfFrame")
+        gdb_continue()
+        gdb_finish()
+
+        # check backtrace
+        backtrace = gdb_execute('backtrace 5')
+        self.assertIn('[DEOPT FRAME] com.oracle.truffle.runtime.OptimizedCallTarget::profiledPERoot', backtrace)
+        self.assertIn('(deoptFrameValues=2, __0=com.oracle.svm.truffle.api.SubstrateOptimizedCallTarget = {...}, __1=java.lang.Object[5] = {...}) at OptimizedCallTarget.java', backtrace)
+        self.assertIn('com.oracle.svm.truffle.api.SubstrateOptimizedCallTargetInstalledCode::doInvoke', backtrace)
+        self.assertNotIn('??', backtrace)
+
+    # the js deopt test uses the jsvm-library
+    # so the debugging symbols do not originate from the main objfile, but from the shared library
+    # this requires gdb to use the full type signature fallback
+    def test_type_signature_fallback_full(self):
+        # stop at a method where we know that the runtime compiled frame is in the backtrace
+        gdb_execute("maintenance set dwarf type-signature-fallback full")
+        gdb_set_breakpoint("com.oracle.svm.core.deopt.Deoptimizer::invalidateMethodOfFrame")
+        gdb_continue()
+
+        # check backtrace
+        backtrace = gdb_execute('backtrace 5')
+        self.assertIn('com.oracle.truffle.runtime.OptimizedCallTarget::profiledPERoot', backtrace)
+        self.assertIn('(this=null, originalArguments=com.oracle.svm.core.option.RuntimeOptionKey = {...})', backtrace)
+        self.assertNotIn('this=<unknown type in <in-memory@', backtrace)
+
+    # this should not work with just main objfile type signature fallback
+    # the main objfile is the js launcher which has no debuggin symbols
+    def test_type_signature_fallback_main(self):
+        # stop at a method where we know that the runtime compiled frame is in the backtrace
+        gdb_execute("maintenance set dwarf type-signature-fallback main")
+        gdb_set_breakpoint("com.oracle.svm.core.deopt.Deoptimizer::invalidateMethodOfFrame")
+        gdb_continue()
+
+        # check backtrace
+        backtrace = gdb_execute('backtrace 5')
+        self.assertIn('com.oracle.truffle.runtime.OptimizedCallTarget::profiledPERoot', backtrace)
+        self.assertNotIn('(this=null, originalArguments=com.oracle.svm.core.option.RuntimeOptionKey = {...})', backtrace)
+        self.assertIn('this=<unknown type in <in-memory@', backtrace)
+        self.assertIn('originalArguments=<unknown type in <in-memory@', backtrace)
+
+
+# redirect unittest output to terminal
+result = unittest.main(testRunner=unittest.TextTestRunner(stream=sys.__stdout__), exit=False)
+# close gdb
+gdb_quit(0 if result.result.wasSuccessful() else 1)

From e3d24981c8792da46a3531d598344346531e7f64 Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Fri, 10 Jan 2025 20:36:12 +0100
Subject: [PATCH 20/34] Update debug info provider to keep multi method info

---
 .../svm/hosted/image/NativeImageDebugInfoProvider.java | 10 ----------
 1 file changed, 10 deletions(-)

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 187da090fcba..fc089d3000d3 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
@@ -65,7 +65,6 @@
 import com.oracle.objectfile.debugentry.PrimitiveTypeEntry;
 import com.oracle.objectfile.debugentry.StructureTypeEntry;
 import com.oracle.objectfile.debugentry.TypeEntry;
-import com.oracle.svm.common.meta.MultiMethod;
 import com.oracle.svm.core.StaticFieldsSupport;
 import com.oracle.svm.core.SubstrateOptions;
 import com.oracle.svm.core.debug.SharedDebugInfoProvider;
@@ -348,15 +347,6 @@ protected long getCodeOffset(SharedMethod method) {
         return ((HostedMethod) method).getCodeAddressOffset();
     }
 
-    @Override
-    public MethodEntry lookupMethodEntry(SharedMethod method) {
-        SharedMethod targetMethod = method;
-        if (targetMethod instanceof HostedMethod hostedMethod && !hostedMethod.isOriginalMethod()) {
-            targetMethod = hostedMethod.getMultiMethod(MultiMethod.ORIGINAL_METHOD);
-        }
-        return super.lookupMethodEntry(targetMethod);
-    }
-
     @Override
     protected void processTypeEntry(SharedType type, TypeEntry typeEntry) {
         assert type instanceof HostedType;

From 0f4b40e54901a1fa60a5dedb566aff973cea2560 Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Mon, 13 Jan 2025 15:17:13 +0100
Subject: [PATCH 21/34] Fix style issue

---
 .../com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java   | 1 +
 1 file changed, 1 insertion(+)

diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
index fefe91b6cd51..d9b0ae1afd0d 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
@@ -203,6 +203,7 @@ public void releaseOnTearDown(InstalledCodeObserverHandle installedCodeObserverH
     }
 
     @Override
+    @SuppressWarnings("try")
     public InstalledCodeObserverHandle install() {
         NonmovableArray<Byte> debugInfoData = writeDebugInfoData();
         Handle handle = GDBJITAccessor.createHandle(debugInfoData);

From c4fde77b472570dafa79e6e1b1abffc42b1ce1e8 Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Tue, 14 Jan 2025 18:31:25 +0100
Subject: [PATCH 22/34] Bugfixes, fix style issue and add copyright headers

---
 .../include/gdb_jit_compilation_interface.h   | 24 ++++++++-
 substratevm/mx.substratevm/testdeopt.js       | 25 +++++++++
 .../elf/dwarf/DwarfAbbrevSectionImpl.java     |  2 +-
 .../elf/dwarf/DwarfInfoSectionImpl.java       |  2 +-
 .../elf/dwarf/constants/DwarfForm.java        | 15 ++++--
 .../com/oracle/svm/core/SubstrateOptions.java |  7 ++-
 .../svm/core/debug/GDBJITInterface.java       |  5 +-
 .../core/debug/SharedDebugInfoProvider.java   | 52 +++++++++++++------
 .../GraalGraphObjectReplacer.java             | 20 +++++--
 .../image/NativeImageDebugInfoProvider.java   |  5 +-
 .../test/debug/helper/test_runtime_deopt.py   |  2 +-
 11 files changed, 123 insertions(+), 36 deletions(-)

diff --git a/substratevm/debug/include/gdb_jit_compilation_interface.h b/substratevm/debug/include/gdb_jit_compilation_interface.h
index 38de0e86d99c..ed4cc2659b1f 100644
--- a/substratevm/debug/include/gdb_jit_compilation_interface.h
+++ b/substratevm/debug/include/gdb_jit_compilation_interface.h
@@ -1,6 +1,26 @@
 /*
- * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
- * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
+ * Copyright (c) 2024, 2025, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
  */
 
 #ifndef SVM_NATIVE_GDBJITCOMPILATIONINTERFACE_H
diff --git a/substratevm/mx.substratevm/testdeopt.js b/substratevm/mx.substratevm/testdeopt.js
index 9f3cbaf8fb96..05e3e991619c 100644
--- a/substratevm/mx.substratevm/testdeopt.js
+++ b/substratevm/mx.substratevm/testdeopt.js
@@ -1,3 +1,28 @@
+/*
+ * Copyright (c) 2025, 2025, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
 function add(a, b, test) {
     if (test) {
         a += b;
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java
index 55ade32fe36e..f4a79937bb69 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java
@@ -920,6 +920,7 @@ public int writeAbbrevs(DebugContext context, byte[] buffer, int p) {
         pos = writeClassLayoutAbbrevs(context, buffer, pos);
         pos = writeMethodDeclarationAbbrevs(context, buffer, pos);
         pos = writeMethodLocationAbbrev(context, buffer, pos);
+        pos = writeAbstractInlineMethodAbbrev(context, buffer, pos);
         pos = writeInlinedSubroutineAbbrev(buffer, pos);
 
         pos = writeParameterDeclarationAbbrevs(context, buffer, pos);
@@ -950,7 +951,6 @@ public int writeAbbrevs(DebugContext context, byte[] buffer, int p) {
             pos = writeArrayElementFieldAbbrev(context, buffer, pos);
             pos = writeArrayDataTypeAbbrevs(context, buffer, pos);
             pos = writeArraySubrangeTypeAbbrev(context, buffer, pos);
-            pos = writeAbstractInlineMethodAbbrev(context, buffer, pos);
             pos = writeStaticFieldLocationAbbrev(context, buffer, pos);
             pos = writeSuperReferenceAbbrev(context, buffer, pos);
             pos = writeInterfaceImplementorAbbrev(context, buffer, pos);
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 1aad8aea6d1f..0970f50bb12a 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
@@ -1929,7 +1929,7 @@ private int writeAbstractInlineMethods(DebugContext context, ClassEntry classEnt
             // n.b. class entry used to index the method belongs to the inlining method
             // not the inlined method
             setAbstractInlineMethodIndex(classEntry, methodEntry, pos);
-            if (dwarfSections.isRuntimeCompilation()) {
+            if (dwarfSections.isRuntimeCompilation() && classEntry != methodEntry.getOwnerType()) {
                 pos = writeMethodDeclaration(context, classEntry, methodEntry, true, buffer, pos);
             } else {
                 pos = writeAbstractInlineMethod(context, classEntry, methodEntry, buffer, pos);
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfForm.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfForm.java
index fcc1dcb01cc9..042d5520a17c 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfForm.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfForm.java
@@ -35,13 +35,18 @@ public enum DwarfForm {
     DW_FORM_data2(0x05),
     DW_FORM_data4(0x6),
     @SuppressWarnings("unused") DW_FORM_data8(0x7),
-    @SuppressWarnings("unused") DW_FORM_string(0x8),
-    @SuppressWarnings("unused") DW_FORM_block1(0x0a),
+    @SuppressWarnings("unused")//
+    DW_FORM_string(0x8),
+    @SuppressWarnings("unused")//
+    DW_FORM_block1(0x0a),
     DW_FORM_ref_addr(0x10),
-    @SuppressWarnings("unused") DW_FORM_ref1(0x11),
-    @SuppressWarnings("unused") DW_FORM_ref2(0x12),
+    @SuppressWarnings("unused")//
+    DW_FORM_ref1(0x11),
+    @SuppressWarnings("unused")//
+    DW_FORM_ref2(0x12),
     DW_FORM_ref4(0x13),
-    @SuppressWarnings("unused") DW_FORM_ref8(0x14),
+    @SuppressWarnings("unused")//
+    DW_FORM_ref8(0x14),
     DW_FORM_sec_offset(0x17),
     DW_FORM_data1(0x0b),
     DW_FORM_flag(0xc),
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java
index aab7a65ca387..be23c56c1aab 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java
@@ -484,7 +484,12 @@ public static Path getRuntimeSourceDestDir() {
         if (sourceDestDir != null) {
             return Paths.get(sourceDestDir);
         }
-        return Paths.get(ProcessProperties.getExecutableName()).getParent().resolve("sources");
+        Path executableParentDir = Paths.get(ProcessProperties.getExecutableName()).getParent();
+        if (executableParentDir != null) {
+            return executableParentDir.resolve("sources");
+        } else {
+            return Paths.get("sources");
+        }
     }
 
     // TODO change to false
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterface.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterface.java
index ff2c28483bbe..396d80768676 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterface.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterface.java
@@ -40,7 +40,6 @@
 import org.graalvm.nativeimage.c.type.CCharPointer;
 import org.graalvm.nativeimage.c.type.CUnsigned;
 import org.graalvm.word.PointerBase;
-import org.graalvm.word.WordFactory;
 
 import com.oracle.svm.core.NeverInline;
 import com.oracle.svm.core.SubstrateOptions;
@@ -49,6 +48,8 @@
 import com.oracle.svm.core.c.CGlobalDataFactory;
 import com.oracle.svm.core.c.ProjectHeaderFile;
 
+import jdk.graal.compiler.word.Word;
+
 @CContext(GDBJITInterface.GDBJITInterfaceDirectives.class)
 public class GDBJITInterface {
 
@@ -161,7 +162,7 @@ public static void registerJITCode(CCharPointer addr, @CUnsigned long size, JITC
 
         /* Insert entry at head of the list. */
         JITCodeEntry nextEntry = jitDebugDescriptor.get().getFirstEntry();
-        entry.setPrevEntry(WordFactory.nullPointer());
+        entry.setPrevEntry(Word.nullPointer());
         entry.setNextEntry(nextEntry);
 
         if (nextEntry.isNonNull()) {
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 ece44e6ad024..057f5ceafa89 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
@@ -93,6 +93,7 @@
 import jdk.vm.ci.code.RegisterConfig;
 import jdk.vm.ci.code.RegisterValue;
 import jdk.vm.ci.code.StackSlot;
+import jdk.vm.ci.code.ValueUtil;
 import jdk.vm.ci.meta.AllocatableValue;
 import jdk.vm.ci.meta.JavaConstant;
 import jdk.vm.ci.meta.JavaKind;
@@ -107,7 +108,6 @@
 import jdk.vm.ci.meta.ResolvedJavaMethod;
 import jdk.vm.ci.meta.ResolvedJavaType;
 import jdk.vm.ci.meta.Signature;
-import jdk.vm.ci.meta.Value;
 
 /**
  * A shared base for providing debug info that can be processed by any debug info format specified
@@ -627,7 +627,7 @@ public SortedSet<LocalEntry> getParamEntries(SharedMethod method, int line) {
         Signature signature = method.getSignature();
         int parameterCount = signature.getParameterCount(false);
         SortedSet<LocalEntry> paramInfos = new TreeSet<>(Comparator.comparingInt(LocalEntry::slot));
-        LocalVariableTable table = method.getLocalVariableTable();
+        LocalVariableTable lvt = method.getLocalVariableTable();
         int slot = 0;
         SharedType ownerType = (SharedType) method.getDeclaringClass();
         if (!method.isStatic()) {
@@ -637,7 +637,14 @@ public SortedSet<LocalEntry> getParamEntries(SharedMethod method, int line) {
             slot += kind.getSlotCount();
         }
         for (int i = 0; i < parameterCount; i++) {
-            Local local = table == null ? null : table.getLocal(slot, 0);
+            Local local = null;
+            if (lvt != null) {
+                try {
+                    local = lvt.getLocal(slot, 0);
+                } catch (IllegalStateException e) {
+                    debug.log("Found invalid local variable table from method %s during debug info generation.", method.getName());
+                }
+            }
             SharedType paramType = (SharedType) signature.getParameterType(i, null);
             JavaKind kind = paramType.getJavaKind();
             JavaKind storageKind = paramType.getStorageKind();
@@ -743,17 +750,19 @@ public FileEntry lookupFileEntry(ResolvedJavaField field) {
     }
 
     public FileEntry lookupFileEntry(Path fullFilePath) {
-        if (fullFilePath == null || fullFilePath.getFileName() == null) {
+        if (fullFilePath == null) {
+            return null;
+        }
+        Path fileName = fullFilePath.getFileName();
+        if (fileName == null) {
             return null;
         }
 
-        String fileName = fullFilePath.getFileName().toString();
         Path dirPath = fullFilePath.getParent();
-
         DirEntry dirEntry = lookupDirEntry(dirPath);
 
         /* Reuse any existing entry if available. */
-        FileEntry fileEntry = fileIndex.computeIfAbsent(fullFilePath, path -> new FileEntry(fileName, dirEntry));
+        FileEntry fileEntry = fileIndex.computeIfAbsent(fullFilePath, path -> new FileEntry(fileName.toString(), dirEntry));
         assert dirPath == null || fileEntry.dirEntry() != null && fileEntry.dirEntry().path().equals(dirPath);
         return fileEntry;
     }
@@ -1072,7 +1081,7 @@ public Range process(CompilationResultFrameTree.FrameNode node, CallRange caller
         }
     }
 
-    protected Map<LocalEntry, LocalValueEntry> initLocalInfoList(BytecodePosition pos, MethodEntry method, int frameSize) {
+    protected Map<LocalEntry, LocalValueEntry> initLocalInfoList(BytecodePosition pos, MethodEntry methodEntry, int frameSize) {
         Map<LocalEntry, LocalValueEntry> localInfos = new HashMap<>();
 
         if (pos instanceof BytecodeFrame frame && frame.numLocals > 0) {
@@ -1085,19 +1094,20 @@ protected Map<LocalEntry, LocalValueEntry> initLocalInfoList(BytecodePosition po
              * with a synthesized name, the type according to the JavaKind (Object for all
              * classes/array types) and the line of the current bytecode position
              */
-            LocalVariableTable lvt = pos.getMethod().getLocalVariableTable();
-            LineNumberTable lnt = pos.getMethod().getLineNumberTable();
+            SharedMethod method = (SharedMethod) pos.getMethod();
+            LocalVariableTable lvt = method.getLocalVariableTable();
+            LineNumberTable lnt = method.getLineNumberTable();
             int line = lnt == null ? 0 : lnt.getLineNumber(pos.getBCI());
 
             // the owner type to resolve the local types against
-            SharedType ownerType = (SharedType) pos.getMethod().getDeclaringClass();
+            SharedType ownerType = (SharedType) method.getDeclaringClass();
 
             for (int slot = 0; slot < frame.numLocals; slot++) {
                 // Read locals from frame by slot - this might be an Illegal value
                 JavaValue value = frame.getLocalValue(slot);
                 JavaKind storageKind = frame.getLocalValueKind(slot);
 
-                if (value == Value.ILLEGAL) {
+                if (ValueUtil.isIllegalJavaValue(value)) {
                     /*
                      * If we have an illegal value, also the storage kind must be Illegal. We don't
                      * have any value, so we have to continue with the next slot.
@@ -1109,14 +1119,22 @@ protected Map<LocalEntry, LocalValueEntry> initLocalInfoList(BytecodePosition po
                 /*
                  * We might not have a local variable table at all, which means we can only use the
                  * frame local value. Even if there is a local variable table, there might not be a
-                 * local at this slot in the local variable table.
+                 * local at this slot in the local variable table. We also need to check if the
+                 * local variable table is malformed.
                  */
-                Local local = lvt == null ? null : lvt.getLocal(slot, pos.getBCI());
+                Local local = null;
+                if (lvt != null) {
+                    try {
+                        local = lvt.getLocal(slot, pos.getBCI());
+                    } catch (IllegalStateException e) {
+                        debug.log("Found invalid local variable table from method %s during debug info generation.", method.getName());
+                    }
+                }
 
                 String name;
                 SharedType type;
                 if (local == null) {
-                    if (method.getLastParamSlot() >= slot) {
+                    if (methodEntry.getLastParamSlot() >= slot) {
                         /*
                          * If we e.g. get an int from the frame values can we really be sure that
                          * this is a param and not just any other local value that happens to be an
@@ -1132,7 +1150,7 @@ protected Map<LocalEntry, LocalValueEntry> initLocalInfoList(BytecodePosition po
                      * We don't have a corresponding local in the local variable table. Collect some
                      * usable information for this local from the frame local kind.
                      */
-                    name = "__" + storageKind.getJavaName() + (method.isStatic() ? slot : slot - 1);
+                    name = "__" + storageKind.getJavaName() + (methodEntry.isStatic() ? slot : slot - 1);
                     Class<?> clazz = storageKind.isObject() ? Object.class : storageKind.toJavaClass();
                     type = (SharedType) metaAccess.lookupJavaType(clazz);
                 } else {
@@ -1159,7 +1177,7 @@ protected Map<LocalEntry, LocalValueEntry> initLocalInfoList(BytecodePosition po
                      * Lookup a LocalEntry from the MethodEntry. If the LocalEntry was already read
                      * upfront from the local variable table, the LocalEntry already exists.
                      */
-                    LocalEntry localEntry = method.lookupLocalEntry(name, slot, typeEntry, line);
+                    LocalEntry localEntry = methodEntry.lookupLocalEntry(name, slot, typeEntry, line);
                     LocalValueEntry localValueEntry = createLocalValueEntry(value, frameSize);
                     if (localEntry != null && localValueEntry != null) {
                         localInfos.put(localEntry, localValueEntry);
diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/GraalGraphObjectReplacer.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/GraalGraphObjectReplacer.java
index c40a6bcbec15..13e85c24f0c1 100644
--- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/GraalGraphObjectReplacer.java
+++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/GraalGraphObjectReplacer.java
@@ -29,8 +29,6 @@
 import java.util.concurrent.ConcurrentMap;
 import java.util.function.Function;
 
-import jdk.vm.ci.meta.Local;
-import jdk.vm.ci.meta.LocalVariableTable;
 import org.graalvm.nativeimage.c.function.RelocatedPointer;
 import org.graalvm.nativeimage.hosted.Feature.BeforeHeapLayoutAccess;
 
@@ -72,6 +70,7 @@
 import com.oracle.svm.hosted.meta.HostedMethod;
 import com.oracle.svm.hosted.meta.HostedType;
 import com.oracle.svm.hosted.meta.HostedUniverse;
+import com.oracle.svm.util.LogUtils;
 import com.oracle.svm.util.ReflectionUtil;
 
 import jdk.graal.compiler.api.replacements.SnippetReflectionProvider;
@@ -91,6 +90,8 @@
 import jdk.vm.ci.hotspot.HotSpotJVMCIRuntime;
 import jdk.vm.ci.meta.JavaConstant;
 import jdk.vm.ci.meta.JavaType;
+import jdk.vm.ci.meta.Local;
+import jdk.vm.ci.meta.LocalVariableTable;
 import jdk.vm.ci.meta.MetaAccessProvider;
 import jdk.vm.ci.meta.ResolvedJavaField;
 import jdk.vm.ci.meta.ResolvedJavaMethod;
@@ -281,7 +282,14 @@ public synchronized SubstrateMethod createMethod(ResolvedJavaMethod original) {
                  * The links to other meta objects must be set after adding to the methods to avoid
                  * infinite recursion.
                  */
-                sMethod.setLinks(createSignature(aMethod.getSignature()), createType(aMethod.getDeclaringClass()), createLocalVariableTable(aMethod.getLocalVariableTable()));
+                LocalVariableTable localVariableTable;
+                try {
+                    localVariableTable = createLocalVariableTable(aMethod.getLocalVariableTable());
+                } catch (IllegalStateException e) {
+                    LogUtils.warning("Omit invalid local variable table from method %s", sMethod.getName());
+                    localVariableTable = null;
+                }
+                sMethod.setLinks(createSignature(aMethod.getSignature()), createType(aMethod.getDeclaringClass()), localVariableTable);
             }
         }
         return sMethod;
@@ -419,6 +427,12 @@ private synchronized LocalVariableTable createLocalVariableTable(LocalVariableTa
             Local[] newLocals = new Local[origLocals.length];
             for (int i = 0; i < newLocals.length; ++i) {
                 Local origLocal = origLocals[i];
+                /*
+                 * Check if the local variable table is malformed. This throws an
+                 * IllegalStateException if the bci ranges of variables overlap and the malformed
+                 * local variable table is omitted from the image.
+                 */
+                original.getLocal(origLocal.getSlot(), origLocal.getStartBCI());
                 JavaType origType = origLocal.getType();
                 SubstrateType newType = createType(origType);
                 newLocals[i] = new Local(origLocal.getName(), newType, origLocal.getStartBCI(), origLocal.getEndBCI(), origLocal.getSlot());
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 fc089d3000d3..768094e92bc3 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
@@ -196,10 +196,9 @@ protected void handleDataInfo(Object data) {
                 long size = objectInfo.getSize();
                 String typeName = objectInfo.getClazz().toJavaName();
                 ImageHeapPartition partition = objectInfo.getPartition();
-                String partitionName = partition.getName() + "{" + partition.getSize() + "}@" + partition.getStartOffset();
-                String provenance = objectInfo.toString();
 
-                debug.log(DebugContext.INFO_LEVEL, "Data: offset 0x%x size 0x%x type %s partition %s provenance %s ", offset, size, typeName, partitionName, provenance);
+                debug.log(DebugContext.INFO_LEVEL, "Data: offset 0x%x size 0x%x type %s partition %s{%d}@%d provenance %s ", offset, size, typeName, partition.getName(), partition.getSize(),
+                                partition.getStartOffset(), objectInfo);
             } catch (Throwable e) {
                 throw debug.handle(e);
             }
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/src/com/oracle/svm/test/debug/helper/test_runtime_deopt.py
index 1069b2a1a70c..0c6a379e8f1e 100644
--- 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/src/com/oracle/svm/test/debug/helper/test_runtime_deopt.py
@@ -104,7 +104,7 @@ def test_type_signature_fallback_full(self):
         self.assertNotIn('this=<unknown type in <in-memory@', backtrace)
 
     # this should not work with just main objfile type signature fallback
-    # the main objfile is the js launcher which has no debuggin symbols
+    # the main objfile is the js launcher which has no debugging symbols
     def test_type_signature_fallback_main(self):
         # stop at a method where we know that the runtime compiled frame is in the backtrace
         gdb_execute("maintenance set dwarf type-signature-fallback main")

From 693f353265fbc096a52effe7f2e21bd57083a727 Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Wed, 15 Jan 2025 17:07:51 +0100
Subject: [PATCH 23/34] Bugfixes, Code Cleanup and fix style issue

---
 substratevm/mx.substratevm/mx_substratevm.py  | 187 ++++++++++--------
 .../objectfile/debugentry/DebugInfoBase.java  |   3 +-
 .../objectfile/debugentry/MethodEntry.java    |  34 ++--
 .../objectfile/elf/dwarf/DwarfDebugInfo.java  |  24 +--
 .../com/oracle/svm/core/SubstrateOptions.java |  10 +-
 .../code/InstalledCodeObserverSupport.java    |  17 +-
 .../core/debug/SharedDebugInfoProvider.java   |  32 +--
 .../debug/SubstrateDebugInfoInstaller.java    |  22 ++-
 .../debug/SubstrateDebugInfoProvider.java     |   7 +-
 .../oracle/svm/graal/SubstrateGraalUtils.java |   2 +-
 .../image/NativeImageDebugInfoProvider.java   |   7 +-
 .../debug/helper/test_runtime_compilation.py  |  40 ++--
 12 files changed, 207 insertions(+), 178 deletions(-)

diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py
index 3a695b4735f4..f906429d8945 100644
--- a/substratevm/mx.substratevm/mx_substratevm.py
+++ b/substratevm/mx.substratevm/mx_substratevm.py
@@ -856,79 +856,6 @@ def _cinterfacetutorial(native_image, args=None):
     mx.run([join(build_dir, 'cinterfacetutorial')])
 
 
-def _runtimedebuginfotest(native_image, args=None):
-    """Build and run the runtimedebuginfotest"""
-
-    args = [] if args is None else args
-    build_dir = join(svmbuild_dir(), 'runtimedebuginfotest')
-
-    test_proj = mx.dependency('com.oracle.svm.test')
-    test_source_path = test_proj.source_dirs()[0]
-
-    test_python_source_dir = join(test_source_path, 'com', 'oracle', 'svm', 'test', 'debug', 'helper')
-    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')
-
-    gdb_args = [
-        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
-        '-iex', "set logging redirect on",
-        '-iex', "set logging overwrite off",
-    ]
-
-    # clean / create output directory
-    if exists(build_dir):
-        mx.rmtree(build_dir)
-    mx_util.ensure_dir_exists(build_dir)
-
-    # Build the native image from Java code
-    runtime_compile_binary = native_image([
-                     '-g', '-O0', '--no-fallback',
-                     '-o', join(build_dir, 'runtimedebuginfotest'),
-                     '-cp', classpath('com.oracle.svm.test'),
-                     '--features=com.oracle.svm.test.debug.RuntimeCompileDebugInfoTest$RegisterMethodsFeature',
-                     'com.oracle.svm.test.debug.RuntimeCompileDebugInfoTest',
-                 ] + svm_experimental_options([
-        '-H:DebugInfoSourceSearchPath=' + mx.project('com.oracle.svm.test').source_dirs()[0],
-        ]) + args)
-
-    logfile = join(build_dir, 'test_runtime_compilation.log')
-    os.environ.update({'gdb_logfile': logfile})
-    gdb_command = gdb_args + [
-        '-iex', f"set logging file {logfile}",
-        '-iex', "set logging enabled on",
-        '-iex', f"set auto-load safe-path {join(build_dir, 'gdb-debughelpers.py')}",
-        '-x', test_runtime_compilation_py, runtime_compile_binary
-    ]
-    mx.log(' '.join([(f"'{c}'" if ' ' in c else c) for c in gdb_command]))
-    # unittest may result in different exit code, nonZeroIsFatal ensures that we can go on with other test
-    status = mx.run(gdb_command, cwd=build_dir, nonZeroIsFatal=False)
-
-    # Start the native image
-    # mx.run([join(build_dir, 'runtimedebuginfotest')])
-
-    jslib = mx.add_lib_suffix(native_image(['-g', '-O0', '-J-Xmx16g', '--macro:jsvm-library']))
-    js_launcher = get_js_launcher(jslib)
-
-    testdeopt_js = join(suite.dir, 'mx.substratevm', 'testdeopt.js')
-
-    logfile = join(build_dir, 'test_runtime_deopt.log')
-    os.environ.update({'gdb_logfile': logfile})
-    gdb_command = gdb_args + [
-        '-iex', f"set logging file {logfile}",
-        '-iex', "set logging enabled on",
-        '-iex', f"set auto-load safe-path {join(build_dir, 'gdb-debughelpers.py')}",
-        '-x', test_runtime_deopt_py, '--args', js_launcher, testdeopt_js
-    ]
-    mx.log(' '.join([(f"'{c}'" if ' ' in c else c) for c in gdb_command]))
-    # unittest may result in different exit code, nonZeroIsFatal ensures that we can go on with other test
-    status |= mx.run(gdb_command, cwd=build_dir, nonZeroIsFatal=False)
-
-    return status
-
-
 _helloworld_variants = {
     'traditional': '''
 public class HelloWorld {
@@ -1195,7 +1122,7 @@ def run_debug_test(image_name: str, testfile: str, source_path: str, with_isolat
             else:
                 build_args += ['-o', join(build_dir, image_name)]
 
-            mx.log(f"native_image {' '.join(build_args)}")
+            mx.log(f"native-image {' '.join(build_args)}")
             native_image(build_args)
 
             if build_cinterfacetutorial:
@@ -1246,6 +1173,88 @@ def run_debug_test(image_name: str, testfile: str, source_path: str, with_isolat
         mx.abort(status)
 
 
+def _runtimedebuginfotest(native_image, output_path, with_isolates_only, args=None):
+    """Build and run the runtimedebuginfotest"""
+
+    args = [] if args is None else args
+
+    test_proj = mx.dependency('com.oracle.svm.test')
+    test_source_path = test_proj.source_dirs()[0]
+
+    test_python_source_dir = join(test_source_path, 'com', 'oracle', 'svm', 'test', 'debug', 'helper')
+    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')
+
+    gdb_args = [
+        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
+        '-iex', "set logging redirect on",
+        '-iex', "set logging overwrite off",
+    ]
+
+    # clean / create output directory
+    if exists(output_path):
+        mx.rmtree(output_path)
+    mx_util.ensure_dir_exists(output_path)
+
+    # Build the native image from Java code
+    build_args = [
+        '-g', '-O0',
+        '-o', join(output_path, 'runtimedebuginfotest'),
+        '-cp', classpath('com.oracle.svm.test'),
+        # 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',
+        '--features=com.oracle.svm.test.debug.helper.RuntimeCompileDebugInfoTest$RegisterMethodsFeature',
+        'com.oracle.svm.test.debug.helper.RuntimeCompileDebugInfoTest',
+    ] + svm_experimental_options([
+        '-H:DebugInfoSourceSearchPath=' + test_source_path,
+        '-H:+SourceLevelDebug',
+        '-H:+RuntimeDebugInfo',
+    ]) + args
+
+    mx.log(f"native-image {' '.join(build_args)}")
+    runtime_compile_binary = native_image(build_args)
+
+    logfile = join(output_path, 'test_runtime_compilation.log')
+    os.environ.update({'gdb_logfile': logfile})
+    gdb_command = gdb_args + [
+        '-iex', f"set logging file {logfile}",
+        '-iex', "set logging enabled on",
+        '-iex', f"set auto-load safe-path {join(output_path, 'gdb-debughelpers.py')}",
+        '-x', test_runtime_compilation_py, runtime_compile_binary
+    ]
+    mx.log(' '.join([(f"'{c}'" if ' ' in c else c) for c in gdb_command]))
+    # unittest may result in different exit code, nonZeroIsFatal ensures that we can go on with other test
+    status = mx.run(gdb_command, cwd=output_path, nonZeroIsFatal=False)
+
+    jslib = mx.add_lib_suffix(native_image(
+        args +
+        svm_experimental_options([
+            '-H:+SourceLevelDebug',
+            '-H:+RuntimeDebugInfo',
+        ]) +
+        ['-g', '-O0', '--macro:jsvm-library']
+    ))
+    js_launcher = get_js_launcher(jslib)
+    testdeopt_js = join(suite.dir, 'mx.substratevm', 'testdeopt.js')
+    logfile = join(output_path, 'test_runtime_deopt.log')
+    os.environ.update({'gdb_logfile': logfile})
+    gdb_command = gdb_args + [
+        '-iex', f"set logging file {logfile}",
+        '-iex', "set logging enabled on",
+        '-iex', f"set auto-load safe-path {join(output_path, 'gdb-debughelpers.py')}",
+        '-x', test_runtime_deopt_py, '--args', js_launcher, testdeopt_js
+    ]
+    mx.log(' '.join([(f"'{c}'" if ' ' in c else c) for c in gdb_command]))
+    # unittest may result in different exit code, nonZeroIsFatal ensures that we can go on with other test
+    status |= mx.run(gdb_command, cwd=output_path, nonZeroIsFatal=False)
+
+    if status != 0:
+        mx.abort(status)
+
+
 def _javac_image(native_image, path, args=None):
     args = [] if args is None else args
     mx_util.ensure_dir_exists(path)
@@ -1836,6 +1845,28 @@ def gdbdebughelperstest(args, config=None):
         config=config
     )
 
+
+@mx.command(suite.name, 'runtimedebuginfotest', 'Runs the runtime debuginfo generation test')
+def runtimedebuginfotest(args, config=None):
+    """
+    runs a native image that compiles code and creates debug info at runtime.
+    """
+    parser = ArgumentParser(prog='mx runtimedebuginfotest')
+    all_args = ['--output-path', '--with-isolates-only']
+    masked_args = [_mask(arg, all_args) for arg in args]
+    parser.add_argument(all_args[0], metavar='<output-path>', nargs=1, help='Path of the generated image', default=[join(svmbuild_dir(), "runtimedebuginfotest")])
+    parser.add_argument(all_args[1], 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]
+    with_isolates_only = parsed.with_isolates_only
+    native_image_context_run(
+        lambda native_image, a:
+        _runtimedebuginfotest(native_image, output_path, with_isolates_only, a), unmask(parsed.image_args),
+        config=config
+    )
+
+
 @mx.command(suite_name=suite.name, command_name='helloworld', usage_msg='[options]')
 def helloworld(args, config=None):
     """
@@ -1890,14 +1921,6 @@ def cinterfacetutorial(args):
     native_image_context_run(_cinterfacetutorial, args)
 
 
-@mx.command(suite.name, 'runtimedebuginfotest', 'Runs the runtime debuginfo generation test')
-def runtimedebuginfotest(args):
-    """
-    runs a native image that compiles code and creates debug info at runtime.
-    """
-    native_image_context_run(_runtimedebuginfotest, args)
-
-
 @mx.command(suite.name, 'javaagenttest', 'Runs tests for java agent with native image')
 def java_agent_test(args):
     def build_and_run(args, binary_path, native_image, agents, agents_arg):
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java
index a6829911219a..d4b51d282b86 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java
@@ -232,7 +232,8 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) {
 
         stringTable = debugInfoProvider.getStringTable();
 
-        cachePath = debugInfoProvider.cachePath();
+        // Create the cachePath string entry which serves as base directory for source files
+        cachePath = uniqueDebugString(debugInfoProvider.cachePath());
 
         compiledMethods.addAll(debugInfoProvider.compiledMethodEntries());
         debugInfoProvider.typeEntries().forEach(typeEntry -> {
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 8b2965a056a6..2e34b451a21d 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
@@ -26,8 +26,10 @@
 
 package com.oracle.objectfile.debugentry;
 
+import java.util.Comparator;
 import java.util.List;
 import java.util.SortedSet;
+import java.util.concurrent.ConcurrentSkipListSet;
 
 public class MethodEntry extends MemberEntry {
     private final LocalEntry thisParam;
@@ -35,7 +37,7 @@ public class MethodEntry extends MemberEntry {
     private final int lastParamSlot;
     // local vars are accumulated as they are referenced sorted by slot, then name, then
     // type name. we don't currently deal handle references to locals with no slot.
-    private final SortedSet<LocalEntry> locals;
+    private final ConcurrentSkipListSet<LocalEntry> locals;
     private final boolean isDeopt;
     private boolean isInRange;
     private boolean isInlined;
@@ -44,11 +46,10 @@ public class MethodEntry extends MemberEntry {
     private final int vtableOffset;
     private final String symbolName;
 
-    @SuppressWarnings("this-escape")
     public MethodEntry(FileEntry fileEntry, int line, String methodName, StructureTypeEntry ownerType,
                     TypeEntry valueType, int modifiers, SortedSet<LocalEntry> paramInfos, LocalEntry thisParam,
                     String symbolName, boolean isDeopt, boolean isOverride, boolean isConstructor, int vtableOffset,
-                    int lastParamSlot, SortedSet<LocalEntry> locals) {
+                    int lastParamSlot, List<LocalEntry> locals) {
         super(fileEntry, line, methodName, ownerType, valueType, modifiers);
         this.paramInfos = paramInfos;
         this.thisParam = thisParam;
@@ -58,7 +59,11 @@ public MethodEntry(FileEntry fileEntry, int line, String methodName, StructureTy
         this.isConstructor = isConstructor;
         this.vtableOffset = vtableOffset;
         this.lastParamSlot = lastParamSlot;
-        this.locals = locals;
+
+        this.locals = new ConcurrentSkipListSet<>(Comparator.comparingInt(LocalEntry::slot).thenComparing(LocalEntry::name).thenComparing(le -> le.type().getTypeName()));
+        // sort by line and add all locals, such that the methods locals only contain the lowest
+        // line number
+        locals.stream().sorted(Comparator.comparingInt(LocalEntry::line)).forEach(this.locals::add);
 
         this.isInRange = false;
         this.isInlined = false;
@@ -94,27 +99,28 @@ public LocalEntry lookupLocalEntry(String name, int slot, TypeEntry type, int li
 
         if (slot <= lastParamSlot) {
             if (thisParam != null) {
-                if (thisParam.slot() == slot && thisParam.name().equals(name) && thisParam.type() == type) {
+                if (thisParam.slot() == slot && thisParam.name().equals(name) && thisParam.type().equals(type)) {
                     return thisParam;
                 }
             }
             for (LocalEntry param : paramInfos) {
-                if (param.slot() == slot && param.name().equals(name) && param.type() == type) {
+                if (param.slot() == slot && param.name().equals(name) && param.type().equals(type)) {
                     return param;
                 }
             }
         } else {
-            for (LocalEntry local : locals) {
-                if (local.slot() == slot && local.name().equals(name) && local.type() == type) {
-                    return local;
-                }
-            }
-
+            /*
+             * Add a new local entry if it was not already present in the locals list. A local is
+             * unique if it has different slot, name, and/or type. If the local is already
+             * contained, we might update the line number to the lowest occurring line number.
+             */
             LocalEntry local = new LocalEntry(name, type, slot, line);
-            synchronized (locals) {
+            if (!locals.contains(local) || locals.removeIf(le -> le.slot() == slot && le.name().equals(name) && le.type().equals(type) && le.line() > line)) {
                 locals.add(local);
+                return local;
+            } else {
+                return locals.headSet(local, true).last();
             }
-            return local;
         }
 
         /*
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java
index 1d9ad2da5c9e..79c92a78772f 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java
@@ -364,7 +364,7 @@ private DwarfMethodProperties lookupMethodProperties(MethodEntry methodEntry) {
     public void setCUIndex(ClassEntry classEntry, int idx) {
         assert idx >= 0;
         DwarfClassProperties classProperties = lookupClassProperties(classEntry);
-        assert classProperties.getTypeEntry() == classEntry;
+        assert classProperties.getTypeEntry().equals(classEntry);
         assert classProperties.cuIndex == -1 || classProperties.cuIndex == idx;
         classProperties.cuIndex = idx;
     }
@@ -372,7 +372,7 @@ public void setCUIndex(ClassEntry classEntry, int idx) {
     public int getCUIndex(ClassEntry classEntry) {
         DwarfClassProperties classProperties;
         classProperties = lookupClassProperties(classEntry);
-        assert classProperties.getTypeEntry() == classEntry;
+        assert classProperties.getTypeEntry().equals(classEntry);
         assert classProperties.cuIndex >= 0;
         return classProperties.cuIndex;
     }
@@ -380,7 +380,7 @@ public int getCUIndex(ClassEntry classEntry) {
     public void setCodeRangesIndex(ClassEntry classEntry, int idx) {
         assert idx >= 0;
         DwarfClassProperties classProperties = lookupClassProperties(classEntry);
-        assert classProperties.getTypeEntry() == classEntry;
+        assert classProperties.getTypeEntry().equals(classEntry);
         assert classProperties.codeRangesIndex == -1 || classProperties.codeRangesIndex == idx;
         classProperties.codeRangesIndex = idx;
     }
@@ -388,7 +388,7 @@ public void setCodeRangesIndex(ClassEntry classEntry, int idx) {
     public int getCodeRangesIndex(ClassEntry classEntry) {
         DwarfClassProperties classProperties;
         classProperties = lookupClassProperties(classEntry);
-        assert classProperties.getTypeEntry() == classEntry;
+        assert classProperties.getTypeEntry().equals(classEntry);
         assert classProperties.codeRangesIndex >= 0;
         return classProperties.codeRangesIndex;
     }
@@ -396,7 +396,7 @@ public int getCodeRangesIndex(ClassEntry classEntry) {
     public void setLocationListIndex(ClassEntry classEntry, int idx) {
         assert idx >= 0;
         DwarfClassProperties classProperties = lookupClassProperties(classEntry);
-        assert classProperties.getTypeEntry() == classEntry;
+        assert classProperties.getTypeEntry().equals(classEntry);
         assert classProperties.locationListIndex == 0 || classProperties.locationListIndex == idx;
         classProperties.locationListIndex = idx;
     }
@@ -404,14 +404,14 @@ public void setLocationListIndex(ClassEntry classEntry, int idx) {
     public int getLocationListIndex(ClassEntry classEntry) {
         DwarfClassProperties classProperties;
         classProperties = lookupClassProperties(classEntry);
-        assert classProperties.getTypeEntry() == classEntry;
+        assert classProperties.getTypeEntry().equals(classEntry);
         return classProperties.locationListIndex;
     }
 
     public void setLineIndex(ClassEntry classEntry, int idx) {
         assert idx >= 0;
         DwarfClassProperties classProperties = lookupClassProperties(classEntry);
-        assert classProperties.getTypeEntry() == classEntry;
+        assert classProperties.getTypeEntry().equals(classEntry);
         assert classProperties.lineIndex == -1 || classProperties.lineIndex == idx;
         classProperties.lineIndex = idx;
     }
@@ -419,7 +419,7 @@ public void setLineIndex(ClassEntry classEntry, int idx) {
     public int getLineIndex(ClassEntry classEntry) {
         DwarfClassProperties classProperties;
         classProperties = lookupClassProperties(classEntry);
-        assert classProperties.getTypeEntry() == classEntry;
+        assert classProperties.getTypeEntry().equals(classEntry);
         assert classProperties.lineIndex >= 0;
         return classProperties.lineIndex;
     }
@@ -427,7 +427,7 @@ public int getLineIndex(ClassEntry classEntry) {
     public void setLinePrologueSize(ClassEntry classEntry, int size) {
         assert size >= 0;
         DwarfClassProperties classProperties = lookupClassProperties(classEntry);
-        assert classProperties.getTypeEntry() == classEntry;
+        assert classProperties.getTypeEntry().equals(classEntry);
         assert classProperties.linePrologueSize == -1 || classProperties.linePrologueSize == size;
         classProperties.linePrologueSize = size;
     }
@@ -435,7 +435,7 @@ public void setLinePrologueSize(ClassEntry classEntry, int size) {
     public int getLinePrologueSize(ClassEntry classEntry) {
         DwarfClassProperties classProperties;
         classProperties = lookupClassProperties(classEntry);
-        assert classProperties.getTypeEntry() == classEntry;
+        assert classProperties.getTypeEntry().equals(classEntry);
         assert classProperties.linePrologueSize >= 0;
         return classProperties.linePrologueSize;
     }
@@ -443,7 +443,7 @@ public int getLinePrologueSize(ClassEntry classEntry) {
     public void setFieldDeclarationIndex(StructureTypeEntry entry, String fieldName, int pos) {
         DwarfClassProperties classProperties;
         classProperties = lookupClassProperties(entry);
-        assert classProperties.getTypeEntry() == entry;
+        assert classProperties.getTypeEntry().equals(entry);
         HashMap<String, Integer> fieldDeclarationIndex = classProperties.fieldDeclarationIndex;
         if (fieldDeclarationIndex == null) {
             classProperties.fieldDeclarationIndex = fieldDeclarationIndex = new HashMap<>();
@@ -458,7 +458,7 @@ public void setFieldDeclarationIndex(StructureTypeEntry entry, String fieldName,
     public int getFieldDeclarationIndex(StructureTypeEntry entry, String fieldName) {
         DwarfClassProperties classProperties;
         classProperties = lookupClassProperties(entry);
-        assert classProperties.getTypeEntry() == entry;
+        assert classProperties.getTypeEntry().equals(entry);
         HashMap<String, Integer> fieldDeclarationIndex = classProperties.fieldDeclarationIndex;
         assert fieldDeclarationIndex != null : fieldName;
         assert fieldDeclarationIndex.get(fieldName) != null : entry.getTypeName() + fieldName;
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java
index be23c56c1aab..618b78756312 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java
@@ -484,12 +484,12 @@ public static Path getRuntimeSourceDestDir() {
         if (sourceDestDir != null) {
             return Paths.get(sourceDestDir);
         }
-        Path executableParentDir = Paths.get(ProcessProperties.getExecutableName()).getParent();
-        if (executableParentDir != null) {
-            return executableParentDir.resolve("sources");
-        } else {
-            return Paths.get("sources");
+        Path result = Paths.get("sources");
+        Path exeDir = Paths.get(ProcessProperties.getExecutableName()).getParent();
+        if (exeDir != null) {
+            result = exeDir.resolve(result);
         }
+        return result;
     }
 
     // TODO change to false
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/InstalledCodeObserverSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/InstalledCodeObserverSupport.java
index 8a3b92ec2b51..b25952a64950 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/InstalledCodeObserverSupport.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/InstalledCodeObserverSupport.java
@@ -27,7 +27,6 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import jdk.graal.compiler.word.Word;
 import org.graalvm.nativeimage.Platform;
 import org.graalvm.nativeimage.Platforms;
 import org.graalvm.word.Pointer;
@@ -42,13 +41,17 @@
 
 import jdk.graal.compiler.code.CompilationResult;
 import jdk.graal.compiler.debug.DebugContext;
+import jdk.graal.compiler.word.Word;
 
 @AutomaticallyRegisteredImageSingleton
 public final class InstalledCodeObserverSupport {
-    private static final InstalledCodeObserverHandleAction ACTION_ATTACH = h -> getAccessor(h).attachToCurrentIsolate(h);
-    private static final InstalledCodeObserverHandleAction ACTION_DETACH = h -> getAccessor(h).detachFromCurrentIsolate(h);
-    private static final InstalledCodeObserverHandleAction ACTION_RELEASE = h -> getAccessor(h).release(h);
-    private static final InstalledCodeObserverHandleAction ACTION_ACTIVATE = h -> getAccessor(h).activate(h);
+    private static final InstalledCodeObserverHandleAction ACTION_ATTACH = (h, a, i) -> getAccessor(h).attachToCurrentIsolate(h);
+    private static final InstalledCodeObserverHandleAction ACTION_DETACH = (h, a, i) -> getAccessor(h).detachFromCurrentIsolate(h);
+    private static final InstalledCodeObserverHandleAction ACTION_RELEASE = (h, a, i) -> {
+        getAccessor(h).release(h);
+        NonmovableArrays.setWord(a, i, Word.nullPointer());
+    };
+    private static final InstalledCodeObserverHandleAction ACTION_ACTIVATE = (h, a, i) -> getAccessor(h).activate(h);
 
     private final List<InstalledCodeObserver.Factory> observerFactories = new ArrayList<>();
 
@@ -106,7 +109,7 @@ public static void removeObservers(NonmovableArray<InstalledCodeObserverHandle>
     }
 
     private interface InstalledCodeObserverHandleAction {
-        void invoke(InstalledCodeObserverHandle handle);
+        void invoke(InstalledCodeObserverHandle handle, NonmovableArray<InstalledCodeObserverHandle> observerHandles, int index);
     }
 
     private static void forEach(NonmovableArray<InstalledCodeObserverHandle> array, InstalledCodeObserverHandleAction action) {
@@ -115,7 +118,7 @@ private static void forEach(NonmovableArray<InstalledCodeObserverHandle> array,
             for (int i = 0; i < length; i++) {
                 InstalledCodeObserverHandle handle = NonmovableArrays.getWord(array, i);
                 if (handle.isNonNull()) {
-                    action.invoke(handle);
+                    action.invoke(handle, array, i);
                 }
             }
         }
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 057f5ceafa89..67f6b055ebbd 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
@@ -34,7 +34,6 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Optional;
 import java.util.SortedSet;
 import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
@@ -187,8 +186,6 @@ public abstract class SharedDebugInfoProvider implements DebugInfoProvider {
 
     /* Ensure we have a null string and in the string section. */
     protected final String uniqueNullStringEntry = stringTable.uniqueDebugString("");
-    protected final String cachePathEntry;
-
     private HeaderTypeEntry headerTypeEntry;
 
     /*
@@ -204,7 +201,7 @@ public abstract class SharedDebugInfoProvider implements DebugInfoProvider {
 
     static final Path EMPTY_PATH = Paths.get("");
 
-    public SharedDebugInfoProvider(DebugContext debug, RuntimeConfiguration runtimeConfiguration, MetaAccessProvider metaAccess, Path cachePath) {
+    public SharedDebugInfoProvider(DebugContext debug, RuntimeConfiguration runtimeConfiguration, MetaAccessProvider metaAccess) {
         this.runtimeConfiguration = runtimeConfiguration;
         this.metaAccess = metaAccess;
 
@@ -214,9 +211,6 @@ public SharedDebugInfoProvider(DebugContext debug, RuntimeConfiguration runtimeC
          */
         this.debug = debug.isLogEnabled() ? debug : DebugContext.disabled(null);
 
-        // Create the cachePath string entry which serves as base directory for source files
-        this.cachePathEntry = stringTable.uniqueDebugString(cachePath.toString());
-
         // Fetch special types that have special use cases.
         // hubType: type of the 'hub' field in the object header.
         // wordBaseType: for checking for foreign types.
@@ -275,11 +269,6 @@ public int objectAlignment() {
         return objectAlignment;
     }
 
-    @Override
-    public String cachePath() {
-        return cachePathEntry;
-    }
-
     @Override
     public StringTable getStringTable() {
         return stringTable;
@@ -495,7 +484,7 @@ protected MethodEntry installMethodEntry(SharedMethod method) {
             LocalEntry thisParam = Modifier.isStatic(modifiers) ? null : paramInfos.removeFirst();
 
             // look for locals in the methods local variable table
-            SortedSet<LocalEntry> locals = getLocalEntries(method, lastParamSlot);
+            List<LocalEntry> locals = getLocalEntries(method, lastParamSlot);
 
             String symbolName = getSymbolName(method);
             int vTableOffset = getVTableOffset(method);
@@ -583,8 +572,8 @@ public String getMethodName(SharedMethod method) {
         return name;
     }
 
-    public SortedSet<LocalEntry> getLocalEntries(SharedMethod method, int lastParamSlot) {
-        SortedSet<LocalEntry> localEntries = new TreeSet<>(Comparator.comparingInt(LocalEntry::slot).thenComparing(LocalEntry::name).thenComparingInt(LocalEntry::line));
+    public List<LocalEntry> getLocalEntries(SharedMethod method, int lastParamSlot) {
+        List<LocalEntry> localEntries = new ArrayList<>();
 
         LineNumberTable lnt = method.getLineNumberTable();
         LocalVariableTable lvt = method.getLocalVariableTable();
@@ -605,18 +594,7 @@ public SortedSet<LocalEntry> getLocalEntries(SharedMethod method, int lastParamS
                 int bciStart = local.getStartBCI();
                 int line = lnt == null ? 0 : lnt.getLineNumber(bciStart);
                 TypeEntry typeEntry = lookupTypeEntry(type);
-
-                Optional<LocalEntry> existingEntry = localEntries.stream()
-                                .filter(le -> le.slot() == slot && le.type() == typeEntry && le.name().equals(name))
-                                .findFirst();
-
-                LocalEntry localEntry = new LocalEntry(name, typeEntry, slot, line);
-                if (existingEntry.isEmpty()) {
-                    localEntries.add(localEntry);
-                } else if (existingEntry.get().line() > line) {
-                    localEntries.remove(existingEntry.get());
-                    localEntries.add(localEntry);
-                }
+                localEntries.add(new LocalEntry(name, typeEntry, slot, line));
             }
         }
 
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
index d9b0ae1afd0d..b83caba2fef4 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
@@ -33,12 +33,10 @@
 import java.util.ArrayList;
 
 import org.graalvm.nativeimage.ImageSingletons;
-import org.graalvm.nativeimage.UnmanagedMemory;
 import org.graalvm.nativeimage.c.struct.RawField;
 import org.graalvm.nativeimage.c.struct.RawStructure;
 import org.graalvm.nativeimage.c.struct.SizeOf;
 import org.graalvm.nativeimage.c.type.CCharPointer;
-import org.graalvm.nativeimage.impl.UnmanagedMemorySupport;
 import org.graalvm.word.Pointer;
 
 import com.oracle.objectfile.BasicNobitsSectionImpl;
@@ -49,6 +47,7 @@
 import com.oracle.svm.core.c.NonmovableArrays;
 import com.oracle.svm.core.code.InstalledCodeObserver;
 import com.oracle.svm.core.graal.meta.RuntimeConfiguration;
+import com.oracle.svm.core.memory.NativeMemory;
 import com.oracle.svm.core.meta.SharedMethod;
 import com.oracle.svm.core.nmt.NmtCategory;
 import com.oracle.svm.core.os.VirtualMemoryProvider;
@@ -145,8 +144,8 @@ private interface Handle extends InstalledCodeObserverHandle {
     static final class GDBJITAccessor implements InstalledCodeObserverHandleAccessor {
 
         static Handle createHandle(NonmovableArray<Byte> debugInfoData) {
-            Handle handle = UnmanagedMemory.malloc(SizeOf.get(Handle.class));
-            GDBJITInterface.JITCodeEntry entry = UnmanagedMemory.calloc(SizeOf.get(GDBJITInterface.JITCodeEntry.class));
+            Handle handle = NativeMemory.malloc(SizeOf.get(Handle.class), NmtCategory.Code);
+            GDBJITInterface.JITCodeEntry entry = NativeMemory.calloc(SizeOf.get(GDBJITInterface.JITCodeEntry.class), NmtCategory.Code);
             handle.setAccessor(ImageSingletons.lookup(GDBJITAccessor.class));
             handle.setRawHandle(entry);
             handle.setDebugInfoData(debugInfoData);
@@ -178,9 +177,9 @@ public void release(InstalledCodeObserverHandle installedCodeObserverHandle) {
                 GDBJITInterface.unregisterJITCode(entry);
                 handle.setState(Handle.RELEASED);
             }
-            ImageSingletons.lookup(UnmanagedMemorySupport.class).free(entry);
+            NativeMemory.free(entry);
             NonmovableArrays.releaseUnmanagedArray(handle.getDebugInfoData());
-            ImageSingletons.lookup(UnmanagedMemorySupport.class).free(handle);
+            NativeMemory.free(handle);
         }
 
         @Override
@@ -198,7 +197,16 @@ public void attachToCurrentIsolate(InstalledCodeObserverHandle installedCodeObse
         @Override
         @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
         public void releaseOnTearDown(InstalledCodeObserverHandle installedCodeObserverHandle) {
-            release(installedCodeObserverHandle);
+            Handle handle = (Handle) installedCodeObserverHandle;
+            GDBJITInterface.JITCodeEntry entry = handle.getRawHandle();
+            // Handle may still be just initialized here, so it never got registered in GDB.
+            if (handle.getState() == Handle.ACTIVATED) {
+                GDBJITInterface.unregisterJITCode(entry);
+                handle.setState(Handle.RELEASED);
+            }
+            NativeMemory.free(entry);
+            NonmovableArrays.releaseUnmanagedArray(handle.getDebugInfoData());
+            NativeMemory.free(handle);
         }
     }
 
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
index 4cdb0adde339..383b0bb5898f 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
@@ -58,7 +58,7 @@ public class SubstrateDebugInfoProvider extends SharedDebugInfoProvider {
 
     public SubstrateDebugInfoProvider(DebugContext debug, SharedMethod sharedMethod, CompilationResult compilation, RuntimeConfiguration runtimeConfiguration, MetaAccessProvider metaAccess,
                     long codeAddress) {
-        super(debug, runtimeConfiguration, metaAccess, SubstrateOptions.getRuntimeSourceDestDir());
+        super(debug, runtimeConfiguration, metaAccess);
         this.sharedMethod = sharedMethod;
         this.compilation = compilation;
         this.codeAddress = codeAddress;
@@ -78,6 +78,11 @@ public String getCompilationName() {
         return name + "@0x" + Long.toHexString(codeAddress);
     }
 
+    @Override
+    public String cachePath() {
+        return SubstrateOptions.getRuntimeSourceDestDir().toString();
+    }
+
     @Override
     public boolean isRuntimeCompilation() {
         return true;
diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/SubstrateGraalUtils.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/SubstrateGraalUtils.java
index 53406ed70ef7..783da21e849a 100644
--- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/SubstrateGraalUtils.java
+++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/SubstrateGraalUtils.java
@@ -96,7 +96,7 @@ public static InstalledCode compileAndInstall(SubstrateMethod method) {
         RuntimeConfiguration runtimeConfiguration = TruffleRuntimeCompilationSupport.getRuntimeConfig();
         DebugContext debug = new DebugContext.Builder(RuntimeOptionValues.singleton(), new GraalDebugHandlersFactory(runtimeConfiguration.getProviders().getSnippetReflection())).build();
         SubstrateInstalledCodeImpl installedCode = new SubstrateInstalledCodeImpl(method);
-        CompilationResult compilationResult = SubstrateGraalUtils.doCompile(debug, TruffleRuntimeCompilationSupport.getRuntimeConfig(), TruffleRuntimeCompilationSupport.getLIRSuites(), method);
+        CompilationResult compilationResult = doCompile(debug, TruffleRuntimeCompilationSupport.getRuntimeConfig(), TruffleRuntimeCompilationSupport.getLIRSuites(), method);
         RuntimeCodeInstaller.install(method, compilationResult, installedCode);
         Log.log().string("Code for " + method.format("%H.%n(%p)") + ": " + compilationResult.getTargetCodeSize() + " bytes").newline();
         return installedCode;
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 768094e92bc3..2cfef5aad13f 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
@@ -119,7 +119,7 @@ class NativeImageDebugInfoProvider extends SharedDebugInfoProvider {
 
     NativeImageDebugInfoProvider(DebugContext debug, NativeImageCodeCache codeCache, NativeImageHeap heap, NativeLibraries nativeLibs, HostedMetaAccess metaAccess,
                     RuntimeConfiguration runtimeConfiguration) {
-        super(debug, runtimeConfiguration, metaAccess, SubstrateOptions.getDebugInfoSourceCacheRoot());
+        super(debug, runtimeConfiguration, metaAccess);
         this.heap = heap;
         this.codeCache = codeCache;
         this.nativeLibs = nativeLibs;
@@ -186,6 +186,11 @@ private static ResolvedJavaMethod getAnnotatedOrOriginal(ResolvedJavaMethod meth
         return targetMethod;
     }
 
+    @Override
+    public String cachePath() {
+        return SubstrateOptions.getDebugInfoSourceCacheRoot().toString();
+    }
+
     @Override
     @SuppressWarnings("try")
     protected void handleDataInfo(Object data) {
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/src/com/oracle/svm/test/debug/helper/test_runtime_compilation.py
index dbebb4ce555e..07ce191ce5ee 100644
--- 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/src/com/oracle/svm/test/debug/helper/test_runtime_compilation.py
@@ -55,7 +55,7 @@ def tearDown(cls):
     def test_update_breakpoint(self):
         # set breakpoint in runtime compiled function and stop there
         # store initial breakpoint info for comparison
-        gdb_set_breakpoint("com.oracle.svm.test.debug.runtime.RuntimeCompilations::inlineTest")
+        gdb_set_breakpoint("com.oracle.svm.test.debug.helper.RuntimeCompilations::inlineTest")
         breakpoint_info_before = gdb_execute('info breakpoints')
         gdb_continue()
 
@@ -68,7 +68,7 @@ def test_update_breakpoint(self):
         self.assertIn(breakpoint_info_before.split('0x')[-1], breakpoint_info_after)
         # check if exactly one new correct breakpoint was added
         # new breakpoint address is always added after
-        self.assertEqual(breakpoint_info_after.split(breakpoint_info_before.split('0x')[-1])[-1].count('com.oracle.svm.test.debug.runtime.RuntimeCompilations::inlineTest'), 1)
+        self.assertEqual(breakpoint_info_after.split(breakpoint_info_before.split('0x')[-1])[-1].count('com.oracle.svm.test.debug.helper.RuntimeCompilations::inlineTest'), 1)
 
         # run until the runtime compilation is invalidated and check if the breakpoint is removed
         gdb_set_breakpoint('com.oracle.svm.graal.meta.SubstrateInstalledCodeImpl::invalidate')
@@ -77,7 +77,7 @@ def test_update_breakpoint(self):
         breakpoint_info_after_invalidation = gdb_execute('info breakpoints')
         # check if additional breakpoint is cleared after invalidate
         # breakpoint info is still printed as multi-breakpoint, thus we check if exactly one valid breakpoint is remaining
-        self.assertEqual(breakpoint_info_after_invalidation.count('com.oracle.svm.test.debug.runtime.RuntimeCompilations::inlineTest'), 1)
+        self.assertEqual(breakpoint_info_after_invalidation.count('com.oracle.svm.test.debug.helper.RuntimeCompilations::inlineTest'), 1)
         # breakpoint info must change after invalidation
         self.assertNotEquals(breakpoint_info_after, breakpoint_info_after_invalidation)
 
@@ -88,7 +88,7 @@ def test_load_objfile(self):
         self.assertFalse(any([o.filename.startswith('<in-memory@') for o in objfiles]))
 
         # set breakpoint in runtime compiled function and stop there
-        gdb_set_breakpoint("com.oracle.svm.test.debug.runtime.RuntimeCompilations::inlineTest")
+        gdb_set_breakpoint("com.oracle.svm.test.debug.helper.RuntimeCompilations::inlineTest")
         gdb_continue()
         # we are at the breakpoint, check if the objfile got registered in gdb
         objfiles = gdb.objfiles()
@@ -106,24 +106,24 @@ def test_load_objfile(self):
     # a runtime compilation should not add any new function symbols
     def test_method_signature(self):
         # check initially
-        function_info_before = gdb_execute('info function com.oracle.svm.test.debug.runtime.RuntimeCompilations::paramMethod')
+        function_info_before = gdb_execute('info function com.oracle.svm.test.debug.helper.RuntimeCompilations::paramMethod')
         # one in search term, one function symbol, one deoptimized function symbol
         self.assertEqual(function_info_before.count('paramMethod'), 3)
-        self.assertIn('java.lang.Integer *com.oracle.svm.test.debug.runtime.RuntimeCompilations::paramMethod(java.lang.Integer*, int, java.lang.String*, java.lang.Object*);', function_info_before)
+        self.assertIn('java.lang.Integer *com.oracle.svm.test.debug.helper.RuntimeCompilations::paramMethod(java.lang.Integer*, int, java.lang.String*, java.lang.Object*);', function_info_before)
 
         # set breakpoint in runtime compiled function and stop there
-        gdb_set_breakpoint("com.oracle.svm.test.debug.runtime.RuntimeCompilations::paramMethod")
+        gdb_set_breakpoint("com.oracle.svm.test.debug.helper.RuntimeCompilations::paramMethod")
         gdb_continue()  # first stops once for the AOT compiled variant
         gdb_continue()
         # ensure we did not register an extra symbol
-        function_info_after = gdb_execute('info function com.oracle.svm.test.debug.runtime.RuntimeCompilations::paramMethod')
+        function_info_after = gdb_execute('info function com.oracle.svm.test.debug.helper.RuntimeCompilations::paramMethod')
         self.assertEqual(function_info_before, function_info_after)
 
         # run until the runtime compilation is invalidated and check if the symbols still exist
         gdb_set_breakpoint('com.oracle.svm.graal.meta.SubstrateInstalledCodeImpl::invalidate')
         gdb_continue()  # run until invalidation
         gdb_finish()  # finish invalidation - this should trigger an unregister call to gdb
-        function_info_after = gdb_execute('info function com.oracle.svm.test.debug.runtime.RuntimeCompilations::paramMethod')
+        function_info_after = gdb_execute('info function com.oracle.svm.test.debug.helper.RuntimeCompilations::paramMethod')
         self.assertEqual(function_info_before, function_info_after)
 
 
@@ -145,7 +145,7 @@ def tearDown(cls):
 
     # check if initial parameter values are correct
     def test_params_method_initial(self):
-        gdb_set_breakpoint("com.oracle.svm.test.debug.runtime.RuntimeCompilations::paramMethod")
+        gdb_set_breakpoint("com.oracle.svm.test.debug.helper.RuntimeCompilations::paramMethod")
         gdb_continue()  # first stops once for the AOT compiled variant
         gdb_continue()
         self.assertEqual(gdb_output('param1'), '42')
@@ -153,7 +153,7 @@ def test_params_method_initial(self):
         self.assertEqual(gdb_output('param3'), '"test"')
         self.assertEqual(gdb_output('param4'), 'java.util.ArrayList(0)')
         this = gdb_output('this')
-        self.assertTrue(this.startswith('com.oracle.svm.test.debug.runtime.RuntimeCompilations = {'))
+        self.assertTrue(this.startswith('com.oracle.svm.test.debug.helper.RuntimeCompilations = {'))
         self.assertIn('a = 11', this)
         self.assertIn('b = 0', this)
         self.assertIn('c = null', this)
@@ -161,21 +161,21 @@ def test_params_method_initial(self):
 
     # checks if parameter types are resolved correctly from AOT debug info
     def test_param_types(self):
-        gdb_set_breakpoint("com.oracle.svm.test.debug.runtime.RuntimeCompilations::paramMethod")
+        gdb_set_breakpoint("com.oracle.svm.test.debug.helper.RuntimeCompilations::paramMethod")
         gdb_continue()  # first stops once for the AOT compiled variant
         gdb_continue()
         self.assertTrue(gdb_print_type('param1').startswith('type = class java.lang.Integer : public java.lang.Number {'))
         self.assertEquals(gdb_print_type('param2').strip(), 'type = int')  # printed type may contain newline at the end
         self.assertTrue(gdb_print_type('param3').startswith('type = class java.lang.String : public java.lang.Object {'))
         self.assertTrue(gdb_print_type('param4').startswith('type = class java.lang.Object : public _objhdr {'))
-        self.assertTrue(gdb_print_type('this').startswith('type = class com.oracle.svm.test.debug.runtime.RuntimeCompilations : public java.lang.Object {'))
+        self.assertTrue(gdb_print_type('this').startswith('type = class com.oracle.svm.test.debug.helper.RuntimeCompilations : public java.lang.Object {'))
 
     # run through paramMethod and check params after forced breakpoints
     def test_params_method(self):
-        gdb_set_breakpoint("com.oracle.svm.test.debug.runtime.RuntimeCompilations::paramMethod")
+        gdb_set_breakpoint("com.oracle.svm.test.debug.helper.RuntimeCompilations::paramMethod")
         gdb_continue()  # first stops once for the AOT compiled variant
         gdb_continue()
-        gdb_set_breakpoint("com.oracle.svm.test.debug.runtime.RuntimeCompilations::breakHere")
+        gdb_set_breakpoint("com.oracle.svm.test.debug.helper.RuntimeCompilations::breakHere")
         # step 1 set a to param1 (param1 is pinned, so it is not optimized out yet)
         gdb_continue()
         gdb_finish()
@@ -184,7 +184,7 @@ def test_params_method(self):
         self.assertEqual(gdb_output('param3'), '"test"')
         self.assertEqual(gdb_output('param4'), 'java.util.ArrayList(0)')
         this = gdb_output('this')
-        self.assertTrue(this.startswith('com.oracle.svm.test.debug.runtime.RuntimeCompilations = {'))
+        self.assertTrue(this.startswith('com.oracle.svm.test.debug.helper.RuntimeCompilations = {'))
         self.assertIn('a = 42', this)
         self.assertIn('b = 0', this)
         self.assertIn('c = null', this)
@@ -197,7 +197,7 @@ def test_params_method(self):
         self.assertEqual(gdb_output('param3'), '"test"')
         self.assertEqual(gdb_output('param4'), 'java.util.ArrayList(0)')
         this = gdb_output('this')
-        self.assertTrue(this.startswith('com.oracle.svm.test.debug.runtime.RuntimeCompilations = {'))
+        self.assertTrue(this.startswith('com.oracle.svm.test.debug.helper.RuntimeCompilations = {'))
         self.assertIn('a = 42', this)
         self.assertIn('b = 27', this)
         self.assertIn('c = null', this)
@@ -210,7 +210,7 @@ def test_params_method(self):
         self.assertEqual(gdb_output('param3'), '<optimized out>')
         self.assertEqual(gdb_output('param4'), 'java.util.ArrayList(0)')
         this = gdb_output('this')
-        self.assertTrue(this.startswith('com.oracle.svm.test.debug.runtime.RuntimeCompilations = {'))
+        self.assertTrue(this.startswith('com.oracle.svm.test.debug.helper.RuntimeCompilations = {'))
         self.assertIn('a = 42', this)
         self.assertIn('b = 27', this)
         self.assertIn('c = "test"', this)
@@ -223,7 +223,7 @@ def test_params_method(self):
         self.assertEqual(gdb_output('param3'), '<optimized out>')
         self.assertEqual(gdb_output('param4'), '<optimized out>')
         this = gdb_output('this')
-        self.assertTrue(this.startswith('com.oracle.svm.test.debug.runtime.RuntimeCompilations = {'))
+        self.assertTrue(this.startswith('com.oracle.svm.test.debug.helper.RuntimeCompilations = {'))
         self.assertIn('a = 42', this)
         self.assertIn('b = 27', this)
         self.assertIn('c = "test"', this)
@@ -231,7 +231,7 @@ def test_params_method(self):
 
     # compares params and param types of AOT and JIT compiled method
     def test_compare_AOT_to_JIT(self):
-        gdb_set_breakpoint("com.oracle.svm.test.debug.runtime.RuntimeCompilations::paramMethod")
+        gdb_set_breakpoint("com.oracle.svm.test.debug.helper.RuntimeCompilations::paramMethod")
         # first stop for the AOT compiled variant
         gdb_continue()
         this = gdb_output('this')

From 87099ff8e29d3fd48987fbd32616d29ce4d9c8d5 Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Fri, 17 Jan 2025 10:33:59 +0100
Subject: [PATCH 24/34] Code cleanup, Add comments

---
 .../objectfile/debugentry/ClassEntry.java     |  50 +-
 .../debugentry/CompiledMethodEntry.java       |   6 +-
 .../objectfile/debugentry/DebugInfoBase.java  |  19 +-
 .../objectfile/debugentry/LocalEntry.java     |  22 +-
 .../objectfile/debugentry/MemberEntry.java    |   6 +
 .../objectfile/debugentry/MethodEntry.java    |  47 +-
 .../objectfile/debugentry/range/Range.java    |  54 +-
 .../debuginfo/DebugInfoProvider.java          |   3 -
 .../elf/dwarf/DwarfInfoSectionImpl.java       |   4 +-
 .../elf/dwarf/constants/DwarfAttribute.java   |   6 +-
 .../elf/dwarf/constants/DwarfEncoding.java    |   4 +-
 .../constants/DwarfExpressionOpcode.java      |   2 +-
 .../elf/dwarf/constants/DwarfFlag.java        |   2 +-
 .../elf/dwarf/constants/DwarfForm.java        |   3 +-
 .../elf/dwarf/constants/DwarfFrameValue.java  |  12 +-
 .../elf/dwarf/constants/DwarfInline.java      |   6 +-
 .../elf/dwarf/constants/DwarfLineOpcode.java  |   6 +-
 .../com/oracle/svm/core/SubstrateOptions.java |   3 +-
 .../core/debug/SharedDebugInfoProvider.java   | 630 ++++++++++++++----
 .../debug/SubstrateDebugInfoProvider.java     |  73 +-
 .../image/NativeImageDebugInfoProvider.java   | 209 +++++-
 21 files changed, 939 insertions(+), 228 deletions(-)

diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
index 0e39b8a138b9..82911a07a8b0 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
@@ -99,17 +99,33 @@ private void addFile(FileEntry addFileEntry) {
         }
     }
 
+    /**
+     * Add a field to the class entry and store its file entry.
+     * 
+     * @param field the {@code FieldEntry} to add
+     */
     @Override
     public void addField(FieldEntry field) {
         addFile(field.getFileEntry());
         super.addField(field);
     }
 
+    /**
+     * Add a method to the class entry and store its file entry.
+     *
+     * @param methodEntry the {@code MethodEntry} to add
+     */
     public void addMethod(MethodEntry methodEntry) {
         addFile(methodEntry.getFileEntry());
         methods.add(methodEntry);
     }
 
+    /**
+     * Add a compiled method to the class entry and store its file entry and the file entries of
+     * inlined methods.
+     *
+     * @param compiledMethodEntry the {@code CompiledMethodEntry} to add
+     */
     public void addCompiledMethod(CompiledMethodEntry compiledMethodEntry) {
         addFile(compiledMethodEntry.primary().getFileEntry());
         for (Range range : compiledMethodEntry.topDownRangeStream().toList()) {
@@ -156,6 +172,18 @@ public int getFileIdx() {
         return getFileIdx(this.getFileEntry());
     }
 
+    /**
+     * Returns the file index of a given file entry within this class entry.
+     *
+     * <p>
+     * The first time a file entry is fetched, this produces a file index that is used for further
+     * index lookups. The file index is only created once. Therefore, this method must be used only
+     * after debug info generation is finished and no more file entries can be added to this class
+     * entry.
+     * 
+     * @param file the given file entry
+     * @return the index of the file entry
+     */
     public int getFileIdx(FileEntry file) {
         if (file == null || files.isEmpty() || !files.contains(file)) {
             return 0;
@@ -173,7 +201,7 @@ public int getFileIdx(FileEntry file) {
         return indexedFiles.get(file);
     }
 
-    public DirEntry getDirEntry(FileEntry file) {
+    private DirEntry getDirEntry(FileEntry file) {
         if (file == null) {
             return null;
         }
@@ -185,6 +213,18 @@ public int getDirIdx(FileEntry file) {
         return getDirIdx(dirEntry);
     }
 
+    /**
+     * Returns the dir index of a given dir entry within this class entry.
+     *
+     * <p>
+     * The first time a dir entry is fetched, this produces a dir index that is used for further
+     * index lookups. The dir index is only created once. Therefore, this method must be used only
+     * after debug info generation is finished and no more dir entries can be added to this class
+     * entry.
+     *
+     * @param dir the given dir entry
+     * @return the index of the dir entry
+     */
     public int getDirIdx(DirEntry dir) {
         if (dir == null || dir.getPathString().isEmpty() || dirs.isEmpty() || !dirs.contains(dir)) {
             return 0;
@@ -251,20 +291,20 @@ public long hipc() {
     }
 
     /**
-     * Retrieve a stream of all files referenced from debug info for this class in line info file
+     * Retrieve a list of all files referenced from debug info for this class in line info file
      * table order, starting with the file at index 1.
      *
-     * @return a stream of all referenced files
+     * @return a list of all referenced files
      */
     public List<FileEntry> getFiles() {
         return List.copyOf(files);
     }
 
     /**
-     * Retrieve a stream of all directories referenced from debug info for this class in line info
+     * Retrieve a list of all directories referenced from debug info for this class in line info
      * directory table order, starting with the directory at index 1.
      *
-     * @return a stream of all referenced directories
+     * @return a list of all referenced directories
      */
     public List<DirEntry> getDirs() {
         return List.copyOf(dirs);
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/CompiledMethodEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/CompiledMethodEntry.java
index dc0daaeb4f4f..2c154c0f54b2 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/CompiledMethodEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/CompiledMethodEntry.java
@@ -47,7 +47,7 @@ public record CompiledMethodEntry(PrimaryRange primary, List<FrameSizeChangeEntr
      * Returns a stream that traverses all the callees of the method associated with this entry. The
      * stream performs a depth-first pre-order traversal of the call tree.
      *
-     * @return the iterator
+     * @return the stream of all ranges
      */
     public Stream<Range> topDownRangeStream() {
         // skip the root of the range stream which is the primary range
@@ -59,7 +59,7 @@ public Stream<Range> topDownRangeStream() {
      * returns only the leafs. The stream performs a depth-first pre-order traversal of the call
      * tree returning only ranges with no callees.
      *
-     * @return the iterator
+     * @return the stream of leaf ranges
      */
     public Stream<Range> leafRangeStream() {
         return topDownRangeStream().filter(Range::isLeaf);
@@ -70,7 +70,7 @@ public Stream<Range> leafRangeStream() {
      * returns only the call ranges. The stream performs a depth-first pre-order traversal of the
      * call tree returning only ranges with callees.
      *
-     * @return the iterator
+     * @return the stream of call ranges
      */
     public Stream<Range> callRangeStream() {
         return topDownRangeStream().filter(range -> !range.isLeaf());
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java
index d4b51d282b86..df1e6d33d5f2 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java
@@ -155,20 +155,20 @@ public abstract class DebugInfoBase {
      */
     private ClassEntry hubClassEntry;
 
-    /*
+    /**
      * A prefix used to label indirect types used to ensure gdb performs oop reference --> raw
-     * address translation
+     * address translation.
      */
     public static final String COMPRESSED_PREFIX = "_z_.";
-    /*
+    /**
      * A prefix used for type signature generation to generate unique type signatures for type
-     * layout type units
+     * layout type units.
      */
     public static final String LAYOUT_PREFIX = "_layout_.";
 
-    /*
+    /**
      * The name of the type for header field hub which needs special case processing to remove tag
-     * bits
+     * bits.
      */
     public static final String HUB_TYPE_NAME = "java.lang.Class";
 
@@ -230,9 +230,10 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) {
         /* Reference alignment must be 8 bytes. */
         assert objectAlignment == 8;
 
-        stringTable = debugInfoProvider.getStringTable();
-
-        // Create the cachePath string entry which serves as base directory for source files
+        stringTable = new StringTable();
+        /* Ensure we have a null string at the start of the string table. */
+        uniqueDebugString("");
+        /* Create the cachePath string entry which serves as base directory for source files */
         cachePath = uniqueDebugString(debugInfoProvider.cachePath());
 
         compiledMethods.addAll(debugInfoProvider.compiledMethodEntries());
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalEntry.java
index 7a355d7b074f..606babcfd36d 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalEntry.java
@@ -25,10 +25,28 @@
 
 package com.oracle.objectfile.debugentry;
 
-public record LocalEntry(String name, TypeEntry type, int slot, int line) {
+import java.util.concurrent.atomic.AtomicInteger;
+
+public record LocalEntry(String name, TypeEntry type, int slot, AtomicInteger line) {
+
+    public LocalEntry(String name, TypeEntry type, int slot, int line) {
+        /*
+         * Use a AtomicInteger for the line number as it might change if we encounter the same local
+         * variable in a different frame state with a lower line number
+         */
+        this(name, type, slot, new AtomicInteger(line));
+    }
 
     @Override
     public String toString() {
-        return String.format("Local(%s type=%s slot=%d line=%d)", name, type.getTypeName(), slot, line);
+        return String.format("Local(%s type=%s slot=%d line=%d)", name, type.getTypeName(), slot, getLine());
+    }
+
+    public void setLine(int line) {
+        this.line.set(line);
+    }
+
+    public int getLine() {
+        return line.get();
     }
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MemberEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MemberEntry.java
index 827aa48f1623..df9160f4077e 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MemberEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MemberEntry.java
@@ -81,6 +81,12 @@ public FileEntry getFileEntry() {
         return fileEntry;
     }
 
+    /**
+     * Fetch the file index from its owner class entry with {@link ClassEntry#getFileIdx}. The file
+     * entry must only be fetched for members whose owner is a {@link ClassEntry}.
+     * 
+     * @return the file index of this members file in the owner class entry
+     */
     public int getFileIdx() {
         if (ownerType instanceof ClassEntry) {
             return ((ClassEntry) ownerType).getFileIdx(fileEntry);
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 2e34b451a21d..008aa7b5e2b4 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
@@ -61,10 +61,17 @@ public MethodEntry(FileEntry fileEntry, int line, String methodName, StructureTy
         this.lastParamSlot = lastParamSlot;
 
         this.locals = new ConcurrentSkipListSet<>(Comparator.comparingInt(LocalEntry::slot).thenComparing(LocalEntry::name).thenComparing(le -> le.type().getTypeName()));
-        // sort by line and add all locals, such that the methods locals only contain the lowest
-        // line number
-        locals.stream().sorted(Comparator.comparingInt(LocalEntry::line)).forEach(this.locals::add);
+        /*
+         * Sort by line and add all locals, such that the methods locals only contain the lowest
+         * line number.
+         */
+        locals.stream().sorted(Comparator.comparingInt(LocalEntry::getLine)).forEach(this.locals::add);
 
+        /*
+         * Flags to identify compiled methods. We set inRange if there is a compilation for this
+         * method and inlined if it is encountered as a CallNode when traversing the compilation
+         * result frame tree.
+         */
         this.isInRange = false;
         this.isInlined = false;
     }
@@ -92,6 +99,24 @@ public LocalEntry getThisParam() {
         return thisParam;
     }
 
+    /**
+     * Returns the local entry for a given name slot and type entry. Decides with the slot number
+     * whether this is a parameter or local variable.
+     *
+     * <p>
+     * Local variable might not be contained in this method entry. Creates a new local entry in
+     * {@link #locals} with the given parameters.
+     * <p>
+     * For locals, we also make sure that the line information is the lowest line encountered in
+     * local variable lookups. If a new lower line number is encountered for an existing local
+     * entry, we update the line number in the local entry.
+     * 
+     * @param name the given name
+     * @param slot the given slot
+     * @param type the given {@code TypeEntry}
+     * @param line the given line
+     * @return the local entry stored in {@link #locals}
+     */
     public LocalEntry lookupLocalEntry(String name, int slot, TypeEntry type, int line) {
         if (slot < 0) {
             return null;
@@ -115,12 +140,18 @@ public LocalEntry lookupLocalEntry(String name, int slot, TypeEntry type, int li
              * contained, we might update the line number to the lowest occurring line number.
              */
             LocalEntry local = new LocalEntry(name, type, slot, line);
-            if (!locals.contains(local) || locals.removeIf(le -> le.slot() == slot && le.name().equals(name) && le.type().equals(type) && le.line() > line)) {
-                locals.add(local);
-                return local;
-            } else {
-                return locals.headSet(local, true).last();
+            if (!locals.add(local)) {
+                /*
+                 * Fetch local from locals list. This iterates over all locals to create the head
+                 * set and then takes the last value.
+                 */
+                local = locals.headSet(local, true).last();
+                // Update line number if a lower one was encountered.
+                if (local.getLine() > line) {
+                    local.setLine(line);
+                }
             }
+            return local;
         }
 
         /*
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java
index bf79275b6ef9..67b4839d71a4 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java
@@ -97,6 +97,10 @@ public abstract class Range implements Comparable<Range> {
      * alternatively, marked as invalid in this range.
      */
     private final Map<LocalEntry, LocalValueEntry> localValueInfos;
+    /**
+     * Mapping of local entries to subranges they occur in.
+     */
+    private final Map<LocalEntry, List<Range>> varRangeMap = new HashMap<>();
 
     /**
      * Create a primary range representing the root of the subrange tree for a top level compiled
@@ -153,19 +157,31 @@ protected Range(PrimaryRange primary, MethodEntry methodEntry, Map<LocalEntry, L
         this.localValueInfos = localValueInfos;
     }
 
+    /**
+     * Splits an initial range at the given stack decrement point. The lower split will stay as it
+     * with its high offset reduced to the stack decrement point. The higher split starts at the
+     * stack decrement point and has updated local value entries for the parameters in the then
+     * extended stack.
+     * 
+     * @param stackDecrement the offset to split the range at
+     * @param frameSize the frame size after the split
+     * @param preExtendFrameSize the frame size before the split
+     * @return the higher split, that has been split off the original {@code Range}
+     */
     public Range split(int stackDecrement, int frameSize, int preExtendFrameSize) {
-        // this should be for an initial range extending beyond the stack decrement
+        // This should be for an initial range extending beyond the stack decrement.
         assert loOffset == 0 && loOffset < stackDecrement && stackDecrement < hiOffset : "invalid split request";
 
         Map<LocalEntry, LocalValueEntry> splitLocalValueInfos = new HashMap<>();
 
         for (var localInfo : localValueInfos.entrySet()) {
             if (localInfo.getValue() instanceof StackValueEntry stackValue) {
-                // need to redefine the value for this param using a stack slot value
-                // that allows for the stack being extended by framesize. however we
-                // also need to remove any adjustment that was made to allow for the
-                // difference between the caller SP and the pre-extend callee SP
-                // because of a stacked return address.
+                /*
+                 * Need to redefine the value for this param using a stack slot value that allows
+                 * for the stack being extended by framesize. however we also need to remove any
+                 * adjustment that was made to allow for the difference between the caller SP and
+                 * the pre-extend callee SP because of a stacked return address.
+                 */
                 int adjustment = frameSize - preExtendFrameSize;
                 splitLocalValueInfos.put(localInfo.getKey(), new StackValueEntry(stackValue.stackSlot() + adjustment));
             } else {
@@ -314,18 +330,34 @@ public int getDepth() {
         return depth;
     }
 
+    /**
+     * Returns the subranges grouped by local entries in the subranges. If the map is empty, it
+     * first tries to populate the map with its callees {@link #localValueInfos}.
+     * 
+     * @return a mapping from local entries to subranges
+     */
     public Map<LocalEntry, List<Range>> getVarRangeMap() {
-        HashMap<LocalEntry, List<Range>> varRangeMap = new HashMap<>();
-        for (Range callee : getCallees()) {
-            for (LocalEntry local : callee.localValueInfos.keySet()) {
-                varRangeMap.computeIfAbsent(local, l -> new ArrayList<>()).add(callee);
+        if (varRangeMap.isEmpty()) {
+            for (Range callee : getCallees()) {
+                for (LocalEntry local : callee.localValueInfos.keySet()) {
+                    varRangeMap.computeIfAbsent(local, l -> new ArrayList<>()).add(callee);
+                }
             }
         }
+
         return varRangeMap;
     }
 
+    /**
+     * Returns whether subranges contain a value in {@link #localValueInfos} for a given local
+     * entry. A value is valid if it exists, and it can be represented as local values in the debug
+     * info.
+     * 
+     * @param local the local entry to check for
+     * @return whether a local entry has a value in any of this range's subranges
+     */
     public boolean hasLocalValues(LocalEntry local) {
-        for (Range callee : getCallees()) {
+        for (Range callee : getVarRangeMap().getOrDefault(local, List.of())) {
             LocalValueEntry localValue = callee.lookupValue(local);
             if (localValue != null) {
                 if (localValue instanceof ConstantValueEntry constantValueEntry) {
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java
index 759130eb08d6..4a063510a4c5 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java
@@ -29,7 +29,6 @@
 import java.util.SortedSet;
 
 import com.oracle.objectfile.debugentry.CompiledMethodEntry;
-import com.oracle.objectfile.debugentry.StringTable;
 import com.oracle.objectfile.debugentry.TypeEntry;
 
 /**
@@ -69,8 +68,6 @@ public interface DebugInfoProvider {
      */
     int objectAlignment();
 
-    StringTable getStringTable();
-
     SortedSet<TypeEntry> typeEntries();
 
     SortedSet<CompiledMethodEntry> compiledMethodEntries();
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 0970f50bb12a..ef0a6b1d8a46 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
@@ -1217,7 +1217,7 @@ private int writeMethodParameterDeclaration(DebugContext context, LocalEntry par
         AbbrevCode abbrevCode;
         String paramName = paramInfo.name();
         TypeEntry paramType = paramInfo.type();
-        int line = paramInfo.line();
+        int line = paramInfo.getLine();
         if (artificial) {
             abbrevCode = AbbrevCode.METHOD_PARAMETER_DECLARATION_1;
         } else if (line >= 0) {
@@ -1263,7 +1263,7 @@ private int writeMethodLocalDeclaration(DebugContext context, LocalEntry paramIn
         AbbrevCode abbrevCode;
         String paramName = uniqueDebugString(paramInfo.name());
         TypeEntry paramType = paramInfo.type();
-        int line = paramInfo.line();
+        int line = paramInfo.getLine();
         if (line >= 0) {
             abbrevCode = AbbrevCode.METHOD_LOCAL_DECLARATION_1;
         } else {
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfAttribute.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfAttribute.java
index 7441dfb12be0..b6fec32e5ffe 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfAttribute.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfAttribute.java
@@ -47,16 +47,16 @@ public enum DwarfAttribute {
     DW_AT_artificial(0x34),
     DW_AT_count(0x37),
     DW_AT_data_member_location(0x38),
-    @SuppressWarnings("unused")
+    @SuppressWarnings("unused")//
     DW_AT_decl_column(0x39),
     DW_AT_decl_file(0x3a),
     DW_AT_decl_line(0x3b),
     DW_AT_declaration(0x3c),
     DW_AT_encoding(0x3e),
     DW_AT_external(0x3f),
-    @SuppressWarnings("unused")
+    @SuppressWarnings("unused")//
     DW_AT_return_addr(0x2a),
-    @SuppressWarnings("unused")
+    @SuppressWarnings("unused")//
     DW_AT_frame_base(0x40),
     DW_AT_specification(0x47),
     DW_AT_type(0x49),
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfEncoding.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfEncoding.java
index c5cb704e0022..3adbebe9716b 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfEncoding.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfEncoding.java
@@ -30,14 +30,14 @@
  * DW_AT_encoding attribute values.
  */
 public enum DwarfEncoding {
-    @SuppressWarnings("unused")
+    @SuppressWarnings("unused")//
     DW_ATE_address((byte) 0x1),
     DW_ATE_boolean((byte) 0x2),
     DW_ATE_float((byte) 0x4),
     DW_ATE_signed((byte) 0x5),
     DW_ATE_signed_char((byte) 0x6),
     DW_ATE_unsigned((byte) 0x7),
-    @SuppressWarnings("unused")
+    @SuppressWarnings("unused")//
     DW_ATE_unsigned_char((byte) 0x8);
 
     private final byte value;
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfExpressionOpcode.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfExpressionOpcode.java
index c55014c908cb..3da675084ff3 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfExpressionOpcode.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfExpressionOpcode.java
@@ -31,7 +31,7 @@
  */
 public enum DwarfExpressionOpcode {
     DW_OP_addr((byte) 0x03),
-    @SuppressWarnings("unused")
+    @SuppressWarnings("unused")//
     DW_OP_deref((byte) 0x06),
     DW_OP_dup((byte) 0x12),
     DW_OP_and((byte) 0x1a),
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfFlag.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfFlag.java
index f83fe93b8bfd..3ae00282ff4b 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfFlag.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfFlag.java
@@ -30,7 +30,7 @@
  * DW_FORM_flag only has two possible attribute values.
  */
 public enum DwarfFlag {
-    @SuppressWarnings("unused")
+    @SuppressWarnings("unused")//
     DW_FLAG_false((byte) 0),
     DW_FLAG_true((byte) 1);
 
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfForm.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfForm.java
index 042d5520a17c..1766ca8aec77 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfForm.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfForm.java
@@ -34,7 +34,8 @@ public enum DwarfForm {
     DW_FORM_addr(0x1),
     DW_FORM_data2(0x05),
     DW_FORM_data4(0x6),
-    @SuppressWarnings("unused") DW_FORM_data8(0x7),
+    @SuppressWarnings("unused")//
+    DW_FORM_data8(0x7),
     @SuppressWarnings("unused")//
     DW_FORM_string(0x8),
     @SuppressWarnings("unused")//
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfFrameValue.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfFrameValue.java
index 77ba64cecf37..992c2e942700 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfFrameValue.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfFrameValue.java
@@ -37,22 +37,22 @@ public enum DwarfFrameValue {
     DW_CFA_restore((byte) 0x3),
     /* Values encoded in low 6 bits. */
     DW_CFA_nop((byte) 0x0),
-    @SuppressWarnings("unused")
+    @SuppressWarnings("unused")//
     DW_CFA_set_loc1((byte) 0x1),
     DW_CFA_advance_loc1((byte) 0x2),
     DW_CFA_advance_loc2((byte) 0x3),
     DW_CFA_advance_loc4((byte) 0x4),
-    @SuppressWarnings("unused")
+    @SuppressWarnings("unused")//
     DW_CFA_offset_extended((byte) 0x5),
-    @SuppressWarnings("unused")
+    @SuppressWarnings("unused")//
     DW_CFA_restore_extended((byte) 0x6),
-    @SuppressWarnings("unused")
+    @SuppressWarnings("unused")//
     DW_CFA_undefined((byte) 0x7),
-    @SuppressWarnings("unused")
+    @SuppressWarnings("unused")//
     DW_CFA_same_value((byte) 0x8),
     DW_CFA_register((byte) 0x9),
     DW_CFA_def_cfa((byte) 0xc),
-    @SuppressWarnings("unused")
+    @SuppressWarnings("unused")//
     DW_CFA_def_cfa_register((byte) 0xd),
     DW_CFA_def_cfa_offset((byte) 0xe);
 
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfInline.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfInline.java
index 91498c4a1ed0..76f81bde00d3 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfInline.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfInline.java
@@ -30,12 +30,12 @@
  * Values for DW_AT_inline attribute.
  */
 public enum DwarfInline {
-    @SuppressWarnings("unused")
+    @SuppressWarnings("unused")//
     DW_INL_not_inlined((byte) 0),
     DW_INL_inlined((byte) 1),
-    @SuppressWarnings("unused")
+    @SuppressWarnings("unused")//
     DW_INL_declared_not_inlined((byte) 2),
-    @SuppressWarnings("unused")
+    @SuppressWarnings("unused")//
     DW_INL_declared_inlined((byte) 3);
 
     private final byte value;
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfLineOpcode.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfLineOpcode.java
index 2bb3269ee327..406af4ce539f 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfLineOpcode.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfLineOpcode.java
@@ -73,12 +73,12 @@ public enum DwarfLineOpcode {
     /*
      * Increment address 1 ushort arg.
      */
-    @SuppressWarnings("unused")
+    @SuppressWarnings("unused")//
     DW_LNS_set_prologue_end((byte) 10),
     /*
      * Increment address 1 ushort arg.
      */
-    @SuppressWarnings("unused")
+    @SuppressWarnings("unused")//
     DW_LNS_set_epilogue_begin((byte) 11),
     /*
      * Extended line section opcodes defined by DWARF 2.
@@ -86,7 +86,7 @@ public enum DwarfLineOpcode {
     /*
      * There is no extended opcode 0.
      */
-    @SuppressWarnings("unused")
+    @SuppressWarnings("unused")//
     DW_LNE_undefined((byte) 0),
     /*
      * End sequence of addresses.
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java
index 618b78756312..71da84ffd99b 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java
@@ -492,9 +492,8 @@ public static Path getRuntimeSourceDestDir() {
         return result;
     }
 
-    // TODO change to false
     @Option(help = "Provide debuginfo for runtime-compiled code.")//
-    public static final HostedOptionKey<Boolean> RuntimeDebugInfo = new HostedOptionKey<>(true);
+    public static final HostedOptionKey<Boolean> RuntimeDebugInfo = new HostedOptionKey<>(false);
 
     @Option(help = "Search path for C libraries passed to the linker (list of comma-separated directories)", stability = OptionStability.STABLE)//
     @BundleMember(role = BundleMember.Role.Input)//
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 67f6b055ebbd..61dc8cff9aff 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
@@ -42,24 +42,30 @@
 import org.graalvm.collections.Pair;
 import org.graalvm.word.WordBase;
 
+import com.oracle.objectfile.ObjectFile;
+import com.oracle.objectfile.debugentry.ArrayTypeEntry;
 import com.oracle.objectfile.debugentry.ClassEntry;
 import com.oracle.objectfile.debugentry.CompiledMethodEntry;
 import com.oracle.objectfile.debugentry.ConstantValueEntry;
 import com.oracle.objectfile.debugentry.DirEntry;
+import com.oracle.objectfile.debugentry.EnumClassEntry;
 import com.oracle.objectfile.debugentry.FieldEntry;
 import com.oracle.objectfile.debugentry.FileEntry;
+import com.oracle.objectfile.debugentry.ForeignTypeEntry;
 import com.oracle.objectfile.debugentry.FrameSizeChangeEntry;
 import com.oracle.objectfile.debugentry.HeaderTypeEntry;
+import com.oracle.objectfile.debugentry.InterfaceClassEntry;
 import com.oracle.objectfile.debugentry.LoaderEntry;
 import com.oracle.objectfile.debugentry.LocalEntry;
 import com.oracle.objectfile.debugentry.LocalValueEntry;
 import com.oracle.objectfile.debugentry.MethodEntry;
+import com.oracle.objectfile.debugentry.PrimitiveTypeEntry;
 import com.oracle.objectfile.debugentry.RegisterValueEntry;
 import com.oracle.objectfile.debugentry.StackValueEntry;
-import com.oracle.objectfile.debugentry.StringTable;
 import com.oracle.objectfile.debugentry.StructureTypeEntry;
 import com.oracle.objectfile.debugentry.TypeEntry;
 import com.oracle.objectfile.debugentry.range.CallRange;
+import com.oracle.objectfile.debugentry.range.LeafRange;
 import com.oracle.objectfile.debugentry.range.PrimaryRange;
 import com.oracle.objectfile.debugentry.range.Range;
 import com.oracle.objectfile.debuginfo.DebugInfoProvider;
@@ -110,49 +116,55 @@
 
 /**
  * A shared base for providing debug info that can be processed by any debug info format specified
- * in ObjectFile.
+ * in {@link ObjectFile}. Implements most of the {@link DebugInfoProvider} interface.
  *
  * <p>
- * Debug Info is provided as Debug Entries, with each entry containing Debug Info for a logical
+ * Debug Info is provided as debug entries, with each entry containing debug info for a logical
  * unit, i.e. Types, ClassLoaders, Methods, Fields, Compilations as well as the underlying file
- * system . Debug Entries model the hosted universe to provide a normalized view on debug info data
+ * system. Debug entries model the hosted universe to provide a normalized view on debug info data
  * for all debug info implementations.
  * </p>
  *
  * <ul>
- * The Debug Info contains the following Debug Entries up as follows:
- * <li>DirEntry: Represents a parent directory for one or more FileEntries, based on the debug
- * sources directory. One root directory entry contains the path to the debug sources.</li>
- * <li>FileEntry: Represents a single source file, which may be used by the debugger to provide
- * source code. During native-image build, the SourceManager safes all the processed source files
+ * The Debug Info primarily consists of the following debug entries:
+ * <li>{@link DirEntry}: Represents a parent directory for one or more file entries, based on the
+ * debug sources directory. One root directory entry contains the path to the debug sources.</li>
+ * <li>{@link FileEntry}: Represents a single source file, which may be used by the debugger to
+ * provide source code. During native-image build, the
+ * {@code com.oracle.svm.hosted.image.sources.SourceManager} safes all the processed source files
  * and provides the debug sources directory.</li>
- * <li>LoaderEntry: Represents a class loader entry. Built-in class loaders and image classloaders
- * are not stored as LoaderEntries but implicitly inferred for types with no LoaderEntry.</li>
- * <li>TypeEntry: Represents one shared type. For native image build time debug info there exists
- * one TypeEntry per type in the HostedUniverse. TypeEntries are divided into following categories:
+ * <li>{@link LoaderEntry}: Represents a {@link ClassLoader}. Built-in class loaders and image
+ * classloaders are not stored as loader entries, because they are implicitly inferred for types
+ * with no {@code LoaderEntry}.</li>
+ * <li>{@link TypeEntry}: Represents a {@link SharedType}. For native image build time debug info
+ * there exists one {@code TypeEntry} per type in the hosted universe. Type entries are divided into
+ * following categories:
  * <ul>
- * <li>PrimitiveTypeEntry: Represents a primitive java type.</li>
- * <li>HeaderTypeEntry: A special TypeEntry that represents the object header information in the
- * native image heap, as sort of a super type to Object.</li>
- * <li>ArrayTypeEntry: Represents an array type.</li>
- * <li>ForeignTypeEntry: Represents a type that is not a java class, e.g. CStruct types, CPointer
- * types, ... .</li>
- * <li>EnumClassEntry: Represents an enumeration class.</li>
- * <li>InterfaceClassEntry: Represents an interface class, and stores references to all
+ * <li>{@link PrimitiveTypeEntry}: Represents a primitive java type.</li>
+ * <li>{@link HeaderTypeEntry}: A special {@code TypeEntry} that represents the object header
+ * information in the native image heap, as sort of a super type to {@link Object}.</li>
+ * <li>{@link ArrayTypeEntry}: Represents an array type.</li>
+ * <li>{@link ForeignTypeEntry}: Represents a type that is not a java class, e.g.
+ * {@link org.graalvm.nativeimage.c.struct.CStruct CStruct},
+ * {@link org.graalvm.nativeimage.c.struct.CPointerTo CPointerTo}, ... .</li>
+ * <li>{@link EnumClassEntry}: Represents an {@link Enum} class.</li>
+ * <li>{@link InterfaceClassEntry}: Represents an interface, and stores references to all
  * implementors.</li>
- * <li>ClassEntry: Represents any other java class that is not already covered by other type entries
- * (Instance classes).</li>
+ * <li>{@link ClassEntry}: Represents any other java class that is not already covered by other type
+ * entries (Instance classes).</li>
  * </ul>
  * </li>
- * <li>MethodEntry: Represents a method declaration and holds a list of all parameters and locals
- * that are used within the method.</li>
- * <li>CompiledMethodEntry: Represents a compilation. Is composed of ranges, i.e. frame states and
- * location information of params and locals (where variables are stored). A CompiledMethodEntry
- * always has a PrimaryRange that spans the whole compilation, which is further composed of:
+ * <li>{@link MethodEntry}: Represents the method declaration of a {@link SharedMethod} and holds a
+ * list of all parameters and locals that are used within the method.</li>
+ * <li>{@link CompiledMethodEntry}: Represents a {@link CompilationResult}. Is composed of ranges,
+ * i.e. frame states and location information of params and locals (where variables are stored). A
+ * {@code CompiledMethodEntry} always has a {@link PrimaryRange} that spans the whole compilation,
+ * which is further composed of:
  * <ul>
- * <li>LeafRange: A leaf in the compilation tree.</li>
- * <li>CallRange: A CallNode in the compilation tree. Represents inlined calls and is therefore
- * itself composed of ranges.</li>
+ * <li>{@link LeafRange}: A leaf in the {@link CompilationResultFrameTree}.</li>
+ * <li>{@link CallRange}: A {@link CompilationResultFrameTree.CallNode CallNode} in the
+ * {@link CompilationResultFrameTree}. Represents inlined calls and is therefore itself composed of
+ * ranges.</li>
  * </ul>
  * </li>
  * </ul>
@@ -172,30 +184,72 @@ public abstract class SharedDebugInfoProvider implements DebugInfoProvider {
     protected final int objectAlignment;
     protected final int reservedBitsMask;
 
+    /**
+     * The {@code SharedType} for {@link Class}. This is the type that represents a dynamic hub in
+     * the native image.
+     */
     protected final SharedType hubType;
+
+    /**
+     * The {@code SharedType} for {@link WordBase}. This is used to check for foreign types.
+     */
     protected final SharedType wordBaseType;
+
+    /**
+     * The {@code SharedType} for {@link Void}. This is used as fallback for foreign pinter types,
+     * if there is no type it points to.
+     */
     protected final SharedType voidType;
 
+    /**
+     * An index map that holds all unique dir entries used for file entries in the
+     * {@link #fileIndex}.
+     */
     private final ConcurrentHashMap<Path, DirEntry> dirIndex = new ConcurrentHashMap<>();
+
+    /**
+     * An index map that holds all unique file entries used for type entries and method entries in
+     * the {@link #typeIndex} and {@link #methodIndex}.
+     */
     private final ConcurrentHashMap<Path, FileEntry> fileIndex = new ConcurrentHashMap<>();
+
+    /**
+     * An index map that holds all unique loader entries used for type entries in the
+     * {@link #typeIndex}.
+     */
     private final ConcurrentHashMap<String, LoaderEntry> loaderIndex = new ConcurrentHashMap<>();
+
+    /**
+     * An index map that holds all unique type entries except the {@link #headerTypeEntry}.
+     */
     private final ConcurrentHashMap<SharedType, TypeEntry> typeIndex = new ConcurrentHashMap<>();
+
+    /**
+     * An index map that holds all unique method entries used for type entries compiled method
+     * entries in the {@link #typeIndex} and {@link #compiledMethodIndex}.
+     */
     private final ConcurrentHashMap<SharedMethod, MethodEntry> methodIndex = new ConcurrentHashMap<>();
+
+    /**
+     * An index map that holds all unique compiled method entries.
+     */
     private final ConcurrentHashMap<CompilationIdentifier, CompiledMethodEntry> compiledMethodIndex = new ConcurrentHashMap<>();
-    protected final StringTable stringTable = new StringTable();
 
-    /* Ensure we have a null string and in the string section. */
-    protected final String uniqueNullStringEntry = stringTable.uniqueDebugString("");
+    /**
+     * The header type entry which is used as a super class of {@link Object} in the debug info. It
+     * describes the object header of an object in the native image.
+     */
     private HeaderTypeEntry headerTypeEntry;
 
-    /*
-     * A prefix used to label indirect types used to ensure gdb performs oop reference --> raw
-     * address translation
+    /**
+     * A prefix used to label indirect types used to ensure gdb performs oop reference to raw
+     * address translation.
      */
     public static final String INDIRECT_PREFIX = "_z_.";
-    /*
-     * A prefix used for type signature generation to generate unique type signatures for type
-     * layout type units
+
+    /**
+     * A prefix used for type signature generation with {@link #getTypeSignature} to generate unique
+     * type signatures for layout type units.
      */
     public static final String LAYOUT_PREFIX = "_layout_.";
 
@@ -228,12 +282,32 @@ public SharedDebugInfoProvider(DebugContext debug, RuntimeConfiguration runtimeC
         this.reservedBitsMask = Heap.getHeap().getObjectHeader().getReservedBitsMask();
     }
 
+    /**
+     * Produces a stream of shared types that are processed in {@link #installDebugInfo}.
+     *
+     * @return A stream of all {@code SharedType} objects to process
+     */
     protected abstract Stream<SharedType> typeInfo();
 
+    /**
+     * Produces a stream of shared method and compilation pairs that are processed in
+     * {@link #installDebugInfo}.
+     *
+     * @return A stream of all compilations to process.
+     */
     protected abstract Stream<Pair<SharedMethod, CompilationResult>> codeInfo();
 
+    /**
+     * Produces a stream of data objects that are processed in {@link #installDebugInfo}.
+     *
+     * @return A stream of all data objects to process.
+     */
     protected abstract Stream<Object> dataInfo();
 
+    protected abstract long getCodeOffset(SharedMethod method);
+
+    public abstract long objectOffset(JavaConstant constant);
+
     @Override
     public boolean useHeapBase() {
         return useHeapBase;
@@ -269,22 +343,40 @@ public int objectAlignment() {
         return objectAlignment;
     }
 
-    @Override
-    public StringTable getStringTable() {
-        return stringTable;
-    }
-
+    /**
+     * Collect all type entries in {@link #typeIndex} plus the {@link #headerTypeEntry} sorted by
+     * type signature.
+     *
+     * <p>
+     * This ensures that type entries are ordered when processed for the debug info object file.
+     *
+     * @return A {@code SortedSet} of all type entries found and registered in
+     *         {@link #installDebugInfo}.
+     */
     @Override
     public SortedSet<TypeEntry> typeEntries() {
         SortedSet<TypeEntry> typeEntries = new TreeSet<>(Comparator.comparingLong(TypeEntry::getTypeSignature));
-
+        /*
+         * The header type entry does not have an underlying HostedType, so we cant put it into the
+         * type index and have to add it manually.
+         */
         typeEntries.add(headerTypeEntry);
         typeEntries.addAll(typeIndex.values());
 
-        // we will always create the headerTypeEntry, as it will be used as superclass for object
         return typeEntries;
     }
 
+    /**
+     * Collect all compiled method entries in {@link #compiledMethodIndex} sorted by address of
+     * their primary range and the owner class' type signature.
+     *
+     * <p>
+     * This ensures that compiled method entries are ordered when processed for the debug info
+     * object file.
+     *
+     * @return A {@code SortedSet} of all compiled method entries found and registered in
+     *         {@link #installDebugInfo}.
+     */
     @Override
     public SortedSet<CompiledMethodEntry> compiledMethodEntries() {
         SortedSet<CompiledMethodEntry> compiledMethodEntries = new TreeSet<>(
@@ -295,7 +387,14 @@ public SortedSet<CompiledMethodEntry> compiledMethodEntries() {
         return compiledMethodEntries;
     }
 
-    /* Functions for installing debug info into the index maps. */
+    /**
+     * This installs debug info into the index maps for all entries in {@link #typeInfo},
+     * {@link #codeInfo}, and {@link #dataInfo}.
+     *
+     * <p>
+     * If logging with a {@link DebugContext} is enabled, this is done sequential, otherwise in
+     * parallel.
+     */
     @Override
     @SuppressWarnings("try")
     public void installDebugInfo() {
@@ -313,28 +412,62 @@ public void installDebugInfo() {
              * contains source file infos of compilations which are collected in the class entry.
              */
             codeStream.forEach(pair -> handleCodeInfo(pair.getLeft(), pair.getRight()));
-            typeStream.forEach(this::handleTypeInfo);
-            dataStream.forEach(this::handleDataInfo);
+            typeStream.forEach(this::installTypeInfo);
+            dataStream.forEach(this::installDataInfo);
 
             // Create the header type.
-            handleTypeInfo(null);
+            installTypeInfo(null);
         } catch (Throwable e) {
             throw debug.handle(e);
         }
     }
 
-    protected void handleDataInfo(@SuppressWarnings("unused") Object data) {
+    /**
+     * Installs debug info for any given data info. For AOT debug info generation this is used to
+     * log information for objects in the native image heap.
+     *
+     * @param data The data info to process.
+     */
+    protected void installDataInfo(@SuppressWarnings("unused") Object data) {
+        // by default, we do not use data info for installing debug info
     }
 
-    private void handleTypeInfo(SharedType type) {
+    /**
+     * Installs debug info for a given type info. A type info must be a {@code SharedType}.
+     *
+     * <p>
+     * This ensures that the type info is processed and a {@link TypeEntry} is put in the type
+     * index.
+     *
+     * @param type The {@code SharedType} to process.
+     */
+    private void installTypeInfo(SharedType type) {
+        /*
+         * Looking up a type will either return the existing type in the index map or create and
+         * process a new type entry.
+         */
         lookupTypeEntry(type);
     }
 
+    /**
+     * Installs debug info for a {@code CompilationResult} and the underlying {@code SharedMethod}.
+     *
+     * <p>
+     * This ensures that the compilation is processed and a {@link MethodEntry} and a
+     * {@link CompiledMethodEntry} are put into the method index and compiled method index
+     * respectively.
+     *
+     * <p>
+     * A compilation is processed for its frame states from infopoints/sourcemappings. For
+     * performance reasons we mostly only use infopoints for processing compilations
+     *
+     * @param method The {@code SharedMethod} to process.
+     * @param compilation The {@code CompilationResult} to process
+     */
     private void handleCodeInfo(SharedMethod method, CompilationResult compilation) {
         // First make sure the underlying MethodEntry exists.
         MethodEntry methodEntry = lookupMethodEntry(method);
-        // Then process the compilation for frame states from infopoints/sourcemappings.
-        // For performance reasons we mostly only use infopoints for processing compilations
+        // Then process the compilation.
         lookupCompiledMethodEntry(methodEntry, method, compilation);
     }
 
@@ -353,6 +486,15 @@ static boolean debugCodeInfoUseSourceMappings() {
         return SubstrateOptions.DebugCodeInfoUseSourceMappings.getValue();
     }
 
+    /**
+     * Creates a {@code CompiledMethodEntry} that holds a {@link PrimaryRange} and still needs to be
+     * processed in {@link #processCompilationInfo}.
+     *
+     * @param methodEntry the {@code MethodEntry} of the method
+     * @param method the {@code SharedMethod} of the given compilation
+     * @param compilation the given {@code CompilationResult}.
+     * @return an unprocessed {@code CompiledMethodEntry}.
+     */
     protected CompiledMethodEntry createCompilationInfo(MethodEntry methodEntry, SharedMethod method, CompilationResult compilation) {
         int primaryLine = methodEntry.getLine();
         int frameSize = compilation.getTotalFrameSize();
@@ -368,6 +510,14 @@ protected CompiledMethodEntry createCompilationInfo(MethodEntry methodEntry, Sha
         return new CompiledMethodEntry(primaryRange, frameSizeChanges, frameSize, ownerType);
     }
 
+    /**
+     * Processes a {@code CompiledMethodEntry} created in {@link #createCompilationInfo}.
+     * 
+     * @param methodEntry the {@code MethodEntry} of the method
+     * @param method the {@code SharedMethod} of the given compilation
+     * @param compilation the given {@code CompilationResult}
+     * @param compiledMethodEntry the {@code CompiledMethodEntry} to process
+     */
     protected void processCompilationInfo(MethodEntry methodEntry, SharedMethod method, CompilationResult compilation, CompiledMethodEntry compiledMethodEntry) {
         // Mark the method entry for the compilation.
         methodEntry.setInRange();
@@ -415,8 +565,16 @@ protected void processCompilationInfo(MethodEntry methodEntry, SharedMethod meth
         methodEntry.getOwnerType().addCompiledMethod(compiledMethodEntry);
     }
 
+    /**
+     * Installs a compilation info that was not found in {@link #lookupCompiledMethodEntry}.
+     * 
+     * @param methodEntry the {@code MethodEntry} of the method
+     * @param method the {@code SharedMethod} of the given compilation
+     * @param compilation the given {@code CompilationResult}
+     * @return the fully processed {@code CompiledMethodEntry} for the compilation.
+     */
     @SuppressWarnings("try")
-    protected CompiledMethodEntry installCompilationInfo(MethodEntry methodEntry, SharedMethod method, CompilationResult compilation) {
+    private CompiledMethodEntry installCompilationInfo(MethodEntry methodEntry, SharedMethod method, CompilationResult compilation) {
         try (DebugContext.Scope s = debug.scope("DebugInfoCompilation")) {
             debug.log(DebugContext.INFO_LEVEL, "Register compilation %s ", compilation.getName());
 
@@ -437,6 +595,12 @@ protected CompiledMethodEntry installCompilationInfo(MethodEntry methodEntry, Sh
         }
     }
 
+    /**
+     * Collect all frame size changes as list of {@code FrameSizeChangeEntry} for a compilation.
+     * 
+     * @param compilation the given {@code CompilationResult}
+     * @return a list of relevant frame size changes.
+     */
     public List<FrameSizeChangeEntry> getFrameSizeChanges(CompilationResult compilation) {
         List<FrameSizeChangeEntry> frameSizeChanges = new ArrayList<>();
         for (CompilationResult.CodeMark mark : compilation.getMarks()) {
@@ -460,10 +624,14 @@ public List<FrameSizeChangeEntry> getFrameSizeChanges(CompilationResult compilat
         return frameSizeChanges;
     }
 
-    protected abstract long getCodeOffset(SharedMethod method);
-
+    /**
+     * Installs a method info that was not found in {@link #lookupMethodEntry}.
+     * 
+     * @param method the {@code SharedMethod} to process
+     * @return the corresponding {@code MethodEntry}
+     */
     @SuppressWarnings("try")
-    protected MethodEntry installMethodEntry(SharedMethod method) {
+    private MethodEntry installMethodEntry(SharedMethod method) {
         try (DebugContext.Scope s = debug.scope("DebugInfoMethod")) {
             FileEntry fileEntry = lookupFileEntry(method);
 
@@ -474,11 +642,12 @@ protected MethodEntry installMethodEntry(SharedMethod method) {
             StructureTypeEntry ownerType = (StructureTypeEntry) lookupTypeEntry((SharedType) method.getDeclaringClass());
             assert ownerType instanceof ClassEntry;
             TypeEntry valueType = lookupTypeEntry((SharedType) method.getSignature().getReturnType(null));
-            int modifiers = getModifiers(method);
+            int modifiers = method.getModifiers();
 
-            // check the local variable table for parameters
-            // if the params are not in the table, we create synthetic ones from the method
-            // signature
+            /*
+             * Check the local variable table for parameters. If the params are not in the table, we
+             * create synthetic ones from the method signature.
+             */
             SortedSet<LocalEntry> paramInfos = getParamEntries(method, line);
             int lastParamSlot = paramInfos.isEmpty() ? -1 : paramInfos.getLast().slot();
             LocalEntry thisParam = Modifier.isStatic(modifiers) ? null : paramInfos.removeFirst();
@@ -501,6 +670,12 @@ protected MethodEntry installMethodEntry(SharedMethod method) {
         }
     }
 
+    /**
+     * Installs a type info that was not found in {@link #lookupTypeEntry}.
+     *
+     * @param type the {@code SharedType} to process
+     * @return a fully processed {@code TypeEntry}
+     */
     @SuppressWarnings("try")
     private TypeEntry installTypeEntry(SharedType type) {
         try (DebugContext.Scope s = debug.scope("DebugInfoType")) {
@@ -521,21 +696,37 @@ private TypeEntry installTypeEntry(SharedType type) {
         }
     }
 
+    /**
+     * Creates a {@code TypeEntry} that needs to be processed with {@link #processTypeEntry}.
+     *
+     * @param type the {@code SharedType} to process
+     * @return an unprocessed {@code TypeEntry}
+     */
     protected abstract TypeEntry createTypeEntry(SharedType type);
 
+    /**
+     * Processes a {@code TypeEntry} created in {@link #createTypeEntry}.
+     *
+     * @param type the {@code SharedType} of the type entry
+     * @param typeEntry the {@code TypeEntry} to process
+     */
     protected abstract void processTypeEntry(SharedType type, TypeEntry typeEntry);
 
-    protected void installHeaderTypeEntry() {
-        ObjectLayout ol = getObjectLayout();
-
-        int hubOffset = ol.getHubOffset();
-
+    /**
+     * Installs the header type info. This is a made up {@link TypeEntry} that has no underlying
+     * {@link SharedType} and represents the {@link ObjectLayout}.
+     */
+    private void installHeaderTypeEntry() {
         String typeName = "_objhdr";
         long typeSignature = getTypeSignature(typeName);
 
+        // create the header type entry similar to a class entry without a super type
+        ObjectLayout ol = getObjectLayout();
         headerTypeEntry = new HeaderTypeEntry(typeName, ol.getFirstFieldOffset(), typeSignature);
 
-        headerTypeEntry.addField(createSyntheticFieldEntry("hub", headerTypeEntry, hubType, hubOffset, referenceSize));
+        // add synthetic fields that hold the location of the hub and the idHash if it is in the
+        // object header
+        headerTypeEntry.addField(createSyntheticFieldEntry("hub", headerTypeEntry, hubType, ol.getHubOffset(), referenceSize));
         if (ol.isIdentityHashFieldInObjectHeader()) {
             int idHashSize = ol.sizeInBytes(JavaKind.Int);
             headerTypeEntry.addField(createSyntheticFieldEntry("idHash", headerTypeEntry, (SharedType) metaAccess.lookupJavaType(JavaKind.Int.toJavaClass()), ol.getObjectHeaderIdentityHashOffset(),
@@ -543,6 +734,17 @@ protected void installHeaderTypeEntry() {
         }
     }
 
+    /**
+     * Create a synthetic field for the debug info to add additional information as fields in the
+     * debug info.
+     * 
+     * @param name name of the field
+     * @param ownerType {@code StructureTypeEntry} that owns the field
+     * @param type {@code TypeEntry} of the fields type
+     * @param offset offset of the field
+     * @param size size of the field
+     * @return a {@code FieldEntry} that represents the synthetic field
+     */
     protected FieldEntry createSyntheticFieldEntry(String name, StructureTypeEntry ownerType, SharedType type, int offset, int size) {
         TypeEntry typeEntry = lookupTypeEntry(type);
         debug.log("typename %s adding synthetic (public) field %s type %s size %d at offset 0x%x%n",
@@ -550,6 +752,17 @@ protected FieldEntry createSyntheticFieldEntry(String name, StructureTypeEntry o
         return new FieldEntry(null, name, ownerType, typeEntry, size, offset, false, Modifier.PUBLIC);
     }
 
+    /**
+     * Create a {@code FieldEntry} for a field of a structured type.
+     * 
+     * @param fileEntry {@code FileEntry} of the source file, where the field is declared
+     * @param name name of the field
+     * @param ownerType {@code StructureTypeEntry} that owns the field
+     * @param type {@code TypeEntry} of the fields type
+     * @param offset offset of the field
+     * @param size size of the field
+     * @return a {@code FieldEntry} that represents the synthetic field
+     */
     protected FieldEntry createFieldEntry(FileEntry fileEntry, String name, StructureTypeEntry ownerType, SharedType type, int offset, int size, boolean isEmbedded, int modifier) {
         TypeEntry typeEntry = lookupTypeEntry(type);
         debug.log("typename %s adding %s field %s type %s%s size %d at offset 0x%x%n",
@@ -557,12 +770,25 @@ protected FieldEntry createFieldEntry(FileEntry fileEntry, String name, Structur
         return new FieldEntry(fileEntry, name, ownerType, typeEntry, size, offset, isEmbedded, modifier);
     }
 
-    public long getTypeSignature(String typeName) {
+    /**
+     * Produces a signature for a type name.
+     * 
+     * @param typeName name of a type
+     * @return the signature for the type name
+     */
+    protected long getTypeSignature(String typeName) {
         return Digest.digestAsUUID(typeName).getLeastSignificantBits();
     }
 
-    public String getMethodName(SharedMethod method) {
+    /**
+     * Produce a method name of a {@code SharedMethod} for the debug info.
+     * 
+     * @param method method to produce a name for
+     * @return method name for the debug info
+     */
+    protected String getMethodName(SharedMethod method) {
         String name = method.getName();
+        // replace <init> (method name of a constructor) with the class name
         if (name.equals("<init>")) {
             name = method.format("%h");
             if (name.indexOf('$') >= 0) {
@@ -572,7 +798,15 @@ public String getMethodName(SharedMethod method) {
         return name;
     }
 
-    public List<LocalEntry> getLocalEntries(SharedMethod method, int lastParamSlot) {
+    /**
+     * Fetches all locals that are no parameters from a methods {@link LocalVariableTable} and
+     * processes them into {@code LocalEntry} objects.
+     * 
+     * @param method method to fetch locals from
+     * @param lastParamSlot the highest slot number of the methods params
+     * @return a list of locals in the methods local variable table
+     */
+    private List<LocalEntry> getLocalEntries(SharedMethod method, int lastParamSlot) {
         List<LocalEntry> localEntries = new ArrayList<>();
 
         LineNumberTable lnt = method.getLineNumberTable();
@@ -601,7 +835,19 @@ public List<LocalEntry> getLocalEntries(SharedMethod method, int lastParamSlot)
         return localEntries;
     }
 
-    public SortedSet<LocalEntry> getParamEntries(SharedMethod method, int line) {
+    /**
+     * Fetches all parameters from a methods {@link Signature} and processes them into
+     * {@link LocalEntry} objects. The parameters are sorted by slot number.
+     *
+     * <p>
+     * If the parameter also exists in the methods {@link LocalVariableTable} we fetch the
+     * parameters name from there, otherwise a name is generated.
+     *
+     * @param method method to fetch parameters from
+     * @param line first line of the method in its source file
+     * @return a {@code SortedSet} of parameters in the methods signature
+     */
+    private SortedSet<LocalEntry> getParamEntries(SharedMethod method, int line) {
         Signature signature = method.getSignature();
         int parameterCount = signature.getParameterCount(false);
         SortedSet<LocalEntry> paramInfos = new TreeSet<>(Comparator.comparingInt(LocalEntry::slot));
@@ -633,10 +879,12 @@ public SortedSet<LocalEntry> getParamEntries(SharedMethod method, int line) {
         return paramInfos;
     }
 
-    public int getModifiers(SharedMethod method) {
-        return method.getModifiers();
-    }
-
+    /**
+     * Fetch a methods symbol name from the {@link UniqueShortNameProvider}.
+     * 
+     * @param method method to get the symbol name for
+     * @return symbol name of the method
+     */
     public String getSymbolName(SharedMethod method) {
         return UniqueShortNameProvider.singleton().uniqueShortName(null, method.getDeclaringClass(), method.getName(), method.getSignature(), method.isConstructor());
     }
@@ -655,14 +903,26 @@ public int getVTableOffset(SharedMethod method) {
 
     /* Lookup functions for indexed debug info entries. */
 
-    public HeaderTypeEntry lookupHeaderTypeEntry() {
+    /**
+     * Lookup the header type entry and installs it if it does not exist yet.
+     * 
+     * @return the header type entry
+     */
+    protected HeaderTypeEntry lookupHeaderTypeEntry() {
         if (headerTypeEntry == null) {
             installHeaderTypeEntry();
         }
         return headerTypeEntry;
     }
 
-    public MethodEntry lookupMethodEntry(SharedMethod method) {
+    /**
+     * Lookup a {@code MethodEntry} for a {@code SharedMethod}. If it does not exist yet, this
+     * installs the {@code MethodEntry} and updates the method list of the owner type.
+     * 
+     * @param method the given {@code SharedMethod}
+     * @return the corresponding {@code MethodEntry}
+     */
+    protected MethodEntry lookupMethodEntry(SharedMethod method) {
         if (method == null) {
             return null;
         }
@@ -675,7 +935,16 @@ public MethodEntry lookupMethodEntry(SharedMethod method) {
 
     }
 
-    public CompiledMethodEntry lookupCompiledMethodEntry(MethodEntry methodEntry, SharedMethod method, CompilationResult compilation) {
+    /**
+     * Lookup a {@code CompiledMethodEntry} for a {@code CompilationResult}. If the
+     * {@code CompiledMethodEntry} does not exist yet, it is installed.
+     *
+     * @param methodEntry the {@code MethodEntry} of the method param
+     * @param method the {@code SharedMethod} of this compilation
+     * @param compilation the given {@code CompilationResult}
+     * @return the corresponding {@code CompiledMethodEntry}
+     */
+    protected CompiledMethodEntry lookupCompiledMethodEntry(MethodEntry methodEntry, SharedMethod method, CompilationResult compilation) {
         if (method == null) {
             return null;
         }
@@ -686,7 +955,14 @@ public CompiledMethodEntry lookupCompiledMethodEntry(MethodEntry methodEntry, Sh
         return compiledMethodEntry;
     }
 
-    public TypeEntry lookupTypeEntry(SharedType type) {
+    /**
+     * Lookup a {@code TypeEntry} for a {@code SharedType}. If the {@code TypeEntry} does not exist
+     * yet, it is installed.
+     * 
+     * @param type the given {@code SharedType}
+     * @return the corresponding {@code TypeEntry}
+     */
+    protected TypeEntry lookupTypeEntry(SharedType type) {
         if (type == null) {
             // this must be the header type entry, as it is the only one with no underlying
             return lookupHeaderTypeEntry();
@@ -698,7 +974,14 @@ public TypeEntry lookupTypeEntry(SharedType type) {
         return typeEntry;
     }
 
-    public LoaderEntry lookupLoaderEntry(SharedType type) {
+    /**
+     * Lookup a {@code LoaderEntry} for a {@code SharedType}. Extracts the loader name from the
+     * {@code SharedType} and forwards to {@link #lookupLoaderEntry(String)}.
+     *
+     * @param type the given {@code SharedType}
+     * @return the corresponding {@code LoaderEntry}
+     */
+    protected LoaderEntry lookupLoaderEntry(SharedType type) {
         SharedType targetType = type;
         if (type.isArray()) {
             targetType = (SharedType) type.getElementalType();
@@ -706,28 +989,63 @@ public LoaderEntry lookupLoaderEntry(SharedType type) {
         return targetType.getHub().isLoaded() ? lookupLoaderEntry(UniqueShortNameProvider.singleton().uniqueShortLoaderName(targetType.getHub().getClassLoader())) : null;
     }
 
-    public LoaderEntry lookupLoaderEntry(String loaderName) {
+    /**
+     * Lookup a {@code LoaderEntry} for a string. If the {@code LoaderEntry} does not exist yet, it
+     * is added to the {@link #loaderIndex}.
+     *
+     * @param loaderName the given loader name string
+     * @return the corresponding {@code LoaderEntry}
+     */
+    protected LoaderEntry lookupLoaderEntry(String loaderName) {
         if (loaderName == null || loaderName.isEmpty()) {
             return null;
         }
         return loaderIndex.computeIfAbsent(loaderName, LoaderEntry::new);
     }
 
-    public FileEntry lookupFileEntry(ResolvedJavaType type) {
-        // conjure up an appropriate, unique file name to keep tools happy
-        // even though we cannot find a corresponding source
+    /**
+     * Lookup a {@code FileEntry} for a {@code ResolvedJavaType}.
+     *
+     * @param type the given {@code ResolvedJavaType}
+     * @return the corresponding {@code FileEntry}
+     */
+    protected FileEntry lookupFileEntry(ResolvedJavaType type) {
+        /*
+         * Conjure up an appropriate, unique file name to keep tools happy even though we cannot
+         * find a corresponding source.
+         */
         return lookupFileEntry(fullFilePathFromClassName(type));
     }
 
-    public FileEntry lookupFileEntry(ResolvedJavaMethod method) {
+    /**
+     * Lookup a {@code FileEntry} for the declaring class of a {@code ResolvedJavaMethod}.
+     *
+     * @param method the given {@code ResolvedJavaMethod}
+     * @return the corresponding {@code FileEntry}
+     */
+    protected FileEntry lookupFileEntry(ResolvedJavaMethod method) {
         return lookupFileEntry(method.getDeclaringClass());
     }
 
-    public FileEntry lookupFileEntry(ResolvedJavaField field) {
+    /**
+     * Lookup a {@code FileEntry} for the declaring class of a {@code ResolvedJavaField}.
+     *
+     * @param field the given {@code ResolvedJavaField}
+     * @return the corresponding {@code FileEntry}
+     */
+    protected FileEntry lookupFileEntry(ResolvedJavaField field) {
         return lookupFileEntry(field.getDeclaringClass());
     }
 
-    public FileEntry lookupFileEntry(Path fullFilePath) {
+    /**
+     * Lookup a {@code FileEntry} for a file path. First extracts the files directory and the
+     * corresponding {@link DirEntry} and adds a new {@code FileEntry} to {@link #fileIndex} if it
+     * does not exist yet.
+     *
+     * @param fullFilePath the given file path
+     * @return the corresponding {@code FileEntry}
+     */
+    protected FileEntry lookupFileEntry(Path fullFilePath) {
         if (fullFilePath == null) {
             return null;
         }
@@ -745,7 +1063,15 @@ public FileEntry lookupFileEntry(Path fullFilePath) {
         return fileEntry;
     }
 
-    public DirEntry lookupDirEntry(Path dirPath) {
+    /**
+     * Lookup a {@code DirEntry} for a directory path. Adds a new {@code DirEntry} to
+     * {@link #dirIndex} if it does not exist yet. A {@code null} path is represented by
+     * {@link #EMPTY_PATH}.
+     *
+     * @param dirPath the given directory path
+     * @return the corresponding {@code FileEntry}
+     */
+    protected DirEntry lookupDirEntry(Path dirPath) {
         return dirIndex.computeIfAbsent(dirPath == null ? EMPTY_PATH : dirPath, DirEntry::new);
     }
 
@@ -802,6 +1128,25 @@ private static int findMarkOffset(SubstrateBackend.SubstrateMarkId markId, Compi
         return -1;
     }
 
+    /**
+     * If there are any location info records then the first one will be for a nop which follows the
+     * stack decrement, stack range check and pushes of arguments into the stack frame.
+     *
+     * <p>
+     * We can construct synthetic location info covering the first instruction based on the method
+     * arguments and the calling convention and that will normally be valid right up to the nop. In
+     * exceptional cases a call might pass arguments on the stack, in which case the stack decrement
+     * will invalidate the original stack locations. Providing location info for that case requires
+     * adding two locations, one for initial instruction that does the stack decrement and another
+     * for the range up to the nop. They will be essentially the same but the stack locations will
+     * be adjusted to account for the different value of the stack pointer.
+     * 
+     * @param primary the {@code PrimaryRange} of the compilation
+     * @param locationInfos the location infos produced from the compilations frame states
+     * @param compilation the {@code CompilationResult} of a method
+     * @param method the given {@code SharedMethod}
+     * @param methodEntry the methods {@code MethodEntry}
+     */
     private void updateInitialLocation(PrimaryRange primary, List<Range> locationInfos, CompilationResult compilation, SharedMethod method, MethodEntry methodEntry) {
         int prologueEnd = findMarkOffset(SubstrateBackend.SubstrateMarkId.PROLOGUE_END, compilation);
         if (prologueEnd < 0) {
@@ -813,24 +1158,11 @@ private void updateInitialLocation(PrimaryRange primary, List<Range> locationInf
             // this is not a normal compiled method so give up
             return;
         }
-        // If there are any location info records then the first one will be for
-        // a nop which follows the stack decrement, stack range check and pushes
-        // of arguments into the stack frame.
-        //
-        // We can construct synthetic location info covering the first instruction
-        // based on the method arguments and the calling convention and that will
-        // normally be valid right up to the nop. In exceptional cases a call
-        // might pass arguments on the stack, in which case the stack decrement will
-        // invalidate the original stack locations. Providing location info for that
-        // case requires adding two locations, one for initial instruction that does
-        // the stack decrement and another for the range up to the nop. They will
-        // be essentially the same but the stack locations will be adjusted to account
-        // for the different value of the stack pointer.
-
         if (locationInfos.isEmpty()) {
             // this is not a normal compiled method so give up
             return;
         }
+
         Range firstLocation = locationInfos.getFirst();
         int firstLocationOffset = firstLocation.getLoOffset();
 
@@ -842,6 +1174,7 @@ private void updateInitialLocation(PrimaryRange primary, List<Range> locationInf
             // this is not a normal compiled method so give up
             return;
         }
+
         // create a synthetic location record including details of passed arguments
         ParamLocationProducer locProducer = new ParamLocationProducer(method);
         debug.log(DebugContext.DETAILED_LEVEL, "Add synthetic Location Info : %s (0, %d)", methodEntry.getMethodName(), firstLocationOffset - 1);
@@ -849,9 +1182,10 @@ private void updateInitialLocation(PrimaryRange primary, List<Range> locationInf
         Map<LocalEntry, LocalValueEntry> localInfoList = initSyntheticInfoList(locProducer, methodEntry);
         Range locationInfo = Range.createSubrange(primary, methodEntry, localInfoList, 0, firstLocationOffset, methodEntry.getLine(), primary, true);
 
-        // if the prologue extends beyond the stack extend and uses the stack then the info
-        // needs
-        // splitting at the extent point with the stack offsets adjusted in the new info
+        /*
+         * If the prologue extends beyond the stack extend and uses the stack then the info needs
+         * splitting at the extent point with the stack offsets adjusted in the new info.
+         */
         if (locProducer.usesStack() && firstLocationOffset > stackDecrement) {
             Range splitLocationInfo = locationInfo.split(stackDecrement, compilation.getTotalFrameSize(), PRE_EXTEND_FRAME_SIZE);
             debug.log(DebugContext.DETAILED_LEVEL, "Split synthetic Location Info : %s (0, %d) (%d, %d)", methodEntry.getMethodName(),
@@ -861,6 +1195,14 @@ private void updateInitialLocation(PrimaryRange primary, List<Range> locationInf
         locationInfos.addFirst(locationInfo);
     }
 
+    /**
+     * Creates synthetic location infos for a methods parameters that spans to the first location
+     * info from the compilations frame states.
+     * 
+     * @param locProducer the location info producer for the methods parameters
+     * @param methodEntry the given {@code MethodEntry}
+     * @return a mapping of {@code LocalEntry} to synthetic location info
+     */
     private Map<LocalEntry, LocalValueEntry> initSyntheticInfoList(ParamLocationProducer locProducer, MethodEntry methodEntry) {
         HashMap<LocalEntry, LocalValueEntry> localValueInfos = new HashMap<>();
         // Create synthetic this param info
@@ -1015,12 +1357,14 @@ public void apply(CompilationResultFrameTree.FrameNode node, Object... args) {
             }
         }
 
-        protected void handleCallNode(@SuppressWarnings("unused") CompilationResultFrameTree.CallNode callNode, @SuppressWarnings("unused") CallRange locationInfo) {
+        @SuppressWarnings("unused")
+        protected void handleCallNode(CompilationResultFrameTree.CallNode callNode, CallRange locationInfo) {
             // do nothing by default
         }
 
-        protected void handleNodeToEmbed(@SuppressWarnings("unused") CompilationResultFrameTree.CallNode nodeToEmbed, @SuppressWarnings("unused") CompilationResultFrameTree.FrameNode node,
-                        @SuppressWarnings("unused") CallRange callerInfo, @SuppressWarnings("unused") Object... args) {
+        @SuppressWarnings("unused")
+        protected void handleNodeToEmbed(CompilationResultFrameTree.CallNode nodeToEmbed, CompilationResultFrameTree.FrameNode node,
+                        CallRange callerInfo, Object... args) {
             // do nothing by default
         }
 
@@ -1059,6 +1403,16 @@ public Range process(CompilationResultFrameTree.FrameNode node, CallRange caller
         }
     }
 
+    /**
+     * Generate local info list for a location info from the local values of the current frame.
+     * Names and types of local variables are fetched from the methods local variable table. If we
+     * cant find the local in the local variable table, we use the frame information.
+     * 
+     * @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
+     * @return a mapping from {@code LocalEntry} to {@code LocalValueEntry}
+     */
     protected Map<LocalEntry, LocalValueEntry> initLocalInfoList(BytecodePosition pos, MethodEntry methodEntry, int frameSize) {
         Map<LocalEntry, LocalValueEntry> localInfos = new HashMap<>();
 
@@ -1169,6 +1523,14 @@ protected Map<LocalEntry, LocalValueEntry> initLocalInfoList(BytecodePosition po
         return localInfos;
     }
 
+    /**
+     * Creates a {@code LocalValueEntry} for a given {@code JavaValue}. This processes register
+     * values, stack values, primitive constants and constant in the heap.
+     * 
+     * @param value the given {@code JavaValue}
+     * @param frameSize the frame size for stack values
+     * @return the {@code LocalValueEntry} or {@code null} if the value can't be processed
+     */
     private LocalValueEntry createLocalValueEntry(JavaValue value, int frameSize) {
         switch (value) {
             case RegisterValue registerValue -> {
@@ -1195,8 +1557,6 @@ private LocalValueEntry createLocalValueEntry(JavaValue value, int frameSize) {
         }
     }
 
-    public abstract long objectOffset(JavaConstant constant);
-
     private static boolean isIntegralKindPromotion(JavaKind promoted, JavaKind original) {
         return (promoted == JavaKind.Int &&
                         (original == JavaKind.Boolean || original == JavaKind.Byte || original == JavaKind.Short || original == JavaKind.Char));
@@ -1219,13 +1579,13 @@ public class MultiLevelVisitor extends SingleLevelVisitor {
         @Override
         protected void handleCallNode(CompilationResultFrameTree.CallNode callNode, CallRange locationInfo) {
             if (hasChildren(callNode)) {
-                // a call node may include an initial leaf range for the call that must
-                // be
-                // embedded under the newly created location info so pass it as an
-                // argument
+                /*
+                 * A call node may include an initial leaf range for the call that must be embedded
+                 * under the newly created location info so pass it as an argument
+                 */
                 callNode.visitChildren(this, locationInfo, callNode, null);
             } else {
-                // we need to embed a leaf node for the whole call range
+                // We need to embed a leaf node for the whole call range
                 locationInfos.add(createEmbeddedParentLocationInfo(primary, callNode, null, locationInfo, frameSize));
             }
         }
@@ -1233,20 +1593,22 @@ protected void handleCallNode(CompilationResultFrameTree.CallNode callNode, Call
         @Override
         protected void handleNodeToEmbed(CompilationResultFrameTree.CallNode nodeToEmbed, CompilationResultFrameTree.FrameNode node, CallRange callerInfo, Object... args) {
             if (nodeToEmbed != null) {
-                // we only need to insert a range for the caller if it fills a gap
-                // at the start of the caller range before the first child
+                /*
+                 * We only need to insert a range for the caller if it fills a gap at the start of
+                 * the caller range before the first child.
+                 */
                 if (nodeToEmbed.getStartPos() < node.getStartPos()) {
-                    // embed a leaf range for the method start that was included in the
-                    // parent CallNode
-                    // its end range is determined by the start of the first node at this
-                    // level
+                    /*
+                     * Embed a leaf range for the method start that was included in the parent
+                     * CallNode Its end range is determined by the start of the first node at this
+                     * level.
+                     */
                     Range embeddedLocationInfo = createEmbeddedParentLocationInfo(primary, nodeToEmbed, node, callerInfo, frameSize);
                     locationInfos.add(embeddedLocationInfo);
-                    // since this is a leaf node we can merge later leafs into it
+                    // Since this is a leaf node we can merge later leafs into it.
                     args[LAST_LEAF_INFO] = embeddedLocationInfo;
                 }
-                // reset args so we only embed the parent node before the first node at
-                // this level
+                // Reset args so we only embed the parent node before the first node at this level.
                 args[PARENT_NODE_TO_EMBED] = null;
             }
         }
@@ -1296,11 +1658,19 @@ private Range createEmbeddedParentLocationInfo(PrimaryRange primary, Compilation
         return locationInfo;
     }
 
+    /**
+     * Test whether a node is a bad leaf.
+     *
+     * <p>
+     * Sometimes we see a leaf node marked as belonging to an inlined method that sits directly
+     * under the root method rather than under a call node. It needs replacing with a location info
+     * for the root method that covers the relevant code range.
+     *
+     * @param node the node to check
+     * @param callerLocation the caller location info
+     * @return true if the node is a bad leaf otherwise false
+     */
     private static boolean isBadLeaf(CompilationResultFrameTree.FrameNode node, CallRange callerLocation) {
-        // Sometimes we see a leaf node marked as belonging to an inlined method
-        // that sits directly under the root method rather than under a call node.
-        // It needs replacing with a location info for the root method that covers
-        // the relevant code range.
         if (callerLocation.isPrimary()) {
             BytecodePosition pos = node.frame;
             BytecodePosition callerPos = pos.getCaller();
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
index 383b0bb5898f..3ec817edfdfd 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
@@ -50,6 +50,18 @@
 import jdk.vm.ci.meta.MetaAccessProvider;
 import jdk.vm.ci.meta.ResolvedJavaType;
 
+/**
+ * Implements the {@link com.oracle.objectfile.debuginfo.DebugInfoProvider DebugInfoProvider}
+ * interface based on the {@code SharedDebugInfoProvider} to handle runt-time compiled methods.
+ *
+ * <p>
+ * For each run-time compilation, one {@code SubstrateDebugInfoProvider} is created and the debug
+ * info for the compiled method is installed. As type information is already available in the native
+ * image's debug info, the {@code SubstrateDebugInfoProvider} just produces as little information as
+ * needed and reuses debug info from the native image. Therefore, for type entries the
+ * {@code SubstrateDebugInfoProvider} just creates stubs that contain the type signature, which can
+ * then be resolved by the debugger.
+ */
 public class SubstrateDebugInfoProvider extends SharedDebugInfoProvider {
 
     private final SharedMethod sharedMethod;
@@ -64,6 +76,12 @@ public SubstrateDebugInfoProvider(DebugContext debug, SharedMethod sharedMethod,
         this.codeAddress = codeAddress;
     }
 
+    /**
+     * Create a compilation unit name from the {@link CompilationResult} or the {@link SharedMethod}
+     * the debug info is produced for.
+     * 
+     * @return the name of the compilation unit in the debug info
+     */
     public String getCompilationName() {
         String name = null;
         if (compilation != null) {
@@ -88,28 +106,55 @@ public boolean isRuntimeCompilation() {
         return true;
     }
 
+    /**
+     * Returns an empty stream, because there are no types to stream here. All types needed for the
+     * run-time debug info are installed when needed for providing debug info of the compilation.
+     * 
+     * @return an empty stream
+     */
     @Override
     protected Stream<SharedType> typeInfo() {
         // create type infos on demand for compilation
         return Stream.empty();
     }
 
+    /**
+     * Provides the single compilation with its corresponding method as code info for this object
+     * file. All the debug info for the object file is produced based on this compilation and
+     * method.
+     * 
+     * @return a stream containing the run-time compilation result and method
+     */
     @Override
     protected Stream<Pair<SharedMethod, CompilationResult>> codeInfo() {
         return Stream.of(Pair.create(sharedMethod, compilation));
     }
 
+    /**
+     * Returns an empty stream, no any additional data is handled for run-time compilations.
+     * 
+     * @return an empty stream
+     */
     @Override
     protected Stream<Object> dataInfo() {
-        // no data info needed for runtime compilations
+        // no data info needed for run-time compilations
         return Stream.empty();
     }
 
     @Override
     protected long getCodeOffset(@SuppressWarnings("unused") SharedMethod method) {
+        // use the code offset from the compilation
         return codeAddress;
     }
 
+    /**
+     * Fetches the package name from the types hub and the types source file name and produces a
+     * file name with that. There is no guarantee that the source file is at the location of the
+     * file entry, but it is the best guess we can make at run-time.
+     * 
+     * @param type the given {@code ResolvedJavaType}
+     * @return the {@code FileEntry} of the type
+     */
     @Override
     public FileEntry lookupFileEntry(ResolvedJavaType type) {
         if (type instanceof SharedType sharedType) {
@@ -137,13 +182,26 @@ private static int getTypeSize(SharedType type) {
         }
     }
 
+    /**
+     * Creates a {@code TypeEntry} for use in object files produced for run-time compilations.
+     * 
+     * <p>
+     * To avoid duplicating debug info, this mainly produces the {@link #getTypeSignature type
+     * signatures} to link the types to type entries produced at native image build time. Connecting
+     * the run-time compiled type entry with the native image's type entry is left for the debugger.
+     * This allows the reuse of type information from the native image, where we have more
+     * information available to produce debug info.
+     * 
+     * @param type the {@code SharedType} to process
+     * @return a {@code TypeEntry} for the type
+     */
     @Override
     protected TypeEntry createTypeEntry(SharedType type) {
         String typeName = type.toJavaName();
         LoaderEntry loaderEntry = lookupLoaderEntry(type);
         int size = getTypeSize(type);
         long classOffset = -1;
-        String loaderName = loaderEntry == null ? uniqueNullStringEntry : loaderEntry.loaderId();
+        String loaderName = loaderEntry == null ? "" : loaderEntry.loaderId();
         long typeSignature = getTypeSignature(typeName + loaderName);
         long compressedTypeSignature = useHeapBase ? getTypeSignature(INDIRECT_PREFIX + typeName + loaderName) : typeSignature;
 
@@ -173,6 +231,15 @@ protected TypeEntry createTypeEntry(SharedType type) {
         }
     }
 
+    /**
+     * The run-time debug info relies on type entries in the native image. In
+     * {@link #createTypeEntry} we just produce dummy types that hold just enough information to
+     * connect them to the types in the native image. Therefore, there is nothing to do for
+     * processing types at run-time.
+     *
+     * @param type the {@code SharedType} of the type entry
+     * @param typeEntry the {@code TypeEntry} to process
+     */
     @Override
     protected void processTypeEntry(@SuppressWarnings("unused") SharedType type, @SuppressWarnings("unused") TypeEntry typeEntry) {
         // nothing to do here
@@ -180,6 +247,6 @@ protected void processTypeEntry(@SuppressWarnings("unused") SharedType type, @Su
 
     @Override
     public long objectOffset(@SuppressWarnings("unused") JavaConstant constant) {
-        return 0;
+        return -1;
     }
 }
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 2cfef5aad13f..bc657c20cf68 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
@@ -67,6 +67,7 @@
 import com.oracle.objectfile.debugentry.TypeEntry;
 import com.oracle.svm.core.StaticFieldsSupport;
 import com.oracle.svm.core.SubstrateOptions;
+import com.oracle.svm.core.debug.BFDNameProvider;
 import com.oracle.svm.core.debug.SharedDebugInfoProvider;
 import com.oracle.svm.core.graal.meta.RuntimeConfiguration;
 import com.oracle.svm.core.image.ImageHeapPartition;
@@ -106,7 +107,8 @@
 
 /**
  * Implementation of the DebugInfoProvider API interface that allows type, code and heap data info
- * to be passed to an ObjectFile when generation of debug info is enabled.
+ * to be passed to an ObjectFile when generation of debug info is enabled at native image build
+ * time.
  */
 class NativeImageDebugInfoProvider extends SharedDebugInfoProvider {
     protected final NativeImageHeap heap;
@@ -141,8 +143,8 @@ class NativeImageDebugInfoProvider extends SharedDebugInfoProvider {
     @SuppressWarnings("unused")
     private static ResolvedJavaType getOriginal(ResolvedJavaType type) {
         /*
-         * unwrap then traverse through substitutions to the original. We don't want to get the
-         * original type of LambdaSubstitutionType to keep the stable name
+         * Unwrap then traverse through substitutions to the original. We don't want to get the
+         * original type of LambdaSubstitutionType to keep the stable name.
          */
         ResolvedJavaType targetType = type;
         while (targetType instanceof WrappedJavaType wrappedJavaType) {
@@ -164,20 +166,22 @@ private static ResolvedJavaMethod getAnnotatedOrOriginal(ResolvedJavaMethod meth
         while (targetMethod instanceof WrappedJavaMethod wrappedJavaMethod) {
             targetMethod = wrappedJavaMethod.getWrapped();
         }
-        // This method is only used when identifying the modifiers or the declaring class
-        // of a HostedMethod. Normally the method unwraps to the underlying JVMCI method
-        // which is the one that provides bytecode to the compiler as well as, line numbers
-        // and local info. If we unwrap to a SubstitutionMethod then we use the annotated
-        // method, not the JVMCI method that the annotation refers to since that will be the
-        // one providing the bytecode etc used by the compiler. If we unwrap to any other,
-        // custom substitution method we simply use it rather than dereferencing to the
-        // original. The difference is that the annotated method's bytecode will be used to
-        // replace the original and the debugger needs to use it to identify the file and access
-        // permissions. A custom substitution may exist alongside the original, as is the case
-        // with some uses for reflection. So, we don't want to conflate the custom substituted
-        // method and the original. In this latter case the method code will be synthesized without
-        // reference to the bytecode of the original. Hence, there is no associated file and the
-        // permissions need to be determined from the custom substitution method itself.
+        /*
+         * This method is only used when identifying the modifiers or the declaring class of a
+         * HostedMethod. Normally the method unwraps to the underlying JVMCI method which is the one
+         * that provides bytecode to the compiler as well as, line numbers and local info. If we
+         * unwrap to a SubstitutionMethod then we use the annotated method, not the JVMCI method
+         * that the annotation refers to since that will be the one providing the bytecode etc used
+         * by the compiler. If we unwrap to any other, custom substitution method we simply use it
+         * rather than dereferencing to the original. The difference is that the annotated method's
+         * bytecode will be used to replace the original and the debugger needs to use it to
+         * identify the file and access permissions. A custom substitution may exist alongside the
+         * original, as is the case with some uses for reflection. So, we don't want to conflate the
+         * custom substituted method and the original. In this latter case the method code will be
+         * synthesized without reference to the bytecode of the original. Hence, there is no
+         * associated file and the permissions need to be determined from the custom substitution
+         * method itself.
+         */
 
         if (targetMethod instanceof SubstitutionMethod substitutionMethod) {
             targetMethod = substitutionMethod.getAnnotated();
@@ -191,9 +195,14 @@ public String cachePath() {
         return SubstrateOptions.getDebugInfoSourceCacheRoot().toString();
     }
 
+    /**
+     * Logs information of {@link NativeImageHeap.ObjectInfo ObjectInfo}.
+     * 
+     * @param data the data info to process
+     */
     @Override
     @SuppressWarnings("try")
-    protected void handleDataInfo(Object data) {
+    protected void installDataInfo(Object data) {
         // log ObjectInfo data
         if (debug.isLogEnabled(DebugContext.INFO_LEVEL) && data instanceof NativeImageHeap.ObjectInfo objectInfo) {
             try (DebugContext.Scope s = debug.scope("DebugDataInfo")) {
@@ -208,7 +217,7 @@ protected void handleDataInfo(Object data) {
                 throw debug.handle(e);
             }
         } else {
-            super.handleDataInfo(data);
+            super.installDataInfo(data);
         }
     }
 
@@ -231,6 +240,13 @@ private static SizableInfo.ElementKind elementKind(SizableInfo sizableInfo) {
         return sizableInfo.getKind();
     }
 
+    /**
+     * Fetch the typedef name of an element info for {@link StructInfo structs} or
+     * {@link PointerToInfo pointer types}. Otherwise, we use the name of the element info.
+     * 
+     * @param elementInfo the given element info
+     * @return the typedef name
+     */
     private static String typedefName(ElementInfo elementInfo) {
         String name = null;
         if (elementInfo != null) {
@@ -246,6 +262,12 @@ private static String typedefName(ElementInfo elementInfo) {
         return name;
     }
 
+    /**
+     * Checks if a foreign type is a pointer type with {@link NativeLibraries}.
+     * 
+     * @param type the given foreign type
+     * @return true if the type is a foreign pointer type, otherwise false
+     */
     private boolean isForeignPointerType(HostedType type) {
         // unwrap because native libs operates on the analysis type universe
         return nativeLibs.isPointerBase(type.getWrapped());
@@ -329,17 +351,33 @@ protected static boolean fieldTypeIsEmbedded(StructFieldInfo field) {
         throw VMError.shouldNotReachHere("Field %s must have a GETTER, SETTER, ADDRESS or OFFSET accessor".formatted(field));
     }
 
+    /**
+     * Creates a stream all types from the hosted universe.
+     * 
+     * @return a stream of type in the hosted universe
+     */
     @Override
     protected Stream<SharedType> typeInfo() {
         // null represents the header type
         return heap.hUniverse.getTypes().stream().map(type -> type);
     }
 
+    /**
+     * Creates a stream of all compilations with the corresponding hosted methods from the native
+     * image code cache.
+     * 
+     * @return a stream of compilations
+     */
     @Override
     protected Stream<Pair<SharedMethod, CompilationResult>> codeInfo() {
         return codeCache.getOrderedCompilations().stream().map(pair -> Pair.create(pair.getLeft(), pair.getRight()));
     }
 
+    /**
+     * Creates a stream of all {@link NativeImageHeap.ObjectInfo objects} in the native image heap.
+     * 
+     * @return a stream of native image heap objects.
+     */
     @Override
     protected Stream<Object> dataInfo() {
         return heap.getObjects().stream().filter(obj -> obj.getPartition().getStartOffset() > 0).map(obj -> obj);
@@ -351,6 +389,21 @@ protected long getCodeOffset(SharedMethod method) {
         return ((HostedMethod) method).getCodeAddressOffset();
     }
 
+    /**
+     * Processes type entries for {@link HostedType hosted types}.
+     *
+     * <p>
+     * We need to process fields of {@link StructureTypeEntry structured types} after creating it to
+     * make sure that it is available for the field types. Otherwise, this would create a cycle for
+     * the type lookup.
+     *
+     * <p>
+     * For a {@link ClassEntry} we also need to process interfaces and methods for the same reason
+     * with the fields for structured types.
+     * 
+     * @param type the {@code SharedType} of the type entry
+     * @param typeEntry the {@code TypeEntry} to process
+     */
     @Override
     protected void processTypeEntry(SharedType type, TypeEntry typeEntry) {
         assert type instanceof HostedType;
@@ -372,6 +425,14 @@ protected void processTypeEntry(SharedType type, TypeEntry typeEntry) {
         }
     }
 
+    /**
+     * For processing methods of a type, we iterate over all its declared methods and lookup the
+     * corresponding {@link MethodEntry} objects. This ensures that all declared methods of a type
+     * are installed.
+     * 
+     * @param type the given type
+     * @param classEntry the type's {@code ClassEntry}
+     */
     private void processMethods(HostedType type, ClassEntry classEntry) {
         for (HostedMethod method : type.getAllDeclaredMethods()) {
             MethodEntry methodEntry = lookupMethodEntry(method);
@@ -403,11 +464,18 @@ private static String formatParams(LocalEntry thisParam, List<LocalEntry> paramI
         return builder.toString();
     }
 
+    /**
+     * Produce a method name of a {@code HostedMethod} for the debug info.
+     * 
+     * @param method method to produce a name for
+     * @return method name for the debug info
+     */
     @Override
-    public String getMethodName(SharedMethod method) {
+    protected String getMethodName(SharedMethod method) {
         String name;
         if (method instanceof HostedMethod hostedMethod) {
             name = hostedMethod.getName();
+            // replace <init> (method name of a constructor) with the class name
             if (name.equals("<init>")) {
                 name = hostedMethod.getDeclaringClass().toJavaName();
                 if (name.indexOf('.') >= 0) {
@@ -433,11 +501,24 @@ public boolean isVirtual(SharedMethod method) {
         return method instanceof HostedMethod hostedMethod && hostedMethod.hasVTableIndex();
     }
 
+    /**
+     * Fetch a methods symbol produced by the {@link BFDNameProvider}
+     * 
+     * @param method method to get the symbol name for
+     * @return symbol name of the method
+     */
     @Override
     public String getSymbolName(SharedMethod method) {
         return NativeImage.localSymbolNameForMethod(method);
     }
 
+    /**
+     * Process interfaces from the hosted type and add the class entry as an implementor. This
+     * ensures all interfaces are installed as debug entries.
+     * 
+     * @param type the given type
+     * @param classEntry the {@code ClassEntry} of the type
+     */
     private void processInterfaces(HostedType type, ClassEntry classEntry) {
         for (HostedType interfaceType : type.getInterfaces()) {
             TypeEntry entry = lookupTypeEntry(interfaceType);
@@ -453,6 +534,13 @@ private void processInterfaces(HostedType type, ClassEntry classEntry) {
         }
     }
 
+    /**
+     * For arrays, we add a synthetic field for their length. This ensures that the length can be
+     * exposed in the object files debug info.
+     * 
+     * @param type the given array type
+     * @param arrayTypeEntry the {@code ArrayTypeEntry} of the type
+     */
     private void processArrayFields(HostedType type, ArrayTypeEntry arrayTypeEntry) {
         JavaKind arrayKind = type.getBaseType().getJavaKind();
         int headerSize = getObjectLayout().getArrayBaseOffset(arrayKind);
@@ -462,6 +550,13 @@ private void processArrayFields(HostedType type, ArrayTypeEntry arrayTypeEntry)
         arrayTypeEntry.addField(createSyntheticFieldEntry("len", arrayTypeEntry, (HostedType) metaAccess.lookupJavaType(JavaKind.Int.toJavaClass()), arrayLengthOffset, arrayLengthSize));
     }
 
+    /**
+     * Process {@link StructFieldInfo fields} for {@link StructInfo foreign structs}. Fields are
+     * ordered by offset and added as {@link FieldEntry field entries} to the foreign type entry.
+     * 
+     * @param type the given type
+     * @param foreignTypeEntry the {@code ForeignTypeEntry} of the type
+     */
     private void processForeignTypeFields(HostedType type, ForeignTypeEntry foreignTypeEntry) {
         ElementInfo elementInfo = nativeLibs.findElementInfo(type);
         if (elementInfo instanceof StructInfo) {
@@ -477,6 +572,13 @@ private void processForeignTypeFields(HostedType type, ForeignTypeEntry foreignT
         }
     }
 
+    /**
+     * Processes instance fields and static fields of hosted types to and adds {@link FieldEntry
+     * field entries} to the structured type entry.
+     *
+     * @param type the given type
+     * @param structureTypeEntry the {@code StructuredTypeEntry} of the type
+     */
     private void processFieldEntries(HostedType type, StructureTypeEntry structureTypeEntry) {
         for (HostedField field : type.getInstanceFields(false)) {
             structureTypeEntry.addField(createFieldEntry(field, structureTypeEntry));
@@ -488,6 +590,13 @@ private void processFieldEntries(HostedType type, StructureTypeEntry structureTy
         }
     }
 
+    /**
+     * Creates a new field entry for a hosted field.
+     * 
+     * @param field the given hfield
+     * @param ownerType the structured type owning the hosted field
+     * @return a {@code FieldEntry} representing the hosted field
+     */
     private FieldEntry createFieldEntry(HostedField field, StructureTypeEntry ownerType) {
         FileEntry fileEntry = lookupFileEntry(field);
         String fieldName = field.getName();
@@ -511,6 +620,19 @@ private FieldEntry createFieldEntry(HostedField field, StructureTypeEntry ownerT
         return createFieldEntry(fileEntry, fieldName, ownerType, valueType, offset, size, false, modifiers);
     }
 
+    /**
+     * Creates an unprocessed type entry. The type entry contains all static information, which is
+     * its name, size, classOffset, loader entry and type signatures. For {@link ClassEntry} types,
+     * it also contains the superclass {@code ClassEntry}.
+     *
+     * <p>
+     * The returned type entry does not hold information on its fields, methods, and interfaces
+     * implementors. This information is patched later in {@link #processTypeEntry}. This is done to
+     * avoid cycles in the type entry lookup.
+     * 
+     * @param type the {@code SharedType} to process
+     * @return an unprocessed type entry
+     */
     @Override
     protected TypeEntry createTypeEntry(SharedType type) {
         assert type instanceof HostedType;
@@ -520,7 +642,7 @@ protected TypeEntry createTypeEntry(SharedType type) {
         int size = getTypeSize(hostedType);
         long classOffset = getClassOffset(hostedType);
         LoaderEntry loaderEntry = lookupLoaderEntry(hostedType);
-        String loaderName = loaderEntry == null ? uniqueNullStringEntry : loaderEntry.loaderId();
+        String loaderName = loaderEntry == null ? "" : loaderEntry.loaderId();
         long typeSignature = getTypeSignature(typeName + loaderName);
         long compressedTypeSignature = useHeapBase ? getTypeSignature(INDIRECT_PREFIX + typeName + loaderName) : typeSignature;
 
@@ -529,7 +651,10 @@ protected TypeEntry createTypeEntry(SharedType type) {
             debug.log("typename %s (%d bits)%n", typeName, kind == JavaKind.Void ? 0 : kind.getBitCount());
             return new PrimitiveTypeEntry(typeName, size, classOffset, typeSignature, kind);
         } else {
-            // otherwise we have a structured type
+            /*
+             * this is a structured type (array or class entry), or a foreign type entry (uses the
+             * layout signature even for not structured types)
+             */
             long layoutTypeSignature = getTypeSignature(LAYOUT_PREFIX + typeName + loaderName);
             if (hostedType.isArray()) {
                 TypeEntry elementTypeEntry = lookupTypeEntry(hostedType.getComponentType());
@@ -538,7 +663,7 @@ protected TypeEntry createTypeEntry(SharedType type) {
                 return new ArrayTypeEntry(typeName, size, classOffset, typeSignature, compressedTypeSignature,
                                 layoutTypeSignature, elementTypeEntry, loaderEntry);
             } else {
-                // otherwise this is a class entry
+                // this is a class entry or a foreign type entry
                 ClassEntry superClass = hostedType.getSuperclass() == null ? null : (ClassEntry) lookupTypeEntry(hostedType.getSuperclass());
 
                 if (debug.isLogEnabled() && superClass != null) {
@@ -547,6 +672,10 @@ protected TypeEntry createTypeEntry(SharedType type) {
 
                 FileEntry fileEntry = lookupFileEntry(hostedType);
                 if (isForeignWordType(hostedType)) {
+                    /*
+                     * A foreign type is either a generic word type, struct type, integer type,
+                     * float type, or a pointer.
+                     */
                     if (debug.isLogEnabled()) {
                         logForeignTypeInfo(hostedType);
                     }
@@ -561,7 +690,7 @@ protected TypeEntry createTypeEntry(SharedType type) {
                                         superClass, fileEntry, loaderEntry, isSigned);
                     } else if (elementInfo instanceof StructInfo) {
                         // look for the first interface that also has an associated StructInfo
-                        String typedefName = typedefName(elementInfo); // stringTable.uniqueDebugString(typedefName(elementInfo));
+                        String typedefName = typedefName(elementInfo);
                         ForeignStructTypeEntry parentEntry = null;
                         for (HostedInterface hostedInterface : hostedType.getInterfaces()) {
                             ElementInfo otherInfo = nativeLibs.findElementInfo(hostedInterface);
@@ -576,14 +705,16 @@ protected TypeEntry createTypeEntry(SharedType type) {
                     } else if (elementKind == SizableInfo.ElementKind.FLOAT) {
                         return new ForeignFloatTypeEntry(typeName, size, classOffset, typeSignature, layoutTypeSignature, superClass, fileEntry, loaderEntry);
                     } else {
-                        // This must be a pointer. If the target type is known use it to declare the
-                        // pointer
-                        // type, otherwise default to 'void *'
+                        /*
+                         * This must be a pointer. If the target type is known use it to declare the
+                         * pointer type, otherwise default to 'void *'
+                         */
                         TypeEntry pointerToEntry = null;
                         if (elementKind == SizableInfo.ElementKind.POINTER) {
-                            // any target type for the pointer will be defined by a CPointerTo or
-                            // RawPointerTo
-                            // annotation
+                            /*
+                             * any target type for the pointer will be defined by a CPointerTo or
+                             * RawPointerTo annotation
+                             */
                             CPointerTo cPointerTo = type.getAnnotation(CPointerTo.class);
                             if (cPointerTo != null) {
                                 HostedType pointerTo = heap.hMetaAccess.lookupJavaType(cPointerTo.value());
@@ -632,6 +763,7 @@ protected TypeEntry createTypeEntry(SharedType type) {
         }
     }
 
+    /* The following methods provide some logging for foreign type entries. */
     private void logForeignTypeInfo(HostedType hostedType) {
         if (!isForeignPointerType(hostedType)) {
             // non pointer type must be an interface because an instance needs to be pointed to
@@ -756,6 +888,16 @@ private static void indentElementInfo(StringBuilder stringBuilder, int indent) {
         stringBuilder.append("  ".repeat(Math.max(0, indent + 1)));
     }
 
+    /**
+     * Lookup the file entry of a {@code ResolvedJavaType}.
+     *
+     * <p>
+     * First tries to find the source file using the {@link SourceManager}. If no file was found, a
+     * file name is generated from the full class name.
+     * 
+     * @param type the given {@code ResolvedJavaType}
+     * @return the {@code FileEntry} for the type
+     */
     @Override
     @SuppressWarnings("try")
     public FileEntry lookupFileEntry(ResolvedJavaType type) {
@@ -774,6 +916,13 @@ public FileEntry lookupFileEntry(ResolvedJavaType type) {
         return super.lookupFileEntry(type);
     }
 
+    /**
+     * Lookup a {@code FileEntry} for a {@code HostedMethod}. For a {@link SubstitutionMethod}, this
+     * uses the source file of the annotated method.
+     * 
+     * @param method the given {@code ResolvedJavaMethod}
+     * @return the {@code FilEntry} for the method
+     */
     @Override
     public FileEntry lookupFileEntry(ResolvedJavaMethod method) {
         ResolvedJavaMethod targetMethod = method;

From 719609af7da366684a59347f351be97d329b61c1 Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Fri, 17 Jan 2025 19:22:56 +0100
Subject: [PATCH 25/34] Fix style issues

---
 .../src/com/oracle/objectfile/debugentry/ClassEntry.java      | 2 +-
 .../src/com/oracle/objectfile/debugentry/LocalEntry.java      | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
index 82911a07a8b0..f1dbf4c45759 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
@@ -201,7 +201,7 @@ public int getFileIdx(FileEntry file) {
         return indexedFiles.get(file);
     }
 
-    private DirEntry getDirEntry(FileEntry file) {
+    private static DirEntry getDirEntry(FileEntry file) {
         if (file == null) {
             return null;
         }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalEntry.java
index 606babcfd36d..2c95bf44bd71 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalEntry.java
@@ -42,8 +42,8 @@ public String toString() {
         return String.format("Local(%s type=%s slot=%d line=%d)", name, type.getTypeName(), slot, getLine());
     }
 
-    public void setLine(int line) {
-        this.line.set(line);
+    public void setLine(int newLine) {
+        this.line.set(newLine);
     }
 
     public int getLine() {

From 908bccdfdf3feb77b4de93c449742412aee3d79c Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Tue, 21 Jan 2025 15:25:06 +0100
Subject: [PATCH 26/34] Fix style issues, Add native image config for
 runtimedebuginfotest

---
 .../src/com/oracle/svm/core/debug/GDBJITInterface.java       | 2 +-
 .../com/oracle/svm/core/debug/SharedDebugInfoProvider.java   | 5 ++---
 .../svm/hosted/image/NativeImageDebugInfoProvider.java       | 2 +-
 3 files changed, 4 insertions(+), 5 deletions(-)

diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterface.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterface.java
index 396d80768676..a58573827ab7 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterface.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterface.java
@@ -65,7 +65,7 @@ public List<String> getHeaderFiles() {
         }
     }
 
-    private static class IncludeForRuntimeDebugOnly implements BooleanSupplier {
+    private static final class IncludeForRuntimeDebugOnly implements BooleanSupplier {
         @Override
         public boolean getAsBoolean() {
             return SubstrateOptions.RuntimeDebugInfo.getValue();
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 61dc8cff9aff..544ef2ea1127 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
@@ -162,9 +162,8 @@
  * which is further composed of:
  * <ul>
  * <li>{@link LeafRange}: A leaf in the {@link CompilationResultFrameTree}.</li>
- * <li>{@link CallRange}: A {@link CompilationResultFrameTree.CallNode CallNode} in the
- * {@link CompilationResultFrameTree}. Represents inlined calls and is therefore itself composed of
- * ranges.</li>
+ * <li>{@link CallRange}: A {@code CallNode} in the {@link CompilationResultFrameTree}. Represents
+ * inlined calls and is therefore itself composed of ranges.</li>
  * </ul>
  * </li>
  * </ul>
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 bc657c20cf68..15c412e1b62b 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
@@ -502,7 +502,7 @@ public boolean isVirtual(SharedMethod method) {
     }
 
     /**
-     * Fetch a methods symbol produced by the {@link BFDNameProvider}
+     * Fetch a methods symbol produced by the {@link BFDNameProvider}.
      * 
      * @param method method to get the symbol name for
      * @return symbol name of the method

From 6e218b701444c40b75331f3960c5aab6ab71527e Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Tue, 21 Jan 2025 15:42:43 +0100
Subject: [PATCH 27/34] Add changelog entry

---
 substratevm/CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md
index bd900af80a42..a4d0d486566d 100644
--- a/substratevm/CHANGELOG.md
+++ b/substratevm/CHANGELOG.md
@@ -9,6 +9,7 @@ This changelog summarizes major changes to GraalVM Native Image.
 * (GR-59864) Added JVM version check to the Native Image agent. The agent will abort execution if the JVM major version does not match the version it was built with, and warn if the full JVM version is different.
 * (GR-59135) Verify if hosted options passed to `native-image` exist prior to starting the builder. Provide suggestions how to fix unknown options early on.
 * (GR-61492) The experimental JDWP option is now present in standard GraalVM builds.
+* (GR-54697) Parallelize debug info generator and add support run-time debug info generation. `-H:+RuntimeDebugInfo` adds a run-time debug info generator into a native image. The run-time debug info generator notifies GDB via the [JIT Compilation Interface](https://sourceware.org/gdb/current/onlinedocs/gdb.html/JIT-Interface.html) about new object files for run-time compilations.
 
 ## GraalVM for JDK 24 (Internal Version 24.2.0)
 * (GR-59717) Added `DuringSetupAccess.registerObjectReachabilityHandler` to allow registering a callback that is executed when an object of a specified type is marked as reachable during heap scanning.

From d91e05c82462f6897feb2ee4a3581308176fd459 Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Mon, 3 Mar 2025 17:44:14 +0100
Subject: [PATCH 28/34] Update Line section to DWARF5, fix bug when loading
 debug info in recent GDB versions (>= GDB 16)

---
 .../objectfile/debugentry/ClassEntry.java     |   4 +
 .../objectfile/debugentry/DebugInfoBase.java  |  34 +++-
 .../oracle/objectfile/elf/ELFObjectFile.java  |  10 +-
 .../elf/dwarf/DwarfAbbrevSectionImpl.java     |  19 ++-
 .../objectfile/elf/dwarf/DwarfDebugInfo.java  |   7 +
 .../elf/dwarf/DwarfFrameSectionImpl.java      |   4 +-
 .../elf/dwarf/DwarfInfoSectionImpl.java       |   8 +-
 .../elf/dwarf/DwarfLineSectionImpl.java       | 159 ++++++++++--------
 .../elf/dwarf/DwarfLineStrSectionImpl.java    |  75 +++++++++
 .../elf/dwarf/DwarfSectionImpl.java           |  23 ++-
 .../elf/dwarf/DwarfStrSectionImpl.java        |   2 +-
 .../elf/dwarf/constants/DwarfForm.java        |  12 +-
 .../constants/DwarfLineNumberHeaderEntry.java |  47 ++++++
 .../elf/dwarf/constants/DwarfSectionName.java |   1 +
 14 files changed, 305 insertions(+), 100 deletions(-)
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLineStrSectionImpl.java
 create mode 100644 substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfLineNumberHeaderEntry.java

diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
index f1dbf4c45759..0883dcc45e05 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java
@@ -172,6 +172,10 @@ public int getFileIdx() {
         return getFileIdx(this.getFileEntry());
     }
 
+    public int getDirIdx() {
+        return getDirIdx(this.getFileEntry());
+    }
+
     /**
      * Returns the file index of a given file entry within this class entry.
      *
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java
index df1e6d33d5f2..9be7a2c45ce8 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java
@@ -71,11 +71,15 @@
 public abstract class DebugInfoBase {
     protected ByteOrder byteOrder;
     /**
-     * A table listing all known strings, some of which may be marked for insertion into the
-     * debug_str section.
+     * A table listing all known strings except strings in the debug line section.
      */
     private StringTable stringTable;
 
+    /**
+     * A table listing all known strings in the debug line section.
+     */
+    private StringTable lineStringTable;
+
     /**
      * List of all types present in the native image including instance classes, array classes,
      * primitive types and the one-off Java header struct.
@@ -231,10 +235,13 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) {
         assert objectAlignment == 8;
 
         stringTable = new StringTable();
+        lineStringTable = new StringTable();
         /* Ensure we have a null string at the start of the string table. */
         uniqueDebugString("");
+        uniqueDebugLineString("");
         /* Create the cachePath string entry which serves as base directory for source files */
         cachePath = uniqueDebugString(debugInfoProvider.cachePath());
+        uniqueDebugLineString(debugInfoProvider.cachePath());
 
         compiledMethods.addAll(debugInfoProvider.compiledMethodEntries());
         debugInfoProvider.typeEntries().forEach(typeEntry -> {
@@ -314,6 +321,10 @@ public StringTable getStringTable() {
         return stringTable;
     }
 
+    public StringTable getLineStringTable() {
+        return lineStringTable;
+    }
+
     /**
      * Indirects this call to the string table.
      *
@@ -323,6 +334,15 @@ public String uniqueDebugString(String string) {
         return stringTable.uniqueDebugString(string);
     }
 
+    /**
+     * Indirects this call to the line string table.
+     *
+     * @param string the string whose index is required.
+     */
+    public String uniqueDebugLineString(String string) {
+        return lineStringTable.uniqueDebugString(string);
+    }
+
     /**
      * Indirects this call to the string table.
      *
@@ -333,6 +353,16 @@ public int debugStringIndex(String string) {
         return stringTable.debugStringIndex(string);
     }
 
+    /**
+     * Indirects this call to the line string table.
+     *
+     * @param string the string whose index is required.
+     * @return the offset of the string in the .debug_line_str section.
+     */
+    public int debugLineStringIndex(String string) {
+        return lineStringTable.debugStringIndex(string);
+    }
+
     public boolean useHeapBase() {
         return useHeapBase;
     }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/ELFObjectFile.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/ELFObjectFile.java
index 76af5bce5409..60ab09fd39a5 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/ELFObjectFile.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/ELFObjectFile.java
@@ -35,8 +35,6 @@
 import java.util.Map;
 import java.util.Set;
 
-import com.oracle.objectfile.elf.dwarf.DwarfLocSectionImpl;
-import com.oracle.objectfile.elf.dwarf.DwarfDebugInfo;
 import org.graalvm.nativeimage.ImageSingletons;
 import org.graalvm.nativeimage.Platform;
 
@@ -50,9 +48,12 @@
 import com.oracle.objectfile.debuginfo.DebugInfoProvider;
 import com.oracle.objectfile.elf.dwarf.DwarfARangesSectionImpl;
 import com.oracle.objectfile.elf.dwarf.DwarfAbbrevSectionImpl;
+import com.oracle.objectfile.elf.dwarf.DwarfDebugInfo;
 import com.oracle.objectfile.elf.dwarf.DwarfFrameSectionImpl;
 import com.oracle.objectfile.elf.dwarf.DwarfInfoSectionImpl;
 import com.oracle.objectfile.elf.dwarf.DwarfLineSectionImpl;
+import com.oracle.objectfile.elf.dwarf.DwarfLineStrSectionImpl;
+import com.oracle.objectfile.elf.dwarf.DwarfLocSectionImpl;
 import com.oracle.objectfile.elf.dwarf.DwarfRangesSectionImpl;
 import com.oracle.objectfile.elf.dwarf.DwarfStrSectionImpl;
 import com.oracle.objectfile.io.AssemblyBuffer;
@@ -1177,6 +1178,7 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) {
 
         /* We need an implementation for each generated DWARF section. */
         DwarfStrSectionImpl elfStrSectionImpl = dwarfSections.getStrSectionImpl();
+        DwarfLineStrSectionImpl elfLineStrSectionImpl = dwarfSections.getLineStrSectionImpl();
         DwarfAbbrevSectionImpl elfAbbrevSectionImpl = dwarfSections.getAbbrevSectionImpl();
         DwarfFrameSectionImpl frameSectionImpl = dwarfSections.getFrameSectionImpl();
         DwarfLocSectionImpl elfLocSectionImpl = dwarfSections.getLocSectionImpl();
@@ -1186,6 +1188,7 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) {
         DwarfLineSectionImpl elfLineSectionImpl = dwarfSections.getLineSectionImpl();
         /* Now we can create the section elements with empty content. */
         newDebugSection(elfStrSectionImpl.getSectionName(), elfStrSectionImpl);
+        newDebugSection(elfLineStrSectionImpl.getSectionName(), elfLineStrSectionImpl);
         newDebugSection(elfAbbrevSectionImpl.getSectionName(), elfAbbrevSectionImpl);
         newDebugSection(frameSectionImpl.getSectionName(), frameSectionImpl);
         newDebugSection(elfLocSectionImpl.getSectionName(), elfLocSectionImpl);
@@ -1203,6 +1206,7 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) {
         createDefinedSymbol(elfInfoSectionImpl.getSectionName(), elfInfoSectionImpl.getElement(), 0, 0, false, false);
         createDefinedSymbol(elfLineSectionImpl.getSectionName(), elfLineSectionImpl.getElement(), 0, 0, false, false);
         createDefinedSymbol(elfStrSectionImpl.getSectionName(), elfStrSectionImpl.getElement(), 0, 0, false, false);
+        createDefinedSymbol(elfLineStrSectionImpl.getSectionName(), elfLineStrSectionImpl.getElement(), 0, 0, false, false);
         createDefinedSymbol(elfRangesSectionImpl.getSectionName(), elfRangesSectionImpl.getElement(), 0, 0, false, false);
         createDefinedSymbol(elfLocSectionImpl.getSectionName(), elfLocSectionImpl.getElement(), 0, 0, false, false);
         /*
@@ -1215,10 +1219,10 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) {
          * relevant reloc sections here in advance.
          */
         elfStrSectionImpl.getOrCreateRelocationElement(0);
+        elfLineStrSectionImpl.getOrCreateRelocationElement(0);
         elfAbbrevSectionImpl.getOrCreateRelocationElement(0);
         frameSectionImpl.getOrCreateRelocationElement(0);
         elfInfoSectionImpl.getOrCreateRelocationElement(0);
-        // elfTypesSectionImpl.getOrCreateRelocationElement(0);
         elfLocSectionImpl.getOrCreateRelocationElement(0);
         elfARangesSectionImpl.getOrCreateRelocationElement(0);
         elfRangesSectionImpl.getOrCreateRelocationElement(0);
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java
index f4a79937bb69..f4474bd5599a 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java
@@ -1124,6 +1124,7 @@ private int writeClassLayoutAbbrevs(@SuppressWarnings("unused") DebugContext con
             }
         }
         pos = writeClassLayoutAbbrev(context, AbbrevCode.CLASS_LAYOUT_3, buffer, pos);
+        pos = writeClassLayoutAbbrev(context, AbbrevCode.CLASS_LAYOUT_4, buffer, pos);
         return pos;
     }
 
@@ -1135,21 +1136,23 @@ private int writeClassLayoutAbbrev(@SuppressWarnings("unused") DebugContext cont
         pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_yes, buffer, pos);
         pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos);
         pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos);
-        if (abbrevCode == AbbrevCode.CLASS_LAYOUT_3) {
+        if (abbrevCode == AbbrevCode.CLASS_LAYOUT_3 || abbrevCode == AbbrevCode.CLASS_LAYOUT_4) {
             pos = writeAttrType(DwarfAttribute.DW_AT_declaration, buffer, pos);
             pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos);
             pos = writeAttrType(DwarfAttribute.DW_AT_signature, buffer, pos);
             pos = writeAttrForm(DwarfForm.DW_FORM_ref_sig8, buffer, pos);
+            if (abbrevCode == AbbrevCode.CLASS_LAYOUT_4) {
+                pos = writeAttrType(DwarfAttribute.DW_AT_decl_file, buffer, pos);
+                pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos);
+                /*
+                 * At present, we definitely don't have a line number for the class itself. pos =
+                 * writeAttrType(DwarfDebugInfo.DW_AT_decl_line, buffer, pos); pos =
+                 * writeAttrForm(DwarfDebugInfo.DW_FORM_data2, buffer, pos);
+                 */
+            }
         } else {
             pos = writeAttrType(DwarfAttribute.DW_AT_byte_size, buffer, pos);
             pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos);
-            pos = writeAttrType(DwarfAttribute.DW_AT_decl_file, buffer, pos);
-            pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos);
-            /*-
-             * At present we definitely don't have a line number for the class itself.
-               pos = writeAttrType(DwarfDebugInfo.DW_AT_decl_line, buffer, pos);
-               pos = writeAttrForm(DwarfDebugInfo.DW_FORM_data2, buffer, pos);
-            */
             if (abbrevCode == AbbrevCode.CLASS_LAYOUT_2) {
                 pos = writeAttrType(DwarfAttribute.DW_AT_data_location, buffer, pos);
                 pos = writeAttrForm(DwarfForm.DW_FORM_expr_loc, buffer, pos);
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java
index 79c92a78772f..4b984629f9b3 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java
@@ -69,6 +69,7 @@ public enum AbbrevCode {
         CLASS_LAYOUT_1,
         CLASS_LAYOUT_2,
         CLASS_LAYOUT_3,
+        CLASS_LAYOUT_4,
         TYPE_POINTER_SIG,
         TYPE_POINTER,
         FOREIGN_TYPEDEF,
@@ -128,6 +129,7 @@ public enum AbbrevCode {
 
     /* Full byte/word values. */
     private final DwarfStrSectionImpl dwarfStrSection;
+    private final DwarfLineStrSectionImpl dwarfLineStrSection;
     private final DwarfAbbrevSectionImpl dwarfAbbrevSection;
     private final DwarfInfoSectionImpl dwarfInfoSection;
     private final DwarfLocSectionImpl dwarfLocSection;
@@ -167,6 +169,7 @@ public DwarfDebugInfo(ELFMachine elfMachine, ByteOrder byteOrder) {
         super(byteOrder);
         this.elfMachine = elfMachine;
         dwarfStrSection = new DwarfStrSectionImpl(this);
+        dwarfLineStrSection = new DwarfLineStrSectionImpl(this);
         dwarfAbbrevSection = new DwarfAbbrevSectionImpl(this);
         dwarfInfoSection = new DwarfInfoSectionImpl(this);
         dwarfLocSection = new DwarfLocSectionImpl(this);
@@ -189,6 +192,10 @@ public DwarfStrSectionImpl getStrSectionImpl() {
         return dwarfStrSection;
     }
 
+    public DwarfLineStrSectionImpl getLineStrSectionImpl() {
+        return dwarfLineStrSection;
+    }
+
     public DwarfAbbrevSectionImpl getAbbrevSectionImpl() {
         return dwarfAbbrevSection;
     }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImpl.java
index be6b7ecc4732..345a2906f2f9 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImpl.java
@@ -46,8 +46,8 @@ public abstract class DwarfFrameSectionImpl extends DwarfSectionImpl {
     private static final int CFA_CIE_id_default = -1;
 
     public DwarfFrameSectionImpl(DwarfDebugInfo dwarfSections) {
-        // debug_frame section depends on debug_line section
-        super(dwarfSections, DwarfSectionName.DW_FRAME_SECTION, DwarfSectionName.DW_LINE_SECTION);
+        // debug_frame section depends on debug_line_str section
+        super(dwarfSections, DwarfSectionName.DW_FRAME_SECTION, DwarfSectionName.DW_LINE_STR_SECTION);
     }
 
     @Override
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 ef0a6b1d8a46..8e9ce6e10f37 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
@@ -163,7 +163,7 @@ public int generateContent(DebugContext context, byte[] buffer) {
     private int writeSkeletonClassLayout(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) {
         int pos = p;
         log(context, "  [0x%08x] class layout", pos);
-        AbbrevCode abbrevCode = AbbrevCode.CLASS_LAYOUT_3;
+        AbbrevCode abbrevCode = AbbrevCode.CLASS_LAYOUT_4;
         log(context, "  [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal());
         pos = writeAbbrevCode(abbrevCode, buffer, pos);
         String name = uniqueDebugString(classEntry.getTypeName());
@@ -174,6 +174,9 @@ private int writeSkeletonClassLayout(DebugContext context, ClassEntry classEntry
         long typeSignature = classEntry.getLayoutTypeSignature();
         log(context, "  [0x%08x]     type specification 0x%x", pos, typeSignature);
         pos = writeTypeSignature(typeSignature, buffer, pos);
+        int fileIdx = classEntry.getFileIdx();
+        log(context, "  [0x%08x]     file  0x%x (%s)", pos, fileIdx, classEntry.getFileName());
+        pos = writeAttrData2((short) fileIdx, buffer, pos);
 
         pos = writeStaticFieldDeclarations(context, classEntry, buffer, pos);
         pos = writeMethodDeclarations(context, classEntry, buffer, pos);
@@ -646,9 +649,6 @@ private int writeClassLayoutTypeUnit(DebugContext context, ClassEntry classEntry
         int size = classEntry.getSize();
         log(context, "  [0x%08x]     byte_size 0x%x", pos, size);
         pos = writeAttrData2((short) size, buffer, pos);
-        int fileIdx = classEntry.getFileIdx();
-        log(context, "  [0x%08x]     file  0x%x (%s)", pos, fileIdx, classEntry.getFileName());
-        pos = writeAttrData2((short) fileIdx, buffer, pos);
         if (abbrevCode == AbbrevCode.CLASS_LAYOUT_2) {
             /* Write a data location expression to mask and/or rebase oop pointers. */
             log(context, "  [0x%08x]     data_location", pos);
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 57cf2993771a..569743c625e0 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
@@ -30,6 +30,8 @@
 import com.oracle.objectfile.debugentry.CompiledMethodEntry;
 import com.oracle.objectfile.debugentry.FileEntry;
 import com.oracle.objectfile.debugentry.range.Range;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfForm;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfLineNumberHeaderEntry;
 import com.oracle.objectfile.elf.dwarf.constants.DwarfLineOpcode;
 import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName;
 import com.oracle.objectfile.elf.dwarf.constants.DwarfVersion;
@@ -48,7 +50,7 @@ public class DwarfLineSectionImpl extends DwarfSectionImpl {
     /**
      * Line header section always contains fixed number of bytes.
      */
-    private static final int LN_HEADER_SIZE = 28;
+    private static final int LN_HEADER_SIZE = 30;
     /**
      * Current generator follows C++ with line base -5.
      */
@@ -80,8 +82,8 @@ public void createContent() {
         instanceClassWithCompilationStream().forEachOrdered(classEntry -> {
             setLineIndex(classEntry, byteCount.get());
             int headerSize = headerSize();
-            int dirTableSize = computeDirTableSize(classEntry);
-            int fileTableSize = computeFileTableSize(classEntry);
+            int dirTableSize = writeDirTable(null, classEntry, null, 0);
+            int fileTableSize = writeFileTable(null, classEntry, null, 0);
             int prologueSize = headerSize + dirTableSize + fileTableSize;
             setLinePrologueSize(classEntry, prologueSize);
             // mark the start of the line table for this entry
@@ -103,6 +105,10 @@ private static int headerSize() {
          *
          * <li><code>uint16 version</code>
          *
+         * <li><code>uint8 address_size</code>
+         *
+         * <li><code>uint8 segment_selector_size</code>
+         *
          * <li><code>uint32 header_length</code>
          *
          * <li><code>uint8 min_insn_length</code>
@@ -125,53 +131,6 @@ private static int headerSize() {
         return LN_HEADER_SIZE;
     }
 
-    private static int computeDirTableSize(ClassEntry classEntry) {
-        /*
-         * Table contains a sequence of 'nul'-terminated UTF8 dir name bytes followed by an extra
-         * 'nul'.
-         */
-        Cursor cursor = new Cursor();
-        classEntry.getDirs().forEach(dirEntry -> {
-            int length = countUTF8Bytes(dirEntry.getPathString());
-            // We should never have a null or zero length entry in local dirs
-            assert length > 0;
-            cursor.add(length + 1);
-        });
-        /*
-         * Allow for terminator nul.
-         */
-        cursor.add(1);
-        return cursor.get();
-    }
-
-    private int computeFileTableSize(ClassEntry classEntry) {
-        /*
-         * Table contains a sequence of file entries followed by an extra 'nul'
-         *
-         * each file entry consists of a 'nul'-terminated UTF8 file name, a dir entry idx and two 0
-         * time stamps
-         */
-        Cursor cursor = new Cursor();
-        classEntry.getFiles().forEach(fileEntry -> {
-            // We want the file base name excluding path.
-            String baseName = fileEntry.fileName();
-            int length = countUTF8Bytes(baseName);
-            // We should never have a null or zero length entry in local files.
-            assert length > 0;
-            cursor.add(length + 1);
-            // The dir index gets written as a ULEB
-            int dirIdx = classEntry.getDirIdx(fileEntry);
-            cursor.add(writeULEB(dirIdx, scratch, 0));
-            // The two zero timestamps require 1 byte each
-            cursor.add(2);
-        });
-        /*
-         * Allow for terminator nul.
-         */
-        cursor.add(1);
-        return cursor.get();
-    }
-
     private int computeLineNumberTableSize(ClassEntry classEntry) {
         /*
          * Sigh -- we have to do this by generating the content even though we cannot write it into
@@ -218,14 +177,25 @@ private int writeHeader(ClassEntry classEntry, byte[] buffer, int p) {
          */
         pos = writeInt(0, buffer, pos);
         /*
-         * 2 ubyte version is always 2.
+         * 2 ubyte version is always 5.
+         */
+        pos = writeDwarfVersion(DwarfVersion.DW_VERSION_5, buffer, pos);
+        /*
+         * 1 ubyte address size field.
          */
-        pos = writeDwarfVersion(DwarfVersion.DW_VERSION_4, buffer, pos);
+        pos = writeByte((byte) 8, buffer, pos);
         /*
-         * 4 ubyte prologue length includes rest of header and dir + file table section.
+         * 1 ubyte segment selector size field.
          */
-        int prologueSize = getLinePrologueSize(classEntry) - (4 + 2 + 4);
+        pos = writeByte((byte) 0, buffer, pos);
+
+        /*
+         * TODO: fix this 4 ubyte prologue length includes rest of header and dir + file table
+         * section.
+         */
+        int prologueSize = getLinePrologueSize(classEntry) - (4 + 2 + 1 + 1 + 4);
         pos = writeInt(prologueSize, buffer, pos);
+
         /*
          * 1 ubyte min instruction length is always 1.
          */
@@ -281,52 +251,93 @@ private int writeHeader(ClassEntry classEntry, byte[] buffer, int p) {
     }
 
     private int writeDirTable(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) {
+        int pos = p;
         verboseLog(context, "  [0x%08x] Dir Name", p);
+
+        /*
+         * 1 ubyte directory entry format count field.
+         */
+        pos = writeByte((byte) 1, buffer, pos);
+        /*
+         * 1 ULEB128 pair for the directory entry format.
+         */
+        pos = writeULEB(DwarfLineNumberHeaderEntry.DW_LNCT_path.value(), buffer, pos);
+        // DW_FORM_strp is not supported by GDB but DW_FORM_line_strp is
+        pos = writeULEB(DwarfForm.DW_FORM_line_strp.value(), buffer, pos);
+
+        /*
+         * 1 ULEB128 for directory count.
+         */
+        pos = writeULEB(classEntry.getDirs().size() + 1, buffer, pos);
+
+        /*
+         * Write explicit 0 entry for current directory. (compilation directory)
+         */
+        String compilationDirectory = uniqueDebugLineString(dwarfSections.getCachePath());
+        pos = writeLineStrSectionOffset(compilationDirectory, buffer, pos);
+
         /*
          * Write out the list of dirs
          */
-        Cursor cursor = new Cursor(p);
+        Cursor cursor = new Cursor(pos);
         Cursor idx = new Cursor(1);
         classEntry.getDirs().forEach(dirEntry -> {
             int dirIdx = idx.get();
             assert (classEntry.getDirIdx(dirEntry) == dirIdx);
-            String dirPath = dirEntry.getPathString();
+            String dirPath = uniqueDebugLineString(dirEntry.getPathString());
             verboseLog(context, "  [0x%08x] %-4d %s", cursor.get(), dirIdx, dirPath);
-            cursor.set(writeUTF8StringBytes(dirPath, buffer, cursor.get()));
+            cursor.set(writeLineStrSectionOffset(dirPath, buffer, cursor.get()));
             idx.add(1);
         });
-        /*
-         * Separate dirs from files with a nul.
-         */
-        cursor.set(writeByte((byte) 0, buffer, cursor.get()));
+
         return cursor.get();
     }
 
     private int writeFileTable(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) {
+        int pos = p;
         verboseLog(context, "  [0x%08x] Entry Dir  Name", p);
+
+        /*
+         * 1 ubyte file name entry format count field.
+         */
+        pos = writeByte((byte) 2, buffer, pos);
+        /*
+         * 2 ULEB128 pairs for the directory entry format.
+         */
+        pos = writeULEB(DwarfLineNumberHeaderEntry.DW_LNCT_path.value(), buffer, pos);
+        // DW_FORM_strp is not supported by GDB but DW_FORM_line_strp is
+        pos = writeULEB(DwarfForm.DW_FORM_line_strp.value(), buffer, pos);
+        pos = writeULEB(DwarfLineNumberHeaderEntry.DW_LNCT_directory_index.value(), buffer, pos);
+        pos = writeULEB(DwarfForm.DW_FORM_udata.value(), buffer, pos);
+
+        /*
+         * 1 ULEB128 for directory count.
+         */
+        pos = writeULEB(classEntry.getFiles().size() + 1, buffer, pos);
+
+        /*
+         * Write explicit 0 dummy entry.
+         */
+        String fileName = uniqueDebugLineString(classEntry.getFileName());
+        pos = writeLineStrSectionOffset(fileName, buffer, pos);
+        pos = writeULEB(classEntry.getDirIdx(), buffer, pos);
+
         /*
          * Write out the list of files
          */
-        Cursor cursor = new Cursor(p);
+        Cursor cursor = new Cursor(pos);
         Cursor idx = new Cursor(1);
         classEntry.getFiles().forEach(fileEntry -> {
-            int pos = cursor.get();
             int fileIdx = idx.get();
             assert classEntry.getFileIdx(fileEntry) == fileIdx;
             int dirIdx = classEntry.getDirIdx(fileEntry);
-            String baseName = fileEntry.fileName();
-            verboseLog(context, "  [0x%08x] %-5d %-5d %s", pos, fileIdx, dirIdx, baseName);
-            pos = writeUTF8StringBytes(baseName, buffer, pos);
-            pos = writeULEB(dirIdx, buffer, pos);
-            pos = writeULEB(0, buffer, pos);
-            pos = writeULEB(0, buffer, pos);
-            cursor.set(pos);
+            String baseName = uniqueDebugLineString(fileEntry.fileName());
+            verboseLog(context, "  [0x%08x] %-5d %-5d %s", cursor.get(), fileIdx, dirIdx, baseName);
+            cursor.set(writeLineStrSectionOffset(baseName, buffer, cursor.get()));
+            cursor.set(writeULEB(dirIdx, buffer, cursor.get()));
             idx.add(1);
         });
-        /*
-         * Terminate files with a nul.
-         */
-        cursor.set(writeByte((byte) 0, buffer, cursor.get()));
+
         return cursor.get();
     }
 
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLineStrSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLineStrSectionImpl.java
new file mode 100644
index 000000000000..0984c5e3a5fa
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLineStrSectionImpl.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2025, 2025, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.elf.dwarf;
+
+import com.oracle.objectfile.debugentry.StringEntry;
+import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName;
+
+import jdk.graal.compiler.debug.DebugContext;
+
+/**
+ * Generator for debug_line_str section.
+ */
+public class DwarfLineStrSectionImpl extends DwarfSectionImpl {
+    public DwarfLineStrSectionImpl(DwarfDebugInfo dwarfSections) {
+        // debug_line_str section depends on line section
+        super(dwarfSections, DwarfSectionName.DW_LINE_STR_SECTION, DwarfSectionName.DW_LINE_SECTION);
+    }
+
+    @Override
+    public void createContent() {
+        assert !contentByteArrayCreated();
+
+        int pos = 0;
+        for (StringEntry stringEntry : dwarfSections.getLineStringTable()) {
+            stringEntry.setOffset(pos);
+            String string = stringEntry.getString();
+            pos = writeUTF8StringBytes(string, null, pos);
+        }
+        byte[] buffer = new byte[pos];
+        super.setContent(buffer);
+    }
+
+    @Override
+    public void writeContent(DebugContext context) {
+        assert contentByteArrayCreated();
+
+        byte[] buffer = getContent();
+        int size = buffer.length;
+        int pos = 0;
+
+        enableLog(context);
+
+        verboseLog(context, " [0x%08x] DEBUG_STR", pos);
+        for (StringEntry stringEntry : dwarfSections.getLineStringTable()) {
+            assert stringEntry.getOffset() == pos;
+            String string = stringEntry.getString();
+            pos = writeUTF8StringBytes(string, buffer, pos);
+            verboseLog(context, " [0x%08x] string = %s", pos, string);
+        }
+        assert pos == size;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java
index 1c3348720b65..ea9f3e65b98e 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java
@@ -450,7 +450,8 @@ protected int writeUTF8StringBytes(String s, int startChar, byte[] buffer, int p
         if (buffer != null) {
             return putUTF8StringBytes(s, startChar, buffer, p);
         } else {
-            return s.substring(startChar).getBytes(StandardCharsets.UTF_8).length;
+            // +1 for null termination
+            return p + s.substring(startChar).getBytes(StandardCharsets.UTF_8).length + 1;
         }
     }
 
@@ -571,6 +572,15 @@ private int writeStrSectionOffset(int offset, byte[] buffer, int pos) {
         return writeDwarfSectionOffset(offset, buffer, DwarfSectionName.DW_STR_SECTION, pos);
     }
 
+    protected int writeLineStrSectionOffset(String value, byte[] buffer, int p) {
+        int idx = debugLineStringIndex(value);
+        return writeLineStrSectionOffset(idx, buffer, p);
+    }
+
+    private int writeLineStrSectionOffset(int offset, byte[] buffer, int pos) {
+        return writeDwarfSectionOffset(offset, buffer, DwarfSectionName.DW_LINE_STR_SECTION, pos);
+    }
+
     protected int writeLocSectionOffset(int offset, byte[] buffer, int pos) {
         return writeDwarfSectionOffset(offset, buffer, DwarfSectionName.DW_LOCLISTS_SECTION, pos);
     }
@@ -822,10 +832,21 @@ protected int debugStringIndex(String str) {
         return dwarfSections.debugStringIndex(str);
     }
 
+    protected int debugLineStringIndex(String str) {
+        if (!contentByteArrayCreated()) {
+            return 0;
+        }
+        return dwarfSections.debugLineStringIndex(str);
+    }
+
     protected String uniqueDebugString(String str) {
         return dwarfSections.uniqueDebugString(str);
     }
 
+    protected String uniqueDebugLineString(String str) {
+        return dwarfSections.uniqueDebugLineString(str);
+    }
+
     protected ClassEntry lookupObjectClass() {
         return dwarfSections.lookupObjectClass();
     }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfStrSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfStrSectionImpl.java
index 5515d12f1563..303e82a2af1e 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfStrSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfStrSectionImpl.java
@@ -48,7 +48,7 @@ public void createContent() {
         for (StringEntry stringEntry : dwarfSections.getStringTable()) {
             stringEntry.setOffset(pos);
             String string = stringEntry.getString();
-            pos += countUTF8Bytes(string) + 1;
+            pos = writeUTF8StringBytes(string, null, pos);
         }
         byte[] buffer = new byte[pos];
         super.setContent(buffer);
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfForm.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfForm.java
index 1766ca8aec77..0a50b51b506c 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfForm.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfForm.java
@@ -32,14 +32,18 @@
 public enum DwarfForm {
     DW_FORM_null(0x0),
     DW_FORM_addr(0x1),
-    DW_FORM_data2(0x05),
+    DW_FORM_data2(0x5),
     DW_FORM_data4(0x6),
     @SuppressWarnings("unused")//
     DW_FORM_data8(0x7),
     @SuppressWarnings("unused")//
     DW_FORM_string(0x8),
     @SuppressWarnings("unused")//
-    DW_FORM_block1(0x0a),
+    DW_FORM_block1(0xa),
+    DW_FORM_data1(0xb),
+    DW_FORM_flag(0xc),
+    DW_FORM_strp(0xe),
+    DW_FORM_udata(0xf),
     DW_FORM_ref_addr(0x10),
     @SuppressWarnings("unused")//
     DW_FORM_ref1(0x11),
@@ -49,10 +53,8 @@ public enum DwarfForm {
     @SuppressWarnings("unused")//
     DW_FORM_ref8(0x14),
     DW_FORM_sec_offset(0x17),
-    DW_FORM_data1(0x0b),
-    DW_FORM_flag(0xc),
-    DW_FORM_strp(0xe),
     DW_FORM_expr_loc(0x18),
+    DW_FORM_line_strp(0x1f),
     DW_FORM_ref_sig8(0x20),
     DW_FORM_loclistx(0x22);
 
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfLineNumberHeaderEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfLineNumberHeaderEntry.java
new file mode 100644
index 000000000000..0c651d62392f
--- /dev/null
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfLineNumberHeaderEntry.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2025, 2025, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package com.oracle.objectfile.elf.dwarf.constants;
+
+/**
+ * All the Dwarf attribute forms needed to type attribute values generated by GraalVM.
+ */
+public enum DwarfLineNumberHeaderEntry {
+    DW_LNCT_path(0x1),
+    DW_LNCT_directory_index(0x2),
+    DW_LNCT_timestamp(0x3),
+    DW_LNCT_size(0x4),
+    DW_LNCT_MD5(0x5);
+
+    private final int value;
+
+    DwarfLineNumberHeaderEntry(int i) {
+        value = i;
+    }
+
+    public int value() {
+        return value;
+    }
+}
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfSectionName.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfSectionName.java
index 9e81df746eec..09d0366fa9c6 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfSectionName.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfSectionName.java
@@ -33,6 +33,7 @@
 public enum DwarfSectionName {
     TEXT_SECTION(".text"),
     DW_STR_SECTION(".debug_str"),
+    DW_LINE_STR_SECTION(".debug_line_str"),
     DW_LINE_SECTION(".debug_line"),
     DW_FRAME_SECTION(".debug_frame"),
     DW_ABBREV_SECTION(".debug_abbrev"),

From b05b63f4d87b293c0df105455778b7892f944cf6 Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Wed, 5 Mar 2025 15:31:36 +0100
Subject: [PATCH 29/34] Cleanup and changes based on reviews

---
 substratevm/CHANGELOG.md                      |  2 +-
 substratevm/debug/gdbpy/gdb-debughelpers.py   | 58 ++++++-------------
 substratevm/mx.substratevm/mx_substratevm.py  | 16 ++---
 .../src/com/oracle/objectfile/ObjectFile.java |  2 +-
 .../com/oracle/svm/core/SubstrateOptions.java | 28 ++++-----
 .../code/InstalledCodeObserverSupport.java    | 28 ++++++---
 ...JITInterface.java => GdbJitInterface.java} |  6 +-
 .../core/debug/SharedDebugInfoProvider.java   |  2 +-
 .../core/debug/SubstrateDebugInfoFeature.java |  2 +-
 .../debug/SubstrateDebugInfoInstaller.java    | 22 +++----
 .../debug/SubstrateDebugInfoProvider.java     | 24 +++++++-
 .../GraalGraphObjectReplacer.java             | 13 +++--
 .../image/NativeImageDebugInfoFeature.java    | 25 +++++---
 .../image/NativeImageDebugInfoProvider.java   |  4 +-
 14 files changed, 124 insertions(+), 108 deletions(-)
 rename substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/{GDBJITInterface.java => GdbJitInterface.java} (97%)

diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md
index a4d0d486566d..9691d3dc7690 100644
--- a/substratevm/CHANGELOG.md
+++ b/substratevm/CHANGELOG.md
@@ -9,7 +9,7 @@ This changelog summarizes major changes to GraalVM Native Image.
 * (GR-59864) Added JVM version check to the Native Image agent. The agent will abort execution if the JVM major version does not match the version it was built with, and warn if the full JVM version is different.
 * (GR-59135) Verify if hosted options passed to `native-image` exist prior to starting the builder. Provide suggestions how to fix unknown options early on.
 * (GR-61492) The experimental JDWP option is now present in standard GraalVM builds.
-* (GR-54697) Parallelize debug info generator and add support run-time debug info generation. `-H:+RuntimeDebugInfo` adds a run-time debug info generator into a native image. The run-time debug info generator notifies GDB via the [JIT Compilation Interface](https://sourceware.org/gdb/current/onlinedocs/gdb.html/JIT-Interface.html) about new object files for run-time compilations.
+* (GR-54697) Parallelize debug info generation and add support for run-time debug info generation. `-H:+RuntimeDebugInfo` adds a run-time debug info generator into a native image for use with GDB.
 
 ## GraalVM for JDK 24 (Internal Version 24.2.0)
 * (GR-59717) Added `DuringSetupAccess.registerObjectReachabilityHandler` to allow registering a callback that is executed when an object of a specified type is marked as reachable during heap scanning.
diff --git a/substratevm/debug/gdbpy/gdb-debughelpers.py b/substratevm/debug/gdbpy/gdb-debughelpers.py
index cd4e3b6c2aa1..2fb4f2a45162 100644
--- a/substratevm/debug/gdbpy/gdb-debughelpers.py
+++ b/substratevm/debug/gdbpy/gdb-debughelpers.py
@@ -97,14 +97,6 @@ class SVMUtil:
 
     hlreps = dict()
 
-    # AMD64 registers
-    AMD64_RSP = 7
-    AMD64_RIP = 16
-
-    # aarch64 registers
-    AARCH64_RSP = 30
-    AARCH64_RIP = 31
-
     # static methods
     @staticmethod
     def get_deopt_stub_adr() -> int:
@@ -143,24 +135,6 @@ def prompt_hook(cls, current_prompt: str = None):
         cls.selfref_cycles.clear()
         SVMCommandPrint.cache.clear()
 
-    @classmethod
-    def get_rsp(cls) -> int:
-        arch = gdb.selected_frame().architecture().name()
-        if "x86-64" in arch:
-            return cls.AMD64_RSP
-        elif "aarch64" in arch:
-            return cls.AARCH64_RSP
-        return 0
-
-    @classmethod
-    def get_rip(cls) -> int:
-        arch = gdb.selected_frame().architecture().name()
-        if "x86-64" in arch:
-            return cls.AMD64_RIP
-        elif "aarch64" in arch:
-            return cls.AARCH64_RIP
-        return 0
-
     @classmethod
     # checks if node this is reachable from node other (this node is parent of other node)
     def is_reachable(cls, this: hex, other: hex) -> bool:
@@ -1626,26 +1600,26 @@ def __call__(self, pending_frame: gdb.Frame):
         if self.deopt_stub_adr == 0:
             self.deopt_stub_adr = SVMUtil.get_deopt_stub_adr()
 
-        rsp = 0
+        sp = 0
         try:
-            rsp = pending_frame.read_register('sp')
-            rip = pending_frame.read_register('pc')
-            if int(rip) == self.deopt_stub_adr:
-                deopt_frame_stack_slot = rsp.cast(self.svm_util.stack_type.pointer()).dereference()
+            sp = pending_frame.read_register('sp')
+            pc = pending_frame.read_register('pc')
+            if int(pc) == self.deopt_stub_adr:
+                deopt_frame_stack_slot = sp.cast(self.svm_util.stack_type.pointer()).dereference()
                 deopt_frame = deopt_frame_stack_slot.cast(self.svm_util.get_compressed_type(self.svm_util.object_type).pointer())
                 rtt = self.svm_util.get_rtt(deopt_frame)
                 deopt_frame = self.svm_util.cast_to(deopt_frame, rtt)
                 encoded_frame_size = self.svm_util.get_int_field(deopt_frame, 'sourceEncodedFrameSize')
                 source_frame_size = encoded_frame_size & ~self.svm_util.frame_size_status_mask
                 # Now find the register-values for the caller frame
-                unwind_info = pending_frame.create_unwind_info(gdb.unwinder.FrameId(rsp, rip))
-                caller_rsp = rsp + int(source_frame_size)
-                unwind_info.add_saved_register(SVMUtil.get_rsp(), gdb.Value(caller_rsp))
-                caller_rip = gdb.Value(caller_rsp - 8).cast(self.svm_util.stack_type.pointer()).dereference()
-                unwind_info.add_saved_register(SVMUtil.get_rip(), gdb.Value(caller_rip))
+                unwind_info = pending_frame.create_unwind_info(gdb.unwinder.FrameId(sp, pc))
+                caller_sp = sp + int(source_frame_size)
+                unwind_info.add_saved_register('sp', gdb.Value(caller_sp))
+                caller_pc = gdb.Value(caller_sp - 8).cast(self.svm_util.stack_type.pointer()).dereference()
+                unwind_info.add_saved_register('pc', gdb.Value(caller_pc))
                 return unwind_info
         except Exception as ex:
-            trace(f'<SVMFrameUnwinder> - Failed to unwind frame at {hex(rsp)}')
+            trace(f'<SVMFrameUnwinder> - Failed to unwind frame at {hex(sp)}')
             trace(ex)
             # Fallback to default frame unwinding via debug_frame (dwarf)
 
@@ -1712,9 +1686,9 @@ def __init__(self, svm_util: SVMUtil, frame: gdb.Frame, deopt_stub_adr: int):
         super().__init__(frame)
 
         # fetch deoptimized frame from stack
-        rsp = frame.read_register('sp')
+        sp = frame.read_register('sp')
         assert deopt_stub_adr and frame.pc() == deopt_stub_adr
-        deopt_frame_stack_slot = rsp.cast(svm_util.stack_type.pointer()).dereference()
+        deopt_frame_stack_slot = sp.cast(svm_util.stack_type.pointer()).dereference()
         deopt_frame = deopt_frame_stack_slot.cast(svm_util.get_compressed_type(svm_util.object_type).pointer())
         rtt = svm_util.get_rtt(deopt_frame)
         deopt_frame = svm_util.cast_to(deopt_frame, rtt)
@@ -1754,11 +1728,13 @@ def filename(self):
             return None
 
         source_class = self.__svm_util.get_obj_field(self.__frame_info, 'sourceClass')
+        companion = self.__svm_util.get_obj_field(source_class, 'companion')
+        source_file_name = self.__svm_util.get_obj_field(companion, 'sourceFileName')
 
-        if self.__svm_util.is_null(source_class):
+        if self.__svm_util.is_null(source_file_name):
             source_file_name = ''
         else:
-            source_file_name = str(self.__svm_util.get_obj_field(source_class, 'sourceFileName'))[1:-1]
+            source_file_name = str(source_file_name)[1:-1]
 
         return source_file_name
 
diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py
index f906429d8945..031ff0e296b0 100644
--- a/substratevm/mx.substratevm/mx_substratevm.py
+++ b/substratevm/mx.substratevm/mx_substratevm.py
@@ -1146,7 +1146,7 @@ def run_debug_test(image_name: str, testfile: str, source_path: str, with_isolat
                 '-iex', f"set auto-load safe-path {join(build_dir, 'gdb-debughelpers.py')}",
                 '-x', testfile, join(build_dir, image_name)
             ]
-            mx.log(' '.join([(f"'{c}'" if ' ' in c else c) for c in gdb_command]))
+            log_gdb_command(gdb_command)
             # unittest may result in different exit code, nonZeroIsFatal ensures that we can go on with other test
             return mx.run(gdb_command, cwd=build_dir, nonZeroIsFatal=False)
         return 0
@@ -1173,7 +1173,11 @@ def run_debug_test(image_name: str, testfile: str, source_path: str, with_isolat
         mx.abort(status)
 
 
-def _runtimedebuginfotest(native_image, output_path, with_isolates_only, args=None):
+def log_gdb_command(gdb_command):
+    mx.log(' '.join([(f"'{c}'" if ' ' in c else c) for c in gdb_command]))
+
+
+def _runtimedebuginfotest(native_image, output_path, args=None):
     """Build and run the runtimedebuginfotest"""
 
     args = [] if args is None else args
@@ -1225,7 +1229,7 @@ def _runtimedebuginfotest(native_image, output_path, with_isolates_only, args=No
         '-iex', f"set auto-load safe-path {join(output_path, 'gdb-debughelpers.py')}",
         '-x', test_runtime_compilation_py, runtime_compile_binary
     ]
-    mx.log(' '.join([(f"'{c}'" if ' ' in c else c) for c in gdb_command]))
+    log_gdb_command(gdb_command)
     # unittest may result in different exit code, nonZeroIsFatal ensures that we can go on with other test
     status = mx.run(gdb_command, cwd=output_path, nonZeroIsFatal=False)
 
@@ -1247,7 +1251,7 @@ def _runtimedebuginfotest(native_image, output_path, with_isolates_only, args=No
         '-iex', f"set auto-load safe-path {join(output_path, 'gdb-debughelpers.py')}",
         '-x', test_runtime_deopt_py, '--args', js_launcher, testdeopt_js
     ]
-    mx.log(' '.join([(f"'{c}'" if ' ' in c else c) for c in gdb_command]))
+    log_gdb_command(gdb_command)
     # unittest may result in different exit code, nonZeroIsFatal ensures that we can go on with other test
     status |= mx.run(gdb_command, cwd=output_path, nonZeroIsFatal=False)
 
@@ -1855,14 +1859,12 @@ def runtimedebuginfotest(args, config=None):
     all_args = ['--output-path', '--with-isolates-only']
     masked_args = [_mask(arg, all_args) for arg in args]
     parser.add_argument(all_args[0], metavar='<output-path>', nargs=1, help='Path of the generated image', default=[join(svmbuild_dir(), "runtimedebuginfotest")])
-    parser.add_argument(all_args[1], 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]
-    with_isolates_only = parsed.with_isolates_only
     native_image_context_run(
         lambda native_image, a:
-        _runtimedebuginfotest(native_image, output_path, with_isolates_only, a), unmask(parsed.image_args),
+        _runtimedebuginfotest(native_image, output_path, a), unmask(parsed.image_args),
         config=config
     )
 
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/ObjectFile.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/ObjectFile.java
index 23853be51e0b..c758f06eaa8d 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/ObjectFile.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/ObjectFile.java
@@ -223,7 +223,7 @@ private static ObjectFile getNativeObjectFile(int pageSize, boolean runtimeDebug
             case ELF -> new ELFObjectFile(pageSize, runtimeDebugInfoGeneration);
             case MACH_O -> new MachOObjectFile(pageSize);
             case PECOFF -> new PECoffObjectFile(pageSize);
-            default -> throw new AssertionError("Unreachable");
+            case LLVM -> throw new AssertionError("Unsupported NativeObjectFile for format " + ObjectFile.getNativeFormat());
         };
     }
 
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java
index 71da84ffd99b..ea0268ae6ec0 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java
@@ -45,7 +45,6 @@
 import org.graalvm.nativeimage.ImageSingletons;
 import org.graalvm.nativeimage.Platform;
 import org.graalvm.nativeimage.Platforms;
-import org.graalvm.nativeimage.ProcessProperties;
 
 import com.oracle.svm.core.c.libc.LibCBase;
 import com.oracle.svm.core.c.libc.MuslLibC;
@@ -476,22 +475,6 @@ public static void setImageLayerCreateEnabledHandler(OptionEnabledHandler<Boolea
     @Option(help = "Track NodeSourcePositions during runtime-compilation")//
     public static final HostedOptionKey<Boolean> IncludeNodeSourcePositions = new HostedOptionKey<>(false);
 
-    @Option(help = "Directory where Java source-files will be placed for the debugger")//
-    public static final RuntimeOptionKey<String> RuntimeSourceDestDir = new RuntimeOptionKey<>(null, RelevantForCompilationIsolates);
-
-    public static Path getRuntimeSourceDestDir() {
-        String sourceDestDir = RuntimeSourceDestDir.getValue();
-        if (sourceDestDir != null) {
-            return Paths.get(sourceDestDir);
-        }
-        Path result = Paths.get("sources");
-        Path exeDir = Paths.get(ProcessProperties.getExecutableName()).getParent();
-        if (exeDir != null) {
-            result = exeDir.resolve(result);
-        }
-        return result;
-    }
-
     @Option(help = "Provide debuginfo for runtime-compiled code.")//
     public static final HostedOptionKey<Boolean> RuntimeDebugInfo = new HostedOptionKey<>(false);
 
@@ -1023,6 +1006,17 @@ public static boolean useDebugInfoGeneration() {
         return useLIRBackend() && GenerateDebugInfo.getValue() > 0;
     }
 
+    // TODO: change default to 0
+    @Option(help = "Number of threads used to generate debug info.", deprecated = true) //
+    public static final HostedOptionKey<Integer> DebugInfoGenerationThreadCount = new HostedOptionKey<>(256, SubstrateOptions::validateDebugInfoGenerationThreadCount);
+
+    private static void validateDebugInfoGenerationThreadCount(HostedOptionKey<Integer> optionKey) {
+        int value = optionKey.getValue();
+        if (value <= 0) {
+            throw UserError.invalidOptionValue(optionKey, value, "The value must be bigger than 0");
+        }
+    }
+
     @Option(help = "Directory under which to create source file cache for Application or GraalVM classes")//
     static final HostedOptionKey<String> DebugInfoSourceCacheRoot = new HostedOptionKey<>("sources");
 
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/InstalledCodeObserverSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/InstalledCodeObserverSupport.java
index b25952a64950..cb68d519897b 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/InstalledCodeObserverSupport.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/InstalledCodeObserverSupport.java
@@ -45,13 +45,10 @@
 
 @AutomaticallyRegisteredImageSingleton
 public final class InstalledCodeObserverSupport {
-    private static final InstalledCodeObserverHandleAction ACTION_ATTACH = (h, a, i) -> getAccessor(h).attachToCurrentIsolate(h);
-    private static final InstalledCodeObserverHandleAction ACTION_DETACH = (h, a, i) -> getAccessor(h).detachFromCurrentIsolate(h);
-    private static final InstalledCodeObserverHandleAction ACTION_RELEASE = (h, a, i) -> {
-        getAccessor(h).release(h);
-        NonmovableArrays.setWord(a, i, Word.nullPointer());
-    };
-    private static final InstalledCodeObserverHandleAction ACTION_ACTIVATE = (h, a, i) -> getAccessor(h).activate(h);
+    private static final InstalledCodeObserverHandleAction ACTION_ATTACH = h -> getAccessor(h).attachToCurrentIsolate(h);
+    private static final InstalledCodeObserverHandleAction ACTION_DETACH = h -> getAccessor(h).detachFromCurrentIsolate(h);
+    private static final InstalledCodeObserverHandleAction ACTION_RELEASE = h -> getAccessor(h).release(h);
+    private static final InstalledCodeObserverHandleAction ACTION_ACTIVATE = h -> getAccessor(h).activate(h);
 
     private final List<InstalledCodeObserver.Factory> observerFactories = new ArrayList<>();
 
@@ -106,10 +103,11 @@ public static void attachToCurrentIsolate(NonmovableArray<InstalledCodeObserverH
 
     public static void removeObservers(NonmovableArray<InstalledCodeObserverHandle> observerHandles) {
         forEach(observerHandles, ACTION_RELEASE);
+        clearObserverHandles(observerHandles);
     }
 
     private interface InstalledCodeObserverHandleAction {
-        void invoke(InstalledCodeObserverHandle handle, NonmovableArray<InstalledCodeObserverHandle> observerHandles, int index);
+        void invoke(InstalledCodeObserverHandle handle);
     }
 
     private static void forEach(NonmovableArray<InstalledCodeObserverHandle> array, InstalledCodeObserverHandleAction action) {
@@ -118,7 +116,19 @@ private static void forEach(NonmovableArray<InstalledCodeObserverHandle> array,
             for (int i = 0; i < length; i++) {
                 InstalledCodeObserverHandle handle = NonmovableArrays.getWord(array, i);
                 if (handle.isNonNull()) {
-                    action.invoke(handle, array, i);
+                    action.invoke(handle);
+                }
+            }
+        }
+    }
+
+    private static void clearObserverHandles(NonmovableArray<InstalledCodeObserverHandle> array) {
+        if (array.isNonNull()) {
+            int length = NonmovableArrays.lengthOf(array);
+            for (int i = 0; i < length; i++) {
+                InstalledCodeObserverHandle handle = NonmovableArrays.getWord(array, i);
+                if (handle.isNonNull()) {
+                    NonmovableArrays.setWord(array, i, Word.nullPointer());
                 }
             }
         }
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterface.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GdbJitInterface.java
similarity index 97%
rename from substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterface.java
rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GdbJitInterface.java
index a58573827ab7..bcb0c7e539ae 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GDBJITInterface.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GdbJitInterface.java
@@ -50,10 +50,10 @@
 
 import jdk.graal.compiler.word.Word;
 
-@CContext(GDBJITInterface.GDBJITInterfaceDirectives.class)
-public class GDBJITInterface {
+@CContext(GdbJitInterface.GdbJitInterfaceDirectives.class)
+public class GdbJitInterface {
 
-    public static class GDBJITInterfaceDirectives implements CContext.Directives {
+    public static class GdbJitInterfaceDirectives implements CContext.Directives {
         @Override
         public boolean isInConfiguration() {
             return SubstrateOptions.RuntimeDebugInfo.getValue();
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 544ef2ea1127..1767a8a316cb 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
@@ -788,7 +788,7 @@ protected long getTypeSignature(String typeName) {
     protected String getMethodName(SharedMethod method) {
         String name = method.getName();
         // replace <init> (method name of a constructor) with the class name
-        if (name.equals("<init>")) {
+        if (method.isConstructor()) {
             name = method.format("%h");
             if (name.indexOf('$') >= 0) {
                 name = name.substring(name.lastIndexOf('$') + 1);
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoFeature.java
index d73e980f182c..a0ae63c381a3 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoFeature.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoFeature.java
@@ -55,6 +55,6 @@ public void registerCodeObserver(RuntimeConfiguration runtimeConfig) {
         // This is called at image build-time -> the factory then creates a RuntimeDebugInfoProvider
         // at runtime
         ImageSingletons.lookup(InstalledCodeObserverSupport.class).addObserverFactory(new SubstrateDebugInfoInstaller.Factory(runtimeConfig.getProviders().getMetaAccess(), runtimeConfig));
-        ImageSingletons.add(SubstrateDebugInfoInstaller.GDBJITAccessor.class, new SubstrateDebugInfoInstaller.GDBJITAccessor());
+        ImageSingletons.add(SubstrateDebugInfoInstaller.GdbJitAccessor.class, new SubstrateDebugInfoInstaller.GdbJitAccessor());
     }
 }
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
index b83caba2fef4..55380b813e97 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
@@ -123,10 +123,10 @@ private interface Handle extends InstalledCodeObserverHandle {
         int RELEASED = 2;
 
         @RawField
-        GDBJITInterface.JITCodeEntry getRawHandle();
+        GdbJitInterface.JITCodeEntry getRawHandle();
 
         @RawField
-        void setRawHandle(GDBJITInterface.JITCodeEntry value);
+        void setRawHandle(GdbJitInterface.JITCodeEntry value);
 
         @RawField
         NonmovableArray<Byte> getDebugInfoData();
@@ -141,12 +141,12 @@ private interface Handle extends InstalledCodeObserverHandle {
         void setState(int value);
     }
 
-    static final class GDBJITAccessor implements InstalledCodeObserverHandleAccessor {
+    static final class GdbJitAccessor implements InstalledCodeObserverHandleAccessor {
 
         static Handle createHandle(NonmovableArray<Byte> debugInfoData) {
             Handle handle = NativeMemory.malloc(SizeOf.get(Handle.class), NmtCategory.Code);
-            GDBJITInterface.JITCodeEntry entry = NativeMemory.calloc(SizeOf.get(GDBJITInterface.JITCodeEntry.class), NmtCategory.Code);
-            handle.setAccessor(ImageSingletons.lookup(GDBJITAccessor.class));
+            GdbJitInterface.JITCodeEntry entry = NativeMemory.calloc(SizeOf.get(GdbJitInterface.JITCodeEntry.class), NmtCategory.Code);
+            handle.setAccessor(ImageSingletons.lookup(GdbJitAccessor.class));
             handle.setRawHandle(entry);
             handle.setDebugInfoData(debugInfoData);
             handle.setState(Handle.INITIALIZED);
@@ -162,7 +162,7 @@ public void activate(InstalledCodeObserverHandle installedCodeObserverHandle) {
             NonmovableArray<Byte> debugInfoData = handle.getDebugInfoData();
             CCharPointer address = NonmovableArrays.addressOf(debugInfoData, 0);
             int size = NonmovableArrays.lengthOf(debugInfoData);
-            GDBJITInterface.registerJITCode(address, size, handle.getRawHandle());
+            GdbJitInterface.registerJITCode(address, size, handle.getRawHandle());
 
             handle.setState(Handle.ACTIVATED);
         }
@@ -171,10 +171,10 @@ public void activate(InstalledCodeObserverHandle installedCodeObserverHandle) {
         @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
         public void release(InstalledCodeObserverHandle installedCodeObserverHandle) {
             Handle handle = (Handle) installedCodeObserverHandle;
-            GDBJITInterface.JITCodeEntry entry = handle.getRawHandle();
+            GdbJitInterface.JITCodeEntry entry = handle.getRawHandle();
             // Handle may still be just initialized here, so it never got registered in GDB.
             if (handle.getState() == Handle.ACTIVATED) {
-                GDBJITInterface.unregisterJITCode(entry);
+                GdbJitInterface.unregisterJITCode(entry);
                 handle.setState(Handle.RELEASED);
             }
             NativeMemory.free(entry);
@@ -198,10 +198,10 @@ public void attachToCurrentIsolate(InstalledCodeObserverHandle installedCodeObse
         @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
         public void releaseOnTearDown(InstalledCodeObserverHandle installedCodeObserverHandle) {
             Handle handle = (Handle) installedCodeObserverHandle;
-            GDBJITInterface.JITCodeEntry entry = handle.getRawHandle();
+            GdbJitInterface.JITCodeEntry entry = handle.getRawHandle();
             // Handle may still be just initialized here, so it never got registered in GDB.
             if (handle.getState() == Handle.ACTIVATED) {
-                GDBJITInterface.unregisterJITCode(entry);
+                GdbJitInterface.unregisterJITCode(entry);
                 handle.setState(Handle.RELEASED);
             }
             NativeMemory.free(entry);
@@ -214,7 +214,7 @@ public void releaseOnTearDown(InstalledCodeObserverHandle installedCodeObserverH
     @SuppressWarnings("try")
     public InstalledCodeObserverHandle install() {
         NonmovableArray<Byte> debugInfoData = writeDebugInfoData();
-        Handle handle = GDBJITAccessor.createHandle(debugInfoData);
+        Handle handle = GdbJitAccessor.createHandle(debugInfoData);
         try (DebugContext.Scope s = debug.scope("RuntimeCompilation")) {
             debug.log(toString(handle));
         }
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
index 3ec817edfdfd..5ff16536e98e 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
@@ -30,6 +30,7 @@
 import java.util.stream.Stream;
 
 import org.graalvm.collections.Pair;
+import org.graalvm.nativeimage.ProcessProperties;
 
 import com.oracle.objectfile.debugentry.ArrayTypeEntry;
 import com.oracle.objectfile.debugentry.ClassEntry;
@@ -37,14 +38,15 @@
 import com.oracle.objectfile.debugentry.LoaderEntry;
 import com.oracle.objectfile.debugentry.PrimitiveTypeEntry;
 import com.oracle.objectfile.debugentry.TypeEntry;
-import com.oracle.svm.core.SubstrateOptions;
 import com.oracle.svm.core.SubstrateUtil;
 import com.oracle.svm.core.graal.meta.RuntimeConfiguration;
 import com.oracle.svm.core.meta.SharedMethod;
 import com.oracle.svm.core.meta.SharedType;
+import com.oracle.svm.core.option.RuntimeOptionKey;
 
 import jdk.graal.compiler.code.CompilationResult;
 import jdk.graal.compiler.debug.DebugContext;
+import jdk.graal.compiler.options.Option;
 import jdk.vm.ci.meta.JavaConstant;
 import jdk.vm.ci.meta.JavaKind;
 import jdk.vm.ci.meta.MetaAccessProvider;
@@ -64,6 +66,24 @@
  */
 public class SubstrateDebugInfoProvider extends SharedDebugInfoProvider {
 
+    public static class Options {
+        @Option(help = "Directory where Java source-files will be placed for the debugger")//
+        public static final RuntimeOptionKey<String> RuntimeSourceDestDir = new RuntimeOptionKey<>(null, RuntimeOptionKey.RuntimeOptionKeyFlag.RelevantForCompilationIsolates);
+
+        public static Path getRuntimeSourceDestDir() {
+            String sourceDestDir = RuntimeSourceDestDir.getValue();
+            if (sourceDestDir != null) {
+                return Path.of(sourceDestDir);
+            }
+            Path result = Path.of("sources");
+            Path exeDir = Path.of(ProcessProperties.getExecutableName()).getParent();
+            if (exeDir != null) {
+                result = exeDir.resolve(result);
+            }
+            return result;
+        }
+    }
+
     private final SharedMethod sharedMethod;
     private final CompilationResult compilation;
     private final long codeAddress;
@@ -98,7 +118,7 @@ public String getCompilationName() {
 
     @Override
     public String cachePath() {
-        return SubstrateOptions.getRuntimeSourceDestDir().toString();
+        return Options.getRuntimeSourceDestDir().toString();
     }
 
     @Override
diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/GraalGraphObjectReplacer.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/GraalGraphObjectReplacer.java
index 13e85c24f0c1..8036300ac58c 100644
--- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/GraalGraphObjectReplacer.java
+++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/GraalGraphObjectReplacer.java
@@ -43,6 +43,7 @@
 import com.oracle.graal.pointsto.meta.AnalysisType;
 import com.oracle.graal.pointsto.meta.AnalysisUniverse;
 import com.oracle.svm.common.meta.MultiMethod;
+import com.oracle.svm.core.SubstrateOptions;
 import com.oracle.svm.core.code.CodeInfoTable;
 import com.oracle.svm.core.code.ImageCodeInfo;
 import com.oracle.svm.core.graal.meta.SharedRuntimeMethod;
@@ -283,10 +284,14 @@ public synchronized SubstrateMethod createMethod(ResolvedJavaMethod original) {
                  * infinite recursion.
                  */
                 LocalVariableTable localVariableTable;
-                try {
-                    localVariableTable = createLocalVariableTable(aMethod.getLocalVariableTable());
-                } catch (IllegalStateException e) {
-                    LogUtils.warning("Omit invalid local variable table from method %s", sMethod.getName());
+                if (SubstrateOptions.RuntimeDebugInfo.getValue()) {
+                    try {
+                        localVariableTable = createLocalVariableTable(aMethod.getLocalVariableTable());
+                    } catch (IllegalStateException e) {
+                        LogUtils.warning("Omit invalid local variable table from method %s", sMethod.getName());
+                        localVariableTable = null;
+                    }
+                } else {
                     localVariableTable = null;
                 }
                 sMethod.setLinks(createSignature(aMethod.getSignature()), createType(aMethod.getDeclaringClass()), localVariableTable);
diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoFeature.java
index e8c8a0780187..ad824534579c 100644
--- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoFeature.java
+++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoFeature.java
@@ -28,13 +28,10 @@
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.List;
+import java.util.concurrent.ForkJoinPool;
 import java.util.function.Function;
 import java.util.function.Supplier;
 
-import com.oracle.svm.core.ReservedRegisters;
-import jdk.graal.compiler.word.Word;
-import com.oracle.svm.core.code.CodeInfoDecoder;
-import jdk.vm.ci.code.Architecture;
 import org.graalvm.nativeimage.ImageSingletons;
 import org.graalvm.nativeimage.Platform;
 import org.graalvm.nativeimage.c.struct.SizeOf;
@@ -46,14 +43,16 @@
 import com.oracle.objectfile.BasicProgbitsSectionImpl;
 import com.oracle.objectfile.debuginfo.DebugInfoProvider;
 import com.oracle.objectfile.io.AssemblyBuffer;
+import com.oracle.svm.core.ReservedRegisters;
 import com.oracle.svm.core.SubstrateOptions;
 import com.oracle.svm.core.UniqueShortNameProvider;
 import com.oracle.svm.core.UniqueShortNameProviderDefaultImpl;
 import com.oracle.svm.core.c.CGlobalData;
 import com.oracle.svm.core.c.CGlobalDataFactory;
+import com.oracle.svm.core.code.CodeInfoDecoder;
 import com.oracle.svm.core.config.ConfigurationValues;
 import com.oracle.svm.core.debug.BFDNameProvider;
-import com.oracle.svm.core.debug.GDBJITInterface;
+import com.oracle.svm.core.debug.GdbJitInterface;
 import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature;
 import com.oracle.svm.core.feature.InternalFeature;
 import com.oracle.svm.core.graal.meta.RuntimeConfiguration;
@@ -69,6 +68,8 @@
 import jdk.graal.compiler.core.common.CompressEncoding;
 import jdk.graal.compiler.debug.DebugContext;
 import jdk.graal.compiler.printer.GraalDebugHandlersFactory;
+import jdk.graal.compiler.word.Word;
+import jdk.vm.ci.code.Architecture;
 
 @AutomaticallyRegisteredFeature
 @SuppressWarnings("unused")
@@ -144,7 +145,7 @@ public void beforeAnalysis(BeforeAnalysisAccess access) {
          */
         if (SubstrateOptions.RuntimeDebugInfo.getValue()) {
             Architecture arch = ConfigurationValues.getTarget().arch;
-            ByteBuffer buffer = ByteBuffer.allocate(SizeOf.get(GDBJITInterface.JITDescriptor.class)).order(arch.getByteOrder());
+            ByteBuffer buffer = ByteBuffer.allocate(SizeOf.get(GdbJitInterface.JITDescriptor.class)).order(arch.getByteOrder());
 
             /*
              * Set version to 1. Must be 1 otherwise GDB does not register breakpoints for the GDB
@@ -153,7 +154,7 @@ public void beforeAnalysis(BeforeAnalysisAccess access) {
             buffer.putInt(1);
 
             /* Set action flag to JIT_NOACTION (0). */
-            buffer.putInt(GDBJITInterface.JITActions.JIT_NOACTION.ordinal());
+            buffer.putInt(GdbJitInterface.JITActions.JIT_NOACTION.ordinal());
 
             /*
              * Set relevant entry to nullptr. This is the pointer to the debug info entry that is
@@ -184,7 +185,15 @@ public void beforeImageWrite(BeforeImageWriteAccess access) {
             DebugInfoProvider provider = new NativeImageDebugInfoProvider(debugContext, image.getCodeCache(), image.getHeap(), image.getNativeLibs(), accessImpl.getHostedMetaAccess(),
                             runtimeConfiguration);
             var objectFile = image.getObjectFile();
-            objectFile.installDebugInfo(provider);
+
+            int debugInfoGenerationThreadCount = SubstrateOptions.DebugInfoGenerationThreadCount.getValue();
+            if (debugInfoGenerationThreadCount > 0) {
+                try (ForkJoinPool threadPool = new ForkJoinPool(debugInfoGenerationThreadCount)) {
+                    threadPool.submit(() -> objectFile.installDebugInfo(provider)).join();
+                }
+            } else {
+                objectFile.installDebugInfo(provider);
+            }
 
             if (Platform.includedIn(Platform.LINUX.class) && SubstrateOptions.UseImagebuildDebugSections.getValue()) {
                 /*-
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 15c412e1b62b..943094ea4bab 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
@@ -476,7 +476,7 @@ protected String getMethodName(SharedMethod method) {
         if (method instanceof HostedMethod hostedMethod) {
             name = hostedMethod.getName();
             // replace <init> (method name of a constructor) with the class name
-            if (name.equals("<init>")) {
+            if (hostedMethod.isConstructor()) {
                 name = hostedMethod.getDeclaringClass().toJavaName();
                 if (name.indexOf('.') >= 0) {
                     name = name.substring(name.lastIndexOf('.') + 1);
@@ -561,7 +561,7 @@ private void processForeignTypeFields(HostedType type, ForeignTypeEntry foreignT
         ElementInfo elementInfo = nativeLibs.findElementInfo(type);
         if (elementInfo instanceof StructInfo) {
             elementInfo.getChildren().stream().filter(NativeImageDebugInfoProvider::isTypedField)
-                            .map(elt -> ((StructFieldInfo) elt))
+                            .map(StructFieldInfo.class::cast)
                             .sorted(Comparator.comparingInt(field -> field.getOffsetInfo().getProperty()))
                             .forEach(field -> {
                                 HostedType fieldType = getFieldType(field);

From a537f799c8c59366d710a6016cfac4026233f34e Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Wed, 5 Mar 2025 16:41:39 +0100
Subject: [PATCH 30/34] Cleanup after rebase

---
 .../svm/core/debug/SubstrateDebugInfoInstaller.java | 13 ++-----------
 1 file changed, 2 insertions(+), 11 deletions(-)

diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
index 55380b813e97..74fd34379fbe 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java
@@ -156,7 +156,7 @@ static Handle createHandle(NonmovableArray<Byte> debugInfoData) {
         @Override
         public void activate(InstalledCodeObserverHandle installedCodeObserverHandle) {
             Handle handle = (Handle) installedCodeObserverHandle;
-            VMOperation.guaranteeInProgress("SubstrateDebugInfoInstaller.Accessor.activate must run in a VMOperation");
+            VMOperation.guaranteeInProgressAtSafepoint("SubstrateDebugInfoInstaller.Accessor.activate must run in a VMOperation");
             VMError.guarantee(handle.getState() == Handle.INITIALIZED);
 
             NonmovableArray<Byte> debugInfoData = handle.getDebugInfoData();
@@ -197,16 +197,7 @@ public void attachToCurrentIsolate(InstalledCodeObserverHandle installedCodeObse
         @Override
         @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
         public void releaseOnTearDown(InstalledCodeObserverHandle installedCodeObserverHandle) {
-            Handle handle = (Handle) installedCodeObserverHandle;
-            GdbJitInterface.JITCodeEntry entry = handle.getRawHandle();
-            // Handle may still be just initialized here, so it never got registered in GDB.
-            if (handle.getState() == Handle.ACTIVATED) {
-                GdbJitInterface.unregisterJITCode(entry);
-                handle.setState(Handle.RELEASED);
-            }
-            NativeMemory.free(entry);
-            NonmovableArrays.releaseUnmanagedArray(handle.getDebugInfoData());
-            NativeMemory.free(handle);
+            release(installedCodeObserverHandle);
         }
     }
 

From 70815b833289c8a014f292f229c6bb69113b0052 Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Wed, 5 Mar 2025 17:43:23 +0100
Subject: [PATCH 31/34] Set default value for DebugInfoGenerationThreadCount to
 0

---
 .../src/com/oracle/svm/core/SubstrateOptions.java            | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java
index ea0268ae6ec0..d7800f1315c9 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java
@@ -1006,9 +1006,8 @@ public static boolean useDebugInfoGeneration() {
         return useLIRBackend() && GenerateDebugInfo.getValue() > 0;
     }
 
-    // TODO: change default to 0
-    @Option(help = "Number of threads used to generate debug info.", deprecated = true) //
-    public static final HostedOptionKey<Integer> DebugInfoGenerationThreadCount = new HostedOptionKey<>(256, SubstrateOptions::validateDebugInfoGenerationThreadCount);
+    @Option(help = "Number of threads used to generate debug info.") //
+    public static final HostedOptionKey<Integer> DebugInfoGenerationThreadCount = new HostedOptionKey<>(0, SubstrateOptions::validateDebugInfoGenerationThreadCount);
 
     private static void validateDebugInfoGenerationThreadCount(HostedOptionKey<Integer> optionKey) {
         int value = optionKey.getValue();

From 9da0f97401d4802389d97473d9858828eefef830 Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Tue, 11 Mar 2025 16:20:10 +0100
Subject: [PATCH 32/34] Fix deopt stub parsing and runtime debug info tests

---
 substratevm/debug/gdbpy/gdb-debughelpers.py    | 18 +++++++++---------
 .../debug/helper/test_runtime_compilation.py   |  6 ++++--
 .../test/debug/helper/test_runtime_deopt.py    | 15 +++++++++++----
 3 files changed, 24 insertions(+), 15 deletions(-)

diff --git a/substratevm/debug/gdbpy/gdb-debughelpers.py b/substratevm/debug/gdbpy/gdb-debughelpers.py
index 2fb4f2a45162..03510dbab1b5 100644
--- a/substratevm/debug/gdbpy/gdb-debughelpers.py
+++ b/substratevm/debug/gdbpy/gdb-debughelpers.py
@@ -100,7 +100,7 @@ class SVMUtil:
     # static methods
     @staticmethod
     def get_deopt_stub_adr() -> int:
-        return gdb.lookup_global_symbol('com.oracle.svm.core.deopt.Deoptimizer::deoptStub',
+        return gdb.lookup_global_symbol('com.oracle.svm.core.deopt.Deoptimizer::eagerDeoptStub',
                                         gdb.SYMBOL_VAR_DOMAIN).value().address
 
     @staticmethod
@@ -1631,17 +1631,18 @@ def __init__(self, svm_util: SVMUtil, deopt_stub_available: bool):
         self.name = "SubstrateVM FrameFilter"
         self.priority = 100
         self.enabled = True
-        self.deopt_stub_adr = 0 if deopt_stub_available else None
+        self.deopt_stub_available = deopt_stub_available
+        self.deopt_stub_adr = 0
         self.svm_util = svm_util
 
     def filter(self, frame_iter: Iterable) -> FrameDecorator:
-        if self.deopt_stub_adr == 0:
+        if self.deopt_stub_available and self.deopt_stub_adr == 0:
             self.deopt_stub_adr = SVMUtil.get_deopt_stub_adr()
 
         for frame in frame_iter:
             frame = frame.inferior_frame()
-            if self.deopt_stub_adr and frame.pc() == self.deopt_stub_adr:
-                yield SVMFrameDeopt(self.svm_util, frame, self.deopt_stub_adr)
+            if self.deopt_stub_available and int(frame.pc()) == self.deopt_stub_adr:
+                yield SVMFrameDeopt(self.svm_util, frame)
             else:
                 yield SVMFrame(frame)
 
@@ -1682,12 +1683,11 @@ def symbol(self):
 
 class SVMFrameDeopt(SVMFrame):
 
-    def __init__(self, svm_util: SVMUtil, frame: gdb.Frame, deopt_stub_adr: int):
+    def __init__(self, svm_util: SVMUtil, frame: gdb.Frame):
         super().__init__(frame)
 
         # fetch deoptimized frame from stack
         sp = frame.read_register('sp')
-        assert deopt_stub_adr and frame.pc() == deopt_stub_adr
         deopt_frame_stack_slot = sp.cast(svm_util.stack_type.pointer()).dereference()
         deopt_frame = deopt_frame_stack_slot.cast(svm_util.get_compressed_type(svm_util.object_type).pointer())
         rtt = svm_util.get_rtt(deopt_frame)
@@ -1779,8 +1779,8 @@ def register_objfile(objfile: gdb.Objfile):
     svm_util = SVMUtil()
     gdb.printing.register_pretty_printer(objfile, SVMPrettyPrinter(svm_util), True)
 
-    # deopt stub points to the wrong address at first -> set dummy value to fill later (0 from SVMUtil)
-    deopt_stub_available = gdb.lookup_global_symbol('com.oracle.svm.core.deopt.Deoptimizer::deoptStub',
+    # deopt stub points to the wrong address at first -> set dummy value to fill later
+    deopt_stub_available = gdb.lookup_global_symbol('com.oracle.svm.core.deopt.Deoptimizer::eagerDeoptStub',
                                                     gdb.SYMBOL_VAR_DOMAIN) is not None
 
     if deopt_stub_available:
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/src/com/oracle/svm/test/debug/helper/test_runtime_compilation.py
index 07ce191ce5ee..8687ffd698ad 100644
--- 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/src/com/oracle/svm/test/debug/helper/test_runtime_compilation.py
@@ -35,7 +35,8 @@
 from gdb_helper import *
 
 
-# doesn't require the gdb patch to be available,
+# shouldn't require the gdb patch to be available
+# however, running it without the patch causes an error in gdb
 # this just tests the jit compilation interface, not the generated debug info
 # however still requires symbols to be available for setting a breakpoint
 class TestJITCompilationInterface(unittest.TestCase):
@@ -44,6 +45,7 @@ def setUp(cls):
         set_up_test()
         gdb_delete_breakpoints()
         gdb_start()
+        gdb_execute("set dwarf-type-signature-fallback main")
 
     @classmethod
     def tearDown(cls):
@@ -136,7 +138,7 @@ def setUp(cls):
         gdb_delete_breakpoints()
         gdb_start()
         set_up_gdb_debughelpers()
-        gdb_execute("maintenance set dwarf type-signature-fallback main")
+        gdb_execute("set dwarf-type-signature-fallback main")
 
     @classmethod
     def tearDown(cls):
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/src/com/oracle/svm/test/debug/helper/test_runtime_deopt.py
index 0c6a379e8f1e..e894cc0c5b3c 100644
--- 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/src/com/oracle/svm/test/debug/helper/test_runtime_deopt.py
@@ -93,28 +93,35 @@ def test_backtrace_with_deopt(self):
     # this requires gdb to use the full type signature fallback
     def test_type_signature_fallback_full(self):
         # stop at a method where we know that the runtime compiled frame is in the backtrace
-        gdb_execute("maintenance set dwarf type-signature-fallback full")
+        gdb_execute("set dwarf-type-signature-fallback full")
         gdb_set_breakpoint("com.oracle.svm.core.deopt.Deoptimizer::invalidateMethodOfFrame")
         gdb_continue()
 
         # check backtrace
         backtrace = gdb_execute('backtrace 5')
         self.assertIn('com.oracle.truffle.runtime.OptimizedCallTarget::profiledPERoot', backtrace)
-        self.assertIn('(this=null, originalArguments=com.oracle.svm.core.option.RuntimeOptionKey = {...})', backtrace)
+        self.assertIn('(this=<optimized out>, originalArguments=com.oracle.svm.core.option.RuntimeOptionKey = {...})', backtrace)
         self.assertNotIn('this=<unknown type in <in-memory@', backtrace)
 
     # this should not work with just main objfile type signature fallback
     # the main objfile is the js launcher which has no debugging symbols
+    #
+    # Causes GDB to crash due to a GDB internal bug:
+    #   internal-error: copy: Assertion `m_contents != nullptr' failed.
+    # Essentially, GDB creates an ERROR_TYPE if a type signature is not found.
+    # During stack printing GDB copies parameter values.
+    # If one of the parameters has the ERROR_TYPE, GDB doesn't copy the content which causes an assertion error later.
+    @unittest.skip("GDB crashes without type signature fallback here.")
     def test_type_signature_fallback_main(self):
         # stop at a method where we know that the runtime compiled frame is in the backtrace
-        gdb_execute("maintenance set dwarf type-signature-fallback main")
+        gdb_execute("set dwarf-type-signature-fallback main")
         gdb_set_breakpoint("com.oracle.svm.core.deopt.Deoptimizer::invalidateMethodOfFrame")
         gdb_continue()
 
         # check backtrace
         backtrace = gdb_execute('backtrace 5')
         self.assertIn('com.oracle.truffle.runtime.OptimizedCallTarget::profiledPERoot', backtrace)
-        self.assertNotIn('(this=null, originalArguments=com.oracle.svm.core.option.RuntimeOptionKey = {...})', backtrace)
+        self.assertNotIn('(this=<optimized out>, originalArguments=com.oracle.svm.core.option.RuntimeOptionKey = {...})', backtrace)
         self.assertIn('this=<unknown type in <in-memory@', backtrace)
         self.assertIn('originalArguments=<unknown type in <in-memory@', backtrace)
 

From 51818a94699a3e0ea876ae467c9a233144f9a6fb Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Tue, 25 Mar 2025 15:21:52 +0100
Subject: [PATCH 33/34] Add support for lazy deoptimization for frame unwinder
 and frame filter

---
 substratevm/debug/gdbpy/gdb-debughelpers.py   | 91 ++++++++++++++-----
 substratevm/mx.substratevm/mx_substratevm.py  | 47 +++++-----
 .../test/debug/helper/test_runtime_deopt.py   | 15 ++-
 3 files changed, 107 insertions(+), 46 deletions(-)

diff --git a/substratevm/debug/gdbpy/gdb-debughelpers.py b/substratevm/debug/gdbpy/gdb-debughelpers.py
index 03510dbab1b5..f2e72cd86a50 100644
--- a/substratevm/debug/gdbpy/gdb-debughelpers.py
+++ b/substratevm/debug/gdbpy/gdb-debughelpers.py
@@ -99,9 +99,19 @@ class SVMUtil:
 
     # static methods
     @staticmethod
-    def get_deopt_stub_adr() -> int:
-        return gdb.lookup_global_symbol('com.oracle.svm.core.deopt.Deoptimizer::eagerDeoptStub',
-                                        gdb.SYMBOL_VAR_DOMAIN).value().address
+    def get_eager_deopt_stub_adr() -> int:
+        sym = gdb.lookup_global_symbol('com.oracle.svm.core.deopt.Deoptimizer::eagerDeoptStub', gdb.SYMBOL_VAR_DOMAIN)
+        return sym.value().address if sym is not None else -1
+
+    @staticmethod
+    def get_lazy_deopt_stub_primitive_adr() -> int:
+        sym = gdb.lookup_global_symbol('com.oracle.svm.core.deopt.Deoptimizer::lazyDeoptStubPrimitiveReturn', gdb.SYMBOL_VAR_DOMAIN)
+        return sym.value().address if sym is not None else -1
+
+    @staticmethod
+    def get_lazy_deopt_stub_object_adr() -> int:
+        sym = gdb.lookup_global_symbol('com.oracle.svm.core.deopt.Deoptimizer::lazyDeoptStubObjectReturn', gdb.SYMBOL_VAR_DOMAIN)
+        return sym.value().address if sym is not None else -1
 
     @staticmethod
     def get_unqualified_type_name(qualified_type_name: str) -> str:
@@ -1593,31 +1603,46 @@ class SVMFrameUnwinder(gdb.unwinder.Unwinder):
 
     def __init__(self, svm_util: SVMUtil):
         super().__init__('SubstrateVM FrameUnwinder')
-        self.deopt_stub_adr = 0
+        self.eager_deopt_stub_adr = None
+        self.lazy_deopt_stub_primitive_adr = None
+        self.lazy_deopt_stub_object_adr = None
         self.svm_util = svm_util
 
     def __call__(self, pending_frame: gdb.Frame):
-        if self.deopt_stub_adr == 0:
-            self.deopt_stub_adr = SVMUtil.get_deopt_stub_adr()
+        if self.eager_deopt_stub_adr is None:
+            self.eager_deopt_stub_adr = SVMUtil.get_eager_deopt_stub_adr()
+            self.lazy_deopt_stub_primitive_adr = SVMUtil.get_lazy_deopt_stub_primitive_adr()
+            self.lazy_deopt_stub_object_adr = SVMUtil.get_lazy_deopt_stub_object_adr()
 
         sp = 0
         try:
             sp = pending_frame.read_register('sp')
             pc = pending_frame.read_register('pc')
-            if int(pc) == self.deopt_stub_adr:
+            if int(pc) == self.eager_deopt_stub_adr:
                 deopt_frame_stack_slot = sp.cast(self.svm_util.stack_type.pointer()).dereference()
                 deopt_frame = deopt_frame_stack_slot.cast(self.svm_util.get_compressed_type(self.svm_util.object_type).pointer())
                 rtt = self.svm_util.get_rtt(deopt_frame)
                 deopt_frame = self.svm_util.cast_to(deopt_frame, rtt)
                 encoded_frame_size = self.svm_util.get_int_field(deopt_frame, 'sourceEncodedFrameSize')
                 source_frame_size = encoded_frame_size & ~self.svm_util.frame_size_status_mask
+
                 # Now find the register-values for the caller frame
                 unwind_info = pending_frame.create_unwind_info(gdb.unwinder.FrameId(sp, pc))
                 caller_sp = sp + int(source_frame_size)
                 unwind_info.add_saved_register('sp', gdb.Value(caller_sp))
+                # try to fetch return address directly from stack
                 caller_pc = gdb.Value(caller_sp - 8).cast(self.svm_util.stack_type.pointer()).dereference()
                 unwind_info.add_saved_register('pc', gdb.Value(caller_pc))
                 return unwind_info
+            elif int(pc) == self.lazy_deopt_stub_primitive_adr or int(pc) == self.lazy_deopt_stub_object_adr:
+                # Now find the register-values for the caller frame
+                # We only have the original pc for lazy deoptimization -> unwind to original pc with same sp
+                # This is the best guess we can make without knowing the return address and frame size of the lazily deoptimized frame
+                unwind_info = pending_frame.create_unwind_info(gdb.unwinder.FrameId(sp, pc))
+                unwind_info.add_saved_register('sp', gdb.Value(sp))
+                caller_pc = sp.cast(self.svm_util.stack_type.pointer()).dereference()
+                unwind_info.add_saved_register('pc', gdb.Value(caller_pc))
+                return unwind_info
         except Exception as ex:
             trace(f'<SVMFrameUnwinder> - Failed to unwind frame at {hex(sp)}')
             trace(ex)
@@ -1627,22 +1652,28 @@ def __call__(self, pending_frame: gdb.Frame):
 
 
 class SVMFrameFilter:
-    def __init__(self, svm_util: SVMUtil, deopt_stub_available: bool):
+    def __init__(self, svm_util: SVMUtil):
         self.name = "SubstrateVM FrameFilter"
         self.priority = 100
         self.enabled = True
-        self.deopt_stub_available = deopt_stub_available
-        self.deopt_stub_adr = 0
+        self.eager_deopt_stub_adr = None
+        self.lazy_deopt_stub_primitive_adr = None
+        self.lazy_deopt_stub_object_adr = None
         self.svm_util = svm_util
 
     def filter(self, frame_iter: Iterable) -> FrameDecorator:
-        if self.deopt_stub_available and self.deopt_stub_adr == 0:
-            self.deopt_stub_adr = SVMUtil.get_deopt_stub_adr()
+        if self.eager_deopt_stub_adr is None:
+            self.eager_deopt_stub_adr = SVMUtil.get_eager_deopt_stub_adr()
+            self.lazy_deopt_stub_primitive_adr = SVMUtil.get_lazy_deopt_stub_primitive_adr()
+            self.lazy_deopt_stub_object_adr = SVMUtil.get_lazy_deopt_stub_object_adr()
 
         for frame in frame_iter:
             frame = frame.inferior_frame()
-            if self.deopt_stub_available and int(frame.pc()) == self.deopt_stub_adr:
-                yield SVMFrameDeopt(self.svm_util, frame)
+            pc = int(frame.pc())
+            if pc == self.eager_deopt_stub_adr:
+                yield SVMFrameEagerDeopt(self.svm_util, frame)
+            elif pc == self.lazy_deopt_stub_primitive_adr or pc == self.lazy_deopt_stub_object_adr:
+                yield SVMFrameLazyDeopt(self.svm_util, frame)
             else:
                 yield SVMFrame(frame)
 
@@ -1681,7 +1712,7 @@ def symbol(self):
         return self.sym
 
 
-class SVMFrameDeopt(SVMFrame):
+class SVMFrameEagerDeopt(SVMFrame):
 
     def __init__(self, svm_util: SVMUtil, frame: gdb.Frame):
         super().__init__(frame)
@@ -1699,7 +1730,7 @@ def __init__(self, svm_util: SVMUtil, frame: gdb.Frame):
     def function(self) -> str:
         if self.__frame_info is None or self.__svm_util.is_null(self.__frame_info):
             # we have no more information about the frame
-            return '[DEOPT FRAME ...]'
+            return '[EAGER DEOPT FRAME ...]'
 
         # read from deoptimized frame
         source_class = self.__svm_util.get_obj_field(self.__frame_info, 'sourceClass')
@@ -1721,7 +1752,7 @@ def function(self) -> str:
 
         func_name = str(self.__svm_util.get_obj_field(self.__frame_info, 'sourceMethodName'))[1:-1]
 
-        return '[DEOPT FRAME] ' + source_class_name + func_name + source_file_name
+        return '[EAGER DEOPT FRAME] ' + source_class_name + func_name + source_file_name
 
     def filename(self):
         if self.__frame_info is None or self.__svm_util.is_null(self.__frame_info):
@@ -1767,6 +1798,25 @@ def frame_locals(self):
         return None
 
 
+class SVMFrameLazyDeopt(SVMFrame):
+
+    def __init__(self, svm_util: SVMUtil, frame: gdb.Frame):
+        super().__init__(frame)
+
+        # fetch deoptimized frame from stack
+        sp = frame.read_register('sp')
+        real_pc = sp.cast(svm_util.stack_type.pointer()).dereference().cast(svm_util.stack_type.pointer())
+        self.__gdb_text = str(real_pc)
+        self.__svm_util = svm_util
+
+    def function(self) -> str:
+        if self.__gdb_text is None:
+            # we have no more information about the frame
+            return '[LAZY DEOPT FRAME ...]'
+
+        return '[LAZY DEOPT FRAME] at ' + self.__gdb_text
+
+
 try:
     svminitfile = os.path.expandvars('${SVMGDBINITFILE}')
     exec(open(svminitfile).read())
@@ -1779,14 +1829,13 @@ def register_objfile(objfile: gdb.Objfile):
     svm_util = SVMUtil()
     gdb.printing.register_pretty_printer(objfile, SVMPrettyPrinter(svm_util), True)
 
-    # deopt stub points to the wrong address at first -> set dummy value to fill later
-    deopt_stub_available = gdb.lookup_global_symbol('com.oracle.svm.core.deopt.Deoptimizer::eagerDeoptStub',
+    # deopt stub points to the wrong address at first -> fill later when needed
+    deopt_stub_available = gdb.lookup_global_symbol('com.oracle.svm.core.deopt.Deoptimizer',
                                                     gdb.SYMBOL_VAR_DOMAIN) is not None
-
     if deopt_stub_available:
         gdb.unwinder.register_unwinder(objfile, SVMFrameUnwinder(svm_util))
 
-    frame_filter = SVMFrameFilter(svm_util, deopt_stub_available)
+    frame_filter = SVMFrameFilter(svm_util)
     objfile.frame_filters[frame_filter.name] = frame_filter
 
 
diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py
index 031ff0e296b0..d00a6546a6a9 100644
--- a/substratevm/mx.substratevm/mx_substratevm.py
+++ b/substratevm/mx.substratevm/mx_substratevm.py
@@ -1188,6 +1188,7 @@ def _runtimedebuginfotest(native_image, output_path, args=None):
     test_python_source_dir = join(test_source_path, 'com', 'oracle', 'svm', 'test', 'debug', 'helper')
     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')
 
     gdb_args = [
         os.environ.get('GDB_BIN', 'gdb'),
@@ -1233,27 +1234,31 @@ def _runtimedebuginfotest(native_image, output_path, args=None):
     # unittest may result in different exit code, nonZeroIsFatal ensures that we can go on with other test
     status = mx.run(gdb_command, cwd=output_path, nonZeroIsFatal=False)
 
-    jslib = mx.add_lib_suffix(native_image(
-        args +
-        svm_experimental_options([
-            '-H:+SourceLevelDebug',
-            '-H:+RuntimeDebugInfo',
-        ]) +
-        ['-g', '-O0', '--macro:jsvm-library']
-    ))
-    js_launcher = get_js_launcher(jslib)
-    testdeopt_js = join(suite.dir, 'mx.substratevm', 'testdeopt.js')
-    logfile = join(output_path, 'test_runtime_deopt.log')
-    os.environ.update({'gdb_logfile': logfile})
-    gdb_command = gdb_args + [
-        '-iex', f"set logging file {logfile}",
-        '-iex', "set logging enabled on",
-        '-iex', f"set auto-load safe-path {join(output_path, 'gdb-debughelpers.py')}",
-        '-x', test_runtime_deopt_py, '--args', js_launcher, testdeopt_js
-    ]
-    log_gdb_command(gdb_command)
-    # unittest may result in different exit code, nonZeroIsFatal ensures that we can go on with other test
-    status |= mx.run(gdb_command, cwd=output_path, nonZeroIsFatal=False)
+    def run_js_test(eager: bool = False):
+        jslib = mx.add_lib_suffix(native_image(
+            args +
+            svm_experimental_options([
+                '-H:+SourceLevelDebug',
+                '-H:+RuntimeDebugInfo',
+                '-H:+LazyDeoptimization' if eager else '-H:-LazyDeoptimization',
+            ]) +
+            ['-g', '-O0', '--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_args + [
+            '-iex', f"set logging file {logfile}",
+            '-iex', "set logging enabled on",
+            '-iex', f"set auto-load safe-path {join(output_path, 'gdb-debughelpers.py')}",
+            '-x', test_runtime_deopt_py, '--args', js_launcher, testdeopt_js
+        ]
+        log_gdb_command(gdb_command)
+        # unittest may result in different exit code, nonZeroIsFatal ensures that we can go on with other test
+        return mx.run(gdb_command, cwd=output_path, nonZeroIsFatal=False)
+
+    status |= run_js_test()
+    status |= run_js_test(True)
 
     if status != 0:
         mx.abort(status)
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/src/com/oracle/svm/test/debug/helper/test_runtime_deopt.py
index e894cc0c5b3c..5a856eb15186 100644
--- 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/src/com/oracle/svm/test/debug/helper/test_runtime_deopt.py
@@ -83,10 +83,17 @@ def test_backtrace_with_deopt(self):
 
         # check backtrace
         backtrace = gdb_execute('backtrace 5')
-        self.assertIn('[DEOPT FRAME] com.oracle.truffle.runtime.OptimizedCallTarget::profiledPERoot', backtrace)
-        self.assertIn('(deoptFrameValues=2, __0=com.oracle.svm.truffle.api.SubstrateOptimizedCallTarget = {...}, __1=java.lang.Object[5] = {...}) at OptimizedCallTarget.java', backtrace)
-        self.assertIn('com.oracle.svm.truffle.api.SubstrateOptimizedCallTargetInstalledCode::doInvoke', backtrace)
-        self.assertNotIn('??', backtrace)
+        # check if eager deopt frame
+        if 'EAGER DEOPT FRAME' in backtrace:
+            self.assertIn('[EAGER DEOPT FRAME] com.oracle.truffle.runtime.OptimizedCallTarget::profiledPERoot', backtrace)
+            self.assertIn('(deoptFrameValues=2, __0=com.oracle.svm.truffle.api.SubstrateOptimizedCallTarget = {...}, __1=java.lang.Object[5] = {...}) at OptimizedCallTarget.java', backtrace)
+            self.assertIn('com.oracle.svm.truffle.api.SubstrateOptimizedCallTargetInstalledCode::doInvoke', backtrace)
+            self.assertNotIn('??', backtrace)
+            self.assertNotIn('Unknown Frame at', backtrace)
+        else:
+            # must be lazy deopt frame
+            # we can't be sure it is handled properly, but at least it should show up as lazy deopt frame in the backtrace
+            self.assertIn('[LAZY DEOPT FRAME] at', backtrace)
 
     # the js deopt test uses the jsvm-library
     # so the debugging symbols do not originate from the main objfile, but from the shared library

From c206478d55dab7d086eb9dfc6b0ce339412ba6bd Mon Sep 17 00:00:00 2001
From: "dominik.mascherbauer@oracle.com" <dominik.mascherbauer@oracle.com>
Date: Mon, 7 Apr 2025 16:41:25 +0200
Subject: [PATCH 34/34] Initial implementation for opaque type resolution for
 runtime debug information in GDB

---
 .../debugentry/PrimitiveTypeEntry.java        |  29 ++--
 .../elf/dwarf/DwarfAbbrevSectionImpl.java     |  38 ++++-
 .../objectfile/elf/dwarf/DwarfDebugInfo.java  |   1 +
 .../elf/dwarf/DwarfInfoSectionImpl.java       | 123 +++++++++++++--
 .../core/debug/SharedDebugInfoProvider.java   |  18 ++-
 .../debug/SubstrateDebugInfoProvider.java     |   2 +-
 .../image/NativeImageDebugInfoProvider.java   | 141 ++++++++++++------
 7 files changed, 262 insertions(+), 90 deletions(-)

diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PrimitiveTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PrimitiveTypeEntry.java
index 814971edf53e..56c236e69865 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PrimitiveTypeEntry.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PrimitiveTypeEntry.java
@@ -30,12 +30,21 @@
 
 public class PrimitiveTypeEntry extends TypeEntry {
 
-    private final JavaKind kind;
+    private final int bitCount;
+    private final boolean isNumericInteger;
+    private final boolean isNumericFloat;
+    private final boolean isUnsigned;
 
-    public PrimitiveTypeEntry(String typeName, int size, long classOffset, long typeSignature,
-                    JavaKind kind) {
+    public PrimitiveTypeEntry(String typeName, int size, long classOffset, long typeSignature, JavaKind kind) {
+        this(typeName, size, classOffset, typeSignature, kind == JavaKind.Void ? 0 : kind.getBitCount(), kind.isNumericInteger(), kind.isNumericFloat(), kind.isUnsigned());
+    }
+
+    public PrimitiveTypeEntry(String typeName, int size, long classOffset, long typeSignature, int bitCount, boolean isNumericInteger, boolean isNumericFloat, boolean isUnsigned) {
         super(typeName, size, classOffset, typeSignature, typeSignature);
-        this.kind = kind;
+        this.bitCount = bitCount;
+        this.isNumericInteger = isNumericInteger;
+        this.isNumericFloat = isNumericFloat;
+        this.isUnsigned = isUnsigned;
     }
 
     @Override
@@ -43,23 +52,19 @@ public boolean isPrimitive() {
         return true;
     }
 
-    public char getTypeChar() {
-        return kind.getTypeChar();
-    }
-
     public int getBitCount() {
-        return (kind == JavaKind.Void ? 0 : kind.getBitCount());
+        return bitCount;
     }
 
     public boolean isNumericInteger() {
-        return kind.isNumericInteger();
+        return isNumericInteger;
     }
 
     public boolean isNumericFloat() {
-        return kind.isNumericFloat();
+        return isNumericFloat;
     }
 
     public boolean isUnsigned() {
-        return kind.isUnsigned();
+        return isUnsigned;
     }
 }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java
index f4474bd5599a..9a5a477b9169 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java
@@ -916,8 +916,11 @@ public int writeAbbrevs(DebugContext context, byte[] buffer, int p) {
         int pos = p;
         // Write Abbrevs that are shared for AOT and Runtime compilation
         pos = writeCompileUnitAbbrevs(context, buffer, pos);
+        pos = writeTypeUnitAbbrev(context, buffer, pos);
         pos = writeNamespaceAbbrev(context, buffer, pos);
         pos = writeClassLayoutAbbrevs(context, buffer, pos);
+        pos = writeDummyClassLayoutAbbrev(context, buffer, pos);
+        pos = writeClassReferenceAbbrevs(context, buffer, pos);
         pos = writeMethodDeclarationAbbrevs(context, buffer, pos);
         pos = writeMethodLocationAbbrev(context, buffer, pos);
         pos = writeAbstractInlineMethodAbbrev(context, buffer, pos);
@@ -929,14 +932,12 @@ public int writeAbbrevs(DebugContext context, byte[] buffer, int p) {
         pos = writeLocalLocationAbbrevs(context, buffer, pos);
 
         // Write Abbrevs that are only used for AOT debuginfo generation
-        if (!dwarfSections.isRuntimeCompilation()) {
-            pos = writeTypeUnitAbbrev(context, buffer, pos);
+        if (!dwarfSections.isRuntimeCompilation() || true) {
 
             pos = writePrimitiveTypeAbbrev(context, buffer, pos);
             pos = writeVoidTypeAbbrev(context, buffer, pos);
             pos = writeObjectHeaderAbbrev(context, buffer, pos);
 
-            pos = writeClassReferenceAbbrevs(context, buffer, pos);
             pos = writeFieldDeclarationAbbrevs(context, buffer, pos);
             pos = writeClassConstantAbbrev(context, buffer, pos);
 
@@ -988,7 +989,7 @@ private int writeHasChildren(DwarfHasChildren hasChildren, byte[] buffer, int po
 
     private int writeCompileUnitAbbrevs(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
         int pos = p;
-        if (!dwarfSections.isRuntimeCompilation()) {
+        if (!dwarfSections.isRuntimeCompilation() || true) {
             pos = writeCompileUnitAbbrev(context, AbbrevCode.CLASS_CONSTANT_UNIT, buffer, pos);
             pos = writeCompileUnitAbbrev(context, AbbrevCode.CLASS_UNIT_1, buffer, pos);
             pos = writeCompileUnitAbbrev(context, AbbrevCode.CLASS_UNIT_2, buffer, pos);
@@ -1117,7 +1118,7 @@ private int writeNamespaceAbbrev(@SuppressWarnings("unused") DebugContext contex
 
     private int writeClassLayoutAbbrevs(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
         int pos = p;
-        if (!dwarfSections.isRuntimeCompilation()) {
+        if (!dwarfSections.isRuntimeCompilation() || true) {
             pos = writeClassLayoutAbbrev(context, AbbrevCode.CLASS_LAYOUT_1, buffer, pos);
             if (!dwarfSections.useHeapBase()) {
                 pos = writeClassLayoutAbbrev(context, AbbrevCode.CLASS_LAYOUT_2, buffer, pos);
@@ -1167,6 +1168,27 @@ private int writeClassLayoutAbbrev(@SuppressWarnings("unused") DebugContext cont
         return pos;
     }
 
+    private int writeDummyClassLayoutAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
+        int pos = p;
+
+        pos = writeAbbrevCode(AbbrevCode.CLASS_LAYOUT_DUMMY, buffer, pos);
+        pos = writeTag(DwarfTag.DW_TAG_structure_type, buffer, pos);
+        pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_no, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_declaration, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos);
+        pos = writeAttrType(DwarfAttribute.DW_AT_byte_size, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos);
+        /*
+         * Now terminate.
+         */
+        pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos);
+        pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos);
+
+        return pos;
+    }
+
     private int writeClassReferenceAbbrevs(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) {
         int pos = p;
         pos = writeClassReferenceAbbrev(context, AbbrevCode.TYPE_POINTER_SIG, buffer, pos);
@@ -1200,7 +1222,7 @@ private int writeMethodDeclarationAbbrevs(@SuppressWarnings("unused") DebugConte
         pos = writeMethodDeclarationAbbrev(context, AbbrevCode.METHOD_DECLARATION_INLINE, buffer, pos);
         pos = writeMethodDeclarationAbbrev(context, AbbrevCode.METHOD_DECLARATION_STATIC, buffer, pos);
         pos = writeMethodDeclarationAbbrev(context, AbbrevCode.METHOD_DECLARATION_INLINE_STATIC, buffer, pos);
-        if (!dwarfSections.isRuntimeCompilation()) {
+        if (!dwarfSections.isRuntimeCompilation() || true) {
             pos = writeMethodDeclarationAbbrev(context, AbbrevCode.METHOD_DECLARATION_SKELETON, buffer, pos);
         }
         return pos;
@@ -1264,6 +1286,8 @@ private int writeFieldDeclarationAbbrevs(DebugContext context, byte[] buffer, in
         pos = writeFieldDeclarationAbbrev(context, AbbrevCode.FIELD_DECLARATION_3, buffer, pos);
         /* A static field with line and file. */
         pos = writeFieldDeclarationAbbrev(context, AbbrevCode.FIELD_DECLARATION_4, buffer, pos);
+
+        pos = writeFieldDeclarationRelTypeAbbrev(context, buffer, pos);
         return pos;
     }
 
@@ -1629,7 +1653,7 @@ private int writeParameterDeclarationAbbrevs(DebugContext context, byte[] buffer
         pos = writeParameterDeclarationAbbrev(context, AbbrevCode.METHOD_PARAMETER_DECLARATION_1, buffer, pos);
         pos = writeParameterDeclarationAbbrev(context, AbbrevCode.METHOD_PARAMETER_DECLARATION_2, buffer, pos);
         pos = writeParameterDeclarationAbbrev(context, AbbrevCode.METHOD_PARAMETER_DECLARATION_3, buffer, pos);
-        if (!dwarfSections.isRuntimeCompilation()) {
+        if (!dwarfSections.isRuntimeCompilation() || true) {
             pos = writeSkeletonParameterDeclarationAbbrev(context, AbbrevCode.METHOD_PARAMETER_DECLARATION_4, buffer, pos);
             pos = writeSkeletonParameterDeclarationAbbrev(context, AbbrevCode.METHOD_PARAMETER_DECLARATION_5, buffer, pos);
         }
diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java
index 4b984629f9b3..a360b678e2b2 100644
--- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java
+++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java
@@ -70,6 +70,7 @@ public enum AbbrevCode {
         CLASS_LAYOUT_2,
         CLASS_LAYOUT_3,
         CLASS_LAYOUT_4,
+        CLASS_LAYOUT_DUMMY,
         TYPE_POINTER_SIG,
         TYPE_POINTER,
         FOREIGN_TYPEDEF,
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 8e9ce6e10f37..aef975d21098 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
@@ -114,8 +114,10 @@ DwarfEncoding computeEncoding(PrimitiveTypeEntry type) {
             if (type.isUnsigned()) {
                 if (bitCount == 1) {
                     return DwarfEncoding.DW_ATE_boolean;
+                } else if (bitCount == 8) {
+                    return DwarfEncoding.DW_ATE_unsigned_char;
                 } else {
-                    assert bitCount == 16;
+                    assert bitCount == 16 || bitCount == 32 || bitCount == 64;
                     return DwarfEncoding.DW_ATE_unsigned;
                 }
             } else if (bitCount == 8) {
@@ -139,6 +141,25 @@ public int generateContent(DebugContext context, byte[] buffer) {
                 setCUIndex(classEntry, cursor.get());
                 cursor.set(writeInstanceClassInfo(context, classEntry, buffer, cursor.get()));
             });
+            typeStream().forEach(typeEntry -> {
+                switch (typeEntry) {
+                    case PrimitiveTypeEntry primitiveTypeEntry -> {
+                        if (primitiveTypeEntry.getBitCount() > 0) {
+                            cursor.set(writePrimitiveType(context, primitiveTypeEntry, buffer, cursor.get()));
+                        } else {
+                            cursor.set(writeVoidType(context, primitiveTypeEntry, buffer, cursor.get()));
+                        }
+                    }
+                    case HeaderTypeEntry headerTypeEntry -> {
+                        cursor.set(writeHeaderType(context, headerTypeEntry, buffer, cursor.get()));
+                    }
+                    case StructureTypeEntry structureTypeEntry -> {
+                        cursor.set(writeDummyTypeUnit(context, structureTypeEntry, buffer, cursor.get()));
+                    }
+                    default -> {
+                    }
+                }
+            });
             pos = cursor.get();
         } else {
             /* Write TUs for primitive types and header struct. */
@@ -710,28 +731,46 @@ private int writeForeignTypeUnit(DebugContext context, ForeignTypeEntry foreignT
 
         /* Define a pointer type referring to the base type */
         int refTypeIdx = pos;
-        log(context, "  [0x%08x] foreign pointer type", pos);
-        abbrevCode = AbbrevCode.TYPE_POINTER_SIG;
+        log(context, "  [0x%08x] foreign type wrapper", pos);
+        abbrevCode = AbbrevCode.FOREIGN_STRUCT;
         log(context, "  [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal());
         pos = writeAbbrevCode(abbrevCode, buffer, pos);
-        int pointerSize = dwarfSections.pointerSize();
-        log(context, "  [0x%08x]     byte_size 0x%x", pos, pointerSize);
-        pos = writeAttrData1((byte) pointerSize, buffer, pos);
-        long layoutTypeSignature = foreignTypeEntry.getLayoutTypeSignature();
-        log(context, "  [0x%08x]     type 0x%x", pos, layoutTypeSignature);
-        pos = writeTypeSignature(layoutTypeSignature, buffer, pos);
+        String name = uniqueDebugString(foreignTypeEntry.getTypeName());
+        log(context, "  [0x%08x]     name %s", pos, name);
+        pos = writeStrSectionOffset(name, buffer, pos);
+        int size = foreignTypeEntry.getSize();
+        log(context, "  [0x%08x]     byte_size 0x%x", pos, size);
+        pos = writeAttrData1((byte) size, buffer, pos);
+
+        log(context, "  [0x%08x] field definition", pos);
+        abbrevCode = AbbrevCode.FIELD_DECLARATION_1;
+        log(context, "  [0x%08x] <2> Abbrev Number %d", pos, abbrevCode.ordinal());
+        pos = writeAbbrevCode(abbrevCode, buffer, pos);
+        name = uniqueDebugString("rawValue");
+        log(context, "  [0x%08x]     name  0x%x (%s)", pos, debugStringIndex(name), name);
+        pos = writeStrSectionOffset(name, buffer, pos);
+        typeSignature = foreignTypeEntry.getLayoutTypeSignature();
+        log(context, "  [0x%08x]     type  0x%x (%s)", pos, typeSignature, foreignTypeEntry.getTypeName());
+        pos = writeTypeSignature(typeSignature, buffer, pos);
+        int memberOffset = 0;
+        log(context, "  [0x%08x]     member offset 0x%x", pos, memberOffset);
+        pos = writeAttrData2((short) memberOffset, buffer, pos);
+        log(context, "  [0x%08x]     accessibility public", pos);
+        pos = writeAttrAccessibility(Modifier.PUBLIC, buffer, pos);
+
+        /* Write a terminating null attribute for the wrapper type. */
+        pos = writeAttrNull(buffer, pos);
 
         /* Fix up the type offset. */
         writeInt(pos - lengthPos, buffer, typeOffsetPos);
 
-        /* Define a typedef for the layout type using the Java name. */
-        log(context, "  [0x%08x] foreign typedef", pos);
-        abbrevCode = AbbrevCode.FOREIGN_TYPEDEF;
+        log(context, "  [0x%08x] foreign pointer type", pos);
+        abbrevCode = AbbrevCode.TYPE_POINTER;
         log(context, "  [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal());
         pos = writeAbbrevCode(abbrevCode, buffer, pos);
-        String name = uniqueDebugString(foreignTypeEntry.getTypeName());
-        log(context, "  [0x%08x]     name %s", pos, name);
-        pos = writeStrSectionOffset(name, buffer, pos);
+        int pointerSize = dwarfSections.pointerSize();
+        log(context, "  [0x%08x]     byte_size 0x%x", pos, pointerSize);
+        pos = writeAttrData1((byte) pointerSize, buffer, pos);
         log(context, "  [0x%08x]     type 0x%x", pos, refTypeIdx);
         pos = writeAttrRef4(refTypeIdx, buffer, pos);
 
@@ -748,6 +787,58 @@ private int writeForeignTypeUnit(DebugContext context, ForeignTypeEntry foreignT
         return pos;
     }
 
+    private int writeDummyTypeUnit(DebugContext context, TypeEntry typeEntry, byte[] buffer, int p) {
+        int pos = p;
+        long typeSignature = typeEntry.getTypeSignature();
+
+        // Write a type unit header
+        int lengthPos = pos;
+        pos = writeTUHeader(typeSignature, buffer, pos);
+        int typeOffsetPos = pos - 4;
+        assert pos == lengthPos + TU_DIE_HEADER_SIZE;
+        AbbrevCode abbrevCode = AbbrevCode.TYPE_UNIT;
+        log(context, "  [0x%08x] <0> Abbrev Number %d", pos, abbrevCode.ordinal());
+        pos = writeAbbrevCode(abbrevCode, buffer, pos);
+        log(context, "  [0x%08x]     language  %s", pos, "DW_LANG_Java");
+        pos = writeAttrLanguage(DwarfDebugInfo.LANG_ENCODING, buffer, pos);
+        log(context, "  [0x%08x]     use_UTF8", pos);
+        pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos);
+
+        int refTypeIdx = pos;
+        log(context, "  [0x%08x] class layout", pos);
+        abbrevCode = AbbrevCode.CLASS_LAYOUT_DUMMY;
+        log(context, "  [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal());
+        pos = writeAbbrevCode(abbrevCode, buffer, pos);
+        String name = uniqueDebugString(typeEntry.getTypeName());
+        log(context, "  [0x%08x]     name  0x%x (%s)", pos, debugStringIndex(name), name);
+        pos = writeStrSectionOffset(name, buffer, pos);
+        log(context, "  [0x%08x]     declaration true", pos);
+        pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos);
+        log(context, "  [0x%08x]     byte_size  0x%x", pos, 0);
+        pos = writeAttrData2((short) 0, buffer, pos);
+
+        /* Fix up the type offset. */
+        writeInt(pos - lengthPos, buffer, typeOffsetPos);
+
+        /* Define a pointer type referring to the underlying layout. */
+        log(context, "  [0x%08x] %s dummy pointer type", pos, typeEntry.isInterface() ? "interface" : "class");
+        abbrevCode = AbbrevCode.TYPE_POINTER;
+        log(context, "  [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal());
+        pos = writeAbbrevCode(abbrevCode, buffer, pos);
+        int pointerSize = dwarfSections.referenceSize();
+        log(context, "  [0x%08x]     byte_size 0x%x", pos, pointerSize);
+        pos = writeAttrData1((byte) pointerSize, buffer, pos);
+        log(context, "  [0x%08x]     type 0x%x", pos, refTypeIdx);
+        pos = writeAttrRef4(refTypeIdx, buffer, pos);
+
+        /* Write a terminating null attribute for the top level TU DIE. */
+        pos = writeAttrNull(buffer, pos);
+
+        /* Fix up the TU length. */
+        patchLength(lengthPos, buffer, pos);
+        return pos;
+    }
+
     private int writeForeignLayoutTypeUnit(DebugContext context, ForeignTypeEntry foreignTypeEntry, byte[] buffer, int p) {
         int pos = p;
 
@@ -1369,6 +1460,7 @@ private int writeForeignWordLayout(DebugContext context, ForeignWordTypeEntry fo
     }
 
     private int writeForeignIntegerLayout(DebugContext context, ForeignIntegerTypeEntry foreignIntegerTypeEntry, int size, byte[] buffer, int p) {
+        assert false;
         int pos = p;
         log(context, "  [0x%08x] foreign primitive integral type for %s", pos, foreignIntegerTypeEntry.getTypeName());
         /* Record the location of this type entry. */
@@ -1392,6 +1484,7 @@ private int writeForeignIntegerLayout(DebugContext context, ForeignIntegerTypeEn
     }
 
     private int writeForeignFloatLayout(DebugContext context, ForeignFloatTypeEntry foreignFloatTypeEntry, int size, byte[] buffer, int p) {
+        assert false;
         int pos = p;
         log(context, "  [0x%08x] foreign primitive float type for %s", pos, foreignFloatTypeEntry.getTypeName());
         /* Record the location of this type entry. */
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 1767a8a316cb..7c71a121e8b0 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
@@ -195,7 +195,7 @@ public abstract class SharedDebugInfoProvider implements DebugInfoProvider {
     protected final SharedType wordBaseType;
 
     /**
-     * The {@code SharedType} for {@link Void}. This is used as fallback for foreign pinter types,
+     * The {@code SharedType} for {@code void}. This is used as fallback for foreign pointer types,
      * if there is no type it points to.
      */
     protected final SharedType voidType;
@@ -252,6 +252,12 @@ public abstract class SharedDebugInfoProvider implements DebugInfoProvider {
      */
     public static final String LAYOUT_PREFIX = "_layout_.";
 
+    /**
+     * A prefix used for type signature generation with {@link #getTypeSignature} to generate unique
+     * type signatures for foreign primitive type units.
+     */
+    public static final String FOREIGN_PREFIX = "_foreign_.";
+
     static final Path EMPTY_PATH = Paths.get("");
 
     public SharedDebugInfoProvider(DebugContext debug, RuntimeConfiguration runtimeConfiguration, MetaAccessProvider metaAccess) {
@@ -262,7 +268,7 @@ public SharedDebugInfoProvider(DebugContext debug, RuntimeConfiguration runtimeC
          * Use a disabled DebugContext if log is disabled here. We need to make sure the log stays
          * disabled, as we use parallel streams if it is disabled
          */
-        this.debug = debug.isLogEnabled() ? debug : DebugContext.disabled(null);
+        this.debug = debug.isLogEnabledForMethod() ? debug : DebugContext.disabled(null);
 
         // Fetch special types that have special use cases.
         // hubType: type of the 'hub' field in the object header.
@@ -270,7 +276,7 @@ public SharedDebugInfoProvider(DebugContext debug, RuntimeConfiguration runtimeC
         // voidType: fallback type to point to for foreign pointer types
         this.hubType = (SharedType) metaAccess.lookupJavaType(Class.class);
         this.wordBaseType = (SharedType) metaAccess.lookupJavaType(WordBase.class);
-        this.voidType = (SharedType) metaAccess.lookupJavaType(Void.class);
+        this.voidType = (SharedType) metaAccess.lookupJavaType(void.class);
 
         // Get some information on heap layout and object/object header layout
         this.useHeapBase = ReferenceAccess.singleton().haveCompressedReferences() && ReferenceAccess.singleton().getCompressEncoding().hasBase();
@@ -398,9 +404,9 @@ public SortedSet<CompiledMethodEntry> compiledMethodEntries() {
     @SuppressWarnings("try")
     public void installDebugInfo() {
         // we can only meaningfully provide logging if debug info is produced sequentially
-        Stream<SharedType> typeStream = debug.isLogEnabled() ? typeInfo() : typeInfo().parallel();
-        Stream<Pair<SharedMethod, CompilationResult>> codeStream = debug.isLogEnabled() ? codeInfo() : codeInfo().parallel();
-        Stream<Object> dataStream = debug.isLogEnabled() ? dataInfo() : dataInfo().parallel();
+        Stream<SharedType> typeStream = debug.isLogEnabledForMethod() ? typeInfo() : typeInfo().parallel();
+        Stream<Pair<SharedMethod, CompilationResult>> codeStream = debug.isLogEnabledForMethod() ? codeInfo() : codeInfo().parallel();
+        Stream<Object> dataStream = debug.isLogEnabledForMethod() ? dataInfo() : dataInfo().parallel();
 
         try (DebugContext.Scope s = debug.scope("DebugInfoProvider")) {
             // Create and index an empty dir with index 0 for null paths.
diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
index 5ff16536e98e..e3ed93831bfa 100644
--- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
+++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java
@@ -227,7 +227,7 @@ protected TypeEntry createTypeEntry(SharedType type) {
 
         if (type.isPrimitive()) {
             JavaKind kind = type.getStorageKind();
-            return new PrimitiveTypeEntry(typeName, kind == JavaKind.Void ? 0 : kind.getByteCount(), classOffset, typeSignature, kind);
+            return new PrimitiveTypeEntry(typeName, size, classOffset, typeSignature, kind);
         } else {
             // otherwise we have a structured type
             long layoutTypeSignature = getTypeSignature(LAYOUT_PREFIX + typeName + loaderName);
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 943094ea4bab..d7b9c4525927 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
@@ -35,6 +35,8 @@
 import java.util.Comparator;
 import java.util.List;
 import java.util.Set;
+import java.util.SortedSet;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
@@ -52,8 +54,6 @@
 import com.oracle.objectfile.debugentry.EnumClassEntry;
 import com.oracle.objectfile.debugentry.FieldEntry;
 import com.oracle.objectfile.debugentry.FileEntry;
-import com.oracle.objectfile.debugentry.ForeignFloatTypeEntry;
-import com.oracle.objectfile.debugentry.ForeignIntegerTypeEntry;
 import com.oracle.objectfile.debugentry.ForeignPointerTypeEntry;
 import com.oracle.objectfile.debugentry.ForeignStructTypeEntry;
 import com.oracle.objectfile.debugentry.ForeignTypeEntry;
@@ -119,6 +119,11 @@ class NativeImageDebugInfoProvider extends SharedDebugInfoProvider {
     protected final int referenceStartOffset;
     private final Set<HostedMethod> allOverrides;
 
+    /**
+     * An index map that holds all unique native primitive type entries.
+     */
+    private final ConcurrentHashMap<PointerToInfo, TypeEntry> nativePrimitiveTypeIndex = new ConcurrentHashMap<>();
+
     NativeImageDebugInfoProvider(DebugContext debug, NativeImageCodeCache codeCache, NativeImageHeap heap, NativeLibraries nativeLibs, HostedMetaAccess metaAccess,
                     RuntimeConfiguration runtimeConfiguration) {
         super(debug, runtimeConfiguration, metaAccess);
@@ -140,6 +145,13 @@ class NativeImageDebugInfoProvider extends SharedDebugInfoProvider {
                         .collect(Collectors.toSet());
     }
 
+    @Override
+    public SortedSet<TypeEntry> typeEntries() {
+        SortedSet<TypeEntry> typeEntries = super.typeEntries();
+        typeEntries.addAll(nativePrimitiveTypeIndex.values());
+        return typeEntries;
+    }
+
     @SuppressWarnings("unused")
     private static ResolvedJavaType getOriginal(ResolvedJavaType type) {
         /*
@@ -684,68 +696,78 @@ protected TypeEntry createTypeEntry(SharedType type) {
                     SizableInfo.ElementKind elementKind = elementInfo instanceof SizableInfo ? ((SizableInfo) elementInfo).getKind() : null;
                     size = elementSize(elementInfo);
 
-                    if (!isForeignPointerType(hostedType)) {
-                        boolean isSigned = nativeLibs.isSigned(hostedType);
-                        return new ForeignWordTypeEntry(typeName, size, classOffset, typeSignature, layoutTypeSignature,
-                                        superClass, fileEntry, loaderEntry, isSigned);
-                    } else if (elementInfo instanceof StructInfo) {
-                        // look for the first interface that also has an associated StructInfo
-                        String typedefName = typedefName(elementInfo);
-                        ForeignStructTypeEntry parentEntry = null;
-                        for (HostedInterface hostedInterface : hostedType.getInterfaces()) {
-                            ElementInfo otherInfo = nativeLibs.findElementInfo(hostedInterface);
-                            if (otherInfo instanceof StructInfo) {
-                                parentEntry = (ForeignStructTypeEntry) lookupTypeEntry(hostedInterface);
-                            }
-                        }
-                        return new ForeignStructTypeEntry(typeName, size, classOffset, typeSignature, layoutTypeSignature, superClass, fileEntry, loaderEntry, typedefName, parentEntry);
-                    } else if (elementKind == SizableInfo.ElementKind.INTEGER) {
-                        boolean isSigned = nativeLibs.isSigned(hostedType) || !((SizableInfo) elementInfo).isUnsigned();
-                        return new ForeignIntegerTypeEntry(typeName, size, classOffset, typeSignature, layoutTypeSignature, superClass, fileEntry, loaderEntry, isSigned);
-                    } else if (elementKind == SizableInfo.ElementKind.FLOAT) {
-                        return new ForeignFloatTypeEntry(typeName, size, classOffset, typeSignature, layoutTypeSignature, superClass, fileEntry, loaderEntry);
-                    } else {
-                        /*
-                         * This must be a pointer. If the target type is known use it to declare the
-                         * pointer type, otherwise default to 'void *'
-                         */
-                        TypeEntry pointerToEntry = null;
-                        if (elementKind == SizableInfo.ElementKind.POINTER) {
+                    switch (elementInfo) {
+                        case PointerToInfo pointerToInfo -> {
                             /*
-                             * any target type for the pointer will be defined by a CPointerTo or
-                             * RawPointerTo annotation
+                             * This must be a pointer. If the target type is known use it to declare
+                             * the pointer type, otherwise default to 'void *'
                              */
-                            CPointerTo cPointerTo = type.getAnnotation(CPointerTo.class);
-                            if (cPointerTo != null) {
-                                HostedType pointerTo = heap.hMetaAccess.lookupJavaType(cPointerTo.value());
-                                pointerToEntry = lookupTypeEntry(pointerTo);
-                            }
-                            RawPointerTo rawPointerTo = type.getAnnotation(RawPointerTo.class);
-                            if (rawPointerTo != null) {
-                                HostedType pointerTo = heap.hMetaAccess.lookupJavaType(rawPointerTo.value());
-                                pointerToEntry = lookupTypeEntry(pointerTo);
-                            }
-
-                            if (pointerToEntry != null) {
-                                debug.log("foreign type %s referent %s ", typeName, pointerToEntry.getTypeName());
-                            } else {
-                                debug.log("foreign type %s", typeName);
+                            TypeEntry pointerToEntry = null;
+                            if (elementKind == SizableInfo.ElementKind.POINTER) {
+                                /*
+                                 * any target type for the pointer will be defined by a CPointerTo
+                                 * or RawPointerTo annotation
+                                 */
+                                CPointerTo cPointerTo = type.getAnnotation(CPointerTo.class);
+                                if (cPointerTo != null) {
+                                    HostedType pointerTo = heap.hMetaAccess.lookupJavaType(cPointerTo.value());
+                                    pointerToEntry = lookupTypeEntry(pointerTo);
+                                }
+                                RawPointerTo rawPointerTo = type.getAnnotation(RawPointerTo.class);
+                                if (rawPointerTo != null) {
+                                    HostedType pointerTo = heap.hMetaAccess.lookupJavaType(rawPointerTo.value());
+                                    pointerToEntry = lookupTypeEntry(pointerTo);
+                                }
+
+                                if (pointerToEntry != null) {
+                                    debug.log("foreign type %s referent %s ", typeName, pointerToEntry.getTypeName());
+                                } else {
+                                    debug.log("foreign type %s", typeName);
+                                }
+                            } else if (elementKind == SizableInfo.ElementKind.INTEGER || elementKind == SizableInfo.ElementKind.FLOAT) {
+                                pointerToEntry = lookupNativePrimitiveType(pointerToInfo);
                             }
-                        }
 
                         if (pointerToEntry == null) {
                             pointerToEntry = lookupTypeEntry(voidType);
                         }
+                            if (pointerToEntry == null) {
+                                pointerToEntry = lookupTypeEntry(voidType);
+                            }
 
-                        if (pointerToEntry != null) {
                             /*
                              * Setting the layout type to the type we point to reuses an available
                              * type unit, so we do not have to write are separate type unit.
                              */
                             layoutTypeSignature = pointerToEntry.getTypeSignature();
-                        }
 
                         return new ForeignPointerTypeEntry(typeName, size, classOffset, typeSignature, layoutTypeSignature, superClass, fileEntry, loaderEntry, pointerToEntry);
+                            return new ForeignPointerTypeEntry(typeName, size, classOffset, typeSignature, layoutTypeSignature, superClass, fileEntry, loaderEntry, pointerToEntry);
+                        }
+                        case StructInfo structInfo -> {
+                            String typedefName = typedefName(structInfo);
+                            ForeignStructTypeEntry parentEntry = null;
+                            for (HostedInterface hostedInterface : hostedType.getInterfaces()) {
+                                ElementInfo otherInfo = nativeLibs.findElementInfo(hostedInterface);
+                                if (otherInfo instanceof StructInfo) {
+                                    parentEntry = (ForeignStructTypeEntry) lookupTypeEntry(hostedInterface);
+                                }
+                            }
+                            return new ForeignStructTypeEntry(typeName, size, classOffset, typeSignature, layoutTypeSignature, superClass, fileEntry, loaderEntry, typedefName, parentEntry);
+                        }
+                        case null, default -> {
+                            if (isForeignPointerType(hostedType)) {
+                                // unknown pointer type maps to void*
+                                TypeEntry pointerToEntry = lookupTypeEntry(voidType);
+                                layoutTypeSignature = pointerToEntry.getTypeSignature();
+                                return new ForeignPointerTypeEntry(typeName, size, classOffset, typeSignature, layoutTypeSignature, superClass, fileEntry, loaderEntry, pointerToEntry);
+                            } else {
+                                // if not a pointer type, this must be a word type
+                                boolean isSigned = nativeLibs.isSigned(hostedType);
+                                return new ForeignWordTypeEntry(typeName, size, classOffset, typeSignature, layoutTypeSignature,
+                                                superClass, fileEntry, loaderEntry, isSigned);
+                            }
+                        }
                     }
                 } else if (hostedType.isEnum()) {
                     return new EnumClassEntry(typeName, size, classOffset, typeSignature, compressedTypeSignature,
@@ -763,6 +785,27 @@ protected TypeEntry createTypeEntry(SharedType type) {
         }
     }
 
+    private TypeEntry lookupNativePrimitiveType(PointerToInfo pointerToInfo) {
+        return nativePrimitiveTypeIndex.computeIfAbsent(pointerToInfo, p -> {
+            try (DebugContext.Scope s = debug.scope("DebugInfoType")) {
+                assert p.getKind() == SizableInfo.ElementKind.INTEGER || p.getKind() == SizableInfo.ElementKind.FLOAT;
+                String typeName = p.getName();
+                int classOffset = -1;
+                long typeSignature = getTypeSignature(FOREIGN_PREFIX + typeName);
+                int size = p.getSizeInBytes();
+                int bitCount = size * 8;
+                boolean isNumericInteger = p.getKind() == SizableInfo.ElementKind.INTEGER;
+                boolean isNumericFloat = p.getKind() == SizableInfo.ElementKind.FLOAT;
+                boolean isUnsigned = p.isUnsigned();
+
+                debug.log("typename %s (%d bits)%n", typeName, bitCount);
+                return new PrimitiveTypeEntry(typeName, size, classOffset, typeSignature, bitCount, isNumericInteger, isNumericFloat, isUnsigned);
+            } catch (Throwable e) {
+                throw debug.handle(e);
+            }
+        });
+    }
+
     /* The following methods provide some logging for foreign type entries. */
     private void logForeignTypeInfo(HostedType hostedType) {
         if (!isForeignPointerType(hostedType)) {