Skip to content

Commit 95dd661

Browse files
[GR-54697] Implement debuginfo generation at image-runtime.
PullRequest: graal/18057
2 parents a3d6104 + 0a847fd commit 95dd661

File tree

100 files changed

+8068
-6982
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

100 files changed

+8068
-6982
lines changed

substratevm/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ This changelog summarizes major changes to GraalVM Native Image.
3131
* (GR-57827) Security providers can now be initialized at run time (instead of build time) when using the option `--future-defaults=all` or `--future-defaults=run-time-initialized-jdk`.
3232
Run-time initialization of security providers helps reduce image heap size by avoiding unnecessary objects inclusion.
3333
* (GR-48191) Enable lambda classes to be registered for reflection and serialization in _reachability-metadata.json_. The format is detailed [here](https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/ReachabilityMetadata.md).
34+
* (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.
3435

3536
## GraalVM for JDK 24 (Internal Version 24.2.0)
3637
* (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.

substratevm/debug/gdbpy/gdb-debughelpers.py

Lines changed: 710 additions & 441 deletions
Large diffs are not rendered by default.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
26+
#ifndef SVM_NATIVE_GDBJITCOMPILATIONINTERFACE_H
27+
#define SVM_NATIVE_GDBJITCOMPILATIONINTERFACE_H
28+
29+
// This header specifies the types used by the GDB JIT compilation interface (see https://sourceware.org/gdb/current/onlinedocs/gdb.html/Declarations.html#Declarations)
30+
// The implementation of the JIT compilation interface is located in com.oracle.svm.core.debug.GdbJitInterface.
31+
32+
#include <stdint.h>
33+
34+
typedef enum
35+
{
36+
JIT_NOACTION = 0,
37+
JIT_REGISTER,
38+
JIT_UNREGISTER
39+
} jit_actions_t;
40+
41+
struct jit_code_entry
42+
{
43+
struct jit_code_entry *next_entry;
44+
struct jit_code_entry *prev_entry;
45+
const char *symfile_addr;
46+
uint64_t symfile_size;
47+
};
48+
49+
struct jit_descriptor
50+
{
51+
uint32_t version;
52+
/* This type should be jit_actions_t, but we use uint32_t
53+
to be explicit about the bitwidth. */
54+
uint32_t action_flag;
55+
struct jit_code_entry *relevant_entry;
56+
struct jit_code_entry *first_entry;
57+
};
58+
59+
#endif

substratevm/mx.substratevm/gdb_utils.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ def check(self, text, skip_fails=True):
110110
for i in range(0, num_rexps):
111111
rexp = rexps[i]
112112
match = None
113+
if skip_fails:
114+
line_idx = 0
113115
while line_idx < num_lines and match is None:
114116
line = lines[line_idx]
115117
match = rexp.match(line)

substratevm/mx.substratevm/mx_substratevm.py

Lines changed: 116 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,6 +1045,20 @@ def build_debug_test(variant_name, image_name, extra_args):
10451045
mx.run([os.environ.get('GDB_BIN', 'gdb'), '--nx', '-q', '-iex', 'set pagination off', '-ex', 'python "ISOLATES=True"', '-x', testhello_py, hello_binary])
10461046

10471047

1048+
def gdb_base_command(logfile, autoload_path):
1049+
return [
1050+
os.environ.get('GDB_BIN', 'gdb'),
1051+
'--nx',
1052+
'-q', # do not print the introductory and copyright messages
1053+
'-iex', 'set pagination off', # messages from enabling logging could already cause pagination, so this must be done first
1054+
'-iex', 'set logging redirect on',
1055+
'-iex', 'set logging overwrite off',
1056+
'-iex', f"set logging file {logfile}",
1057+
'-iex', 'set logging enabled on',
1058+
'-iex', f"set auto-load safe-path {autoload_path}",
1059+
]
1060+
1061+
10481062
def _gdbdebughelperstest(native_image, path, with_isolates_only, args):
10491063

10501064
# ====== check gdb version ======
@@ -1094,15 +1108,6 @@ def _gdbdebughelperstest(native_image, path, with_isolates_only, args):
10941108
'com.oracle.svm.test.debug.helper.ClassLoaderTest'
10951109
]
10961110

1097-
gdb_args = [
1098-
os.environ.get('GDB_BIN', 'gdb'),
1099-
'--nx',
1100-
'-q', # do not print the introductory and copyright messages
1101-
'-iex', 'set pagination off', # messages from enabling logging could already cause pagination, so this must be done first
1102-
'-iex', 'set logging redirect on',
1103-
'-iex', 'set logging overwrite off',
1104-
]
1105-
11061111
def run_debug_test(image_name: str, testfile: str, source_path: str, with_isolates: bool = True,
11071112
build_cinterfacetutorial: bool = False, extra_args: list = None,
11081113
skip_build: bool = False) -> int:
@@ -1138,7 +1143,7 @@ def run_debug_test(image_name: str, testfile: str, source_path: str, with_isolat
11381143
else:
11391144
build_args += ['-o', join(build_dir, image_name)]
11401145

1141-
mx.log(f"native_image {' '.join(build_args)}")
1146+
mx.log(f"native-image {' '.join(build_args)}")
11421147
native_image(build_args)
11431148

11441149
if build_cinterfacetutorial:
@@ -1151,18 +1156,13 @@ def run_debug_test(image_name: str, testfile: str, source_path: str, with_isolat
11511156
else:
11521157
c_command = ['cl', '-MD', join(tutorial_c_source_dir, 'cinterfacetutorial.c'), '-I.',
11531158
'libcinterfacetutorial.lib']
1154-
mx.log(' '.join(c_command))
11551159
mx.run(c_command, cwd=build_dir)
11561160
if mx.get_os() == 'linux':
11571161
logfile = join(path, pathlib.Path(testfile).stem + ('' if with_isolates else '_no_isolates') + '.log')
1158-
os.environ.update({'gdbdebughelperstest_logfile': logfile})
1159-
gdb_command = gdb_args + [
1160-
'-iex', f"set logging file {logfile}",
1161-
'-iex', 'set logging enabled on',
1162-
'-iex', f"set auto-load safe-path {join(build_dir, 'gdb-debughelpers.py')}",
1162+
os.environ.update({'gdb_logfile': logfile})
1163+
gdb_command = gdb_base_command(logfile, join(build_dir, 'gdb-debughelpers.py')) + [
11631164
'-x', testfile, join(build_dir, image_name)
11641165
]
1165-
mx.log(' '.join(gdb_command))
11661166
# unittest may result in different exit code, nonZeroIsFatal ensures that we can go on with other test
11671167
return mx.run(gdb_command, cwd=build_dir, nonZeroIsFatal=False)
11681168
return 0
@@ -1189,6 +1189,82 @@ def run_debug_test(image_name: str, testfile: str, source_path: str, with_isolat
11891189
mx.abort(status)
11901190

11911191

1192+
def _runtimedebuginfotest(native_image, output_path, with_isolates_only, args=None):
1193+
"""Build and run the runtimedebuginfotest"""
1194+
1195+
args = [] if args is None else args
1196+
1197+
test_proj = mx.dependency('com.oracle.svm.test')
1198+
test_source_path = test_proj.source_dirs()[0]
1199+
1200+
test_python_source_dir = join(test_source_path, 'com', 'oracle', 'svm', 'test', 'debug', 'helper')
1201+
test_runtime_compilation_py = join(test_python_source_dir, 'test_runtime_compilation.py')
1202+
test_runtime_deopt_py = join(test_python_source_dir, 'test_runtime_deopt.py')
1203+
testdeopt_js = join(suite.dir, 'mx.substratevm', 'testdeopt.js')
1204+
1205+
# clean / create output directory
1206+
if exists(output_path):
1207+
mx.rmtree(output_path)
1208+
mx_util.ensure_dir_exists(output_path)
1209+
1210+
# Build the native image from Java code
1211+
build_args = [
1212+
'-g', '-O0',
1213+
# set property controlling inclusion of foreign struct header
1214+
'-DbuildDebugInfoTestExample=true',
1215+
'--native-compiler-options=-I' + test_source_path,
1216+
'-o', join(output_path, 'runtimedebuginfotest'),
1217+
'-cp', classpath('com.oracle.svm.test'),
1218+
# We do not want to step into class initializer, so initialize everything at build time.
1219+
'--initialize-at-build-time=com.oracle.svm.test.debug.helper',
1220+
'--features=com.oracle.svm.test.debug.helper.RuntimeCompileDebugInfoTest$RegisterMethodsFeature',
1221+
'com.oracle.svm.test.debug.helper.RuntimeCompileDebugInfoTest',
1222+
] + svm_experimental_options([
1223+
'-H:DebugInfoSourceSearchPath=' + test_source_path,
1224+
'-H:+SourceLevelDebug',
1225+
'-H:+RuntimeDebugInfo',
1226+
]) + args
1227+
1228+
mx.log(f"native-image {' '.join(build_args)}")
1229+
runtime_compile_binary = native_image(build_args)
1230+
1231+
logfile = join(output_path, 'test_runtime_compilation.log')
1232+
os.environ.update({'gdb_logfile': logfile})
1233+
gdb_command = gdb_base_command(logfile, join(output_path, 'gdb-debughelpers.py')) + [
1234+
'-x', test_runtime_compilation_py, runtime_compile_binary
1235+
]
1236+
# unittest may result in different exit code, nonZeroIsFatal ensures that we can go on with other test
1237+
status = mx.run(gdb_command, cwd=output_path, nonZeroIsFatal=False)
1238+
1239+
def run_js_test(eager: bool = False):
1240+
jslib = mx.add_lib_suffix(native_image(
1241+
args +
1242+
svm_experimental_options([
1243+
'-H:+SourceLevelDebug',
1244+
'-H:+RuntimeDebugInfo',
1245+
'-H:+LazyDeoptimization' if eager else '-H:-LazyDeoptimization',
1246+
]) +
1247+
['-g', '-O0', '--macro:jsvm-library']
1248+
))
1249+
js_launcher = get_js_launcher(jslib)
1250+
logfile = join(output_path, 'test_runtime_deopt_' + ('eager' if eager else 'lazy') + '.log')
1251+
os.environ.update({'gdb_logfile': logfile})
1252+
gdb_command = gdb_base_command(logfile, join(output_path, 'gdb-debughelpers.py')) + [
1253+
'-x', test_runtime_deopt_py, '--args', js_launcher, testdeopt_js
1254+
]
1255+
# unittest may result in different exit code, nonZeroIsFatal ensures that we can go on with other test
1256+
return mx.run(gdb_command, cwd=output_path, nonZeroIsFatal=False)
1257+
1258+
# G1 does not work for the jsvm library
1259+
# avoid complications with '-H:+ProtectionKeys' which is not compatible with '-H:-SpawnIsolates' and '-H:-UseCompressedReferences'
1260+
if '--gc=G1' not in args and '-H:-UseCompressedReferences' not in args and '-H:-SpawnIsolates' not in args:
1261+
status |= run_js_test()
1262+
status |= run_js_test(True)
1263+
1264+
if status != 0:
1265+
mx.abort(status)
1266+
1267+
11921268
def _javac_image(native_image, path, args=None):
11931269
args = [] if args is None else args
11941270
mx_util.ensure_dir_exists(path)
@@ -1766,6 +1842,28 @@ def gdbdebughelperstest(args, config=None):
17661842
config=config
17671843
)
17681844

1845+
1846+
@mx.command(suite.name, 'runtimedebuginfotest', 'Runs the runtime debuginfo generation test')
1847+
def runtimedebuginfotest(args, config=None):
1848+
"""
1849+
runs a native image that compiles code and creates debug info at runtime.
1850+
"""
1851+
parser = ArgumentParser(prog='mx runtimedebuginfotest')
1852+
all_args = ['--output-path', '--with-isolates-only']
1853+
masked_args = [_mask(arg, all_args) for arg in args]
1854+
parser.add_argument(all_args[0], metavar='<output-path>', nargs=1, help='Path of the generated image', default=[join(svmbuild_dir(), "runtimedebuginfotest")])
1855+
parser.add_argument(all_args[1], action='store_true', help='Only build and test the native image with isolates')
1856+
parser.add_argument('image_args', nargs='*', default=[])
1857+
parsed = parser.parse_args(masked_args)
1858+
output_path = unmask(parsed.output_path)[0]
1859+
with_isolates_only = parsed.with_isolates_only
1860+
native_image_context_run(
1861+
lambda native_image, a:
1862+
_runtimedebuginfotest(native_image, output_path, with_isolates_only, a), unmask(parsed.image_args),
1863+
config=config
1864+
)
1865+
1866+
17691867
@mx.command(suite_name=suite.name, command_name='helloworld', usage_msg='[options]')
17701868
def helloworld(args, config=None):
17711869
"""
@@ -1858,6 +1956,7 @@ def build_and_test_java_agent_image(native_image, args):
18581956

18591957
native_image_context_run(build_and_test_java_agent_image, args)
18601958

1959+
18611960
@mx.command(suite.name, 'clinittest', 'Runs the ')
18621961
def clinittest(args):
18631962
def build_and_test_clinittest_image(native_image, args):

substratevm/mx.substratevm/suite.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,7 @@
361361
"dependencies": [
362362
"com.oracle.svm.common",
363363
"com.oracle.svm.shaded.org.objectweb.asm",
364+
"com.oracle.objectfile",
364365
"SVM_CONFIGURE",
365366
"espresso-shared:ESPRESSO_SVM",
366367
],
@@ -738,7 +739,6 @@
738739
"subDir": "src",
739740
"sourceDirs": ["src"],
740741
"dependencies": [
741-
"com.oracle.objectfile",
742742
"com.oracle.graal.reachability",
743743
"com.oracle.svm.core.graal.amd64",
744744
"com.oracle.svm.shaded.org.capnproto",
@@ -1152,6 +1152,10 @@
11521152
"jdk.internal.misc",
11531153
"sun.security.jca",
11541154
],
1155+
"jdk.internal.vm.ci" : [
1156+
"jdk.vm.ci.code",
1157+
"jdk.vm.ci.meta",
1158+
],
11551159
},
11561160
"checkstyle": "com.oracle.svm.test",
11571161
"checkstyleVersion" : "10.21.0",
@@ -1971,6 +1975,8 @@
19711975
"com.oracle.objectfile",
19721976
"com.oracle.objectfile.io",
19731977
"com.oracle.objectfile.debuginfo",
1978+
"com.oracle.objectfile.debugentry",
1979+
"com.oracle.objectfile.debugentry.range",
19741980
"com.oracle.objectfile.macho",
19751981
],
19761982

@@ -2101,6 +2107,7 @@
21012107
"dependency:com.oracle.svm.native.libchelper/*",
21022108
"dependency:com.oracle.svm.native.jvm.posix/*",
21032109
"dependency:com.oracle.svm.native.libcontainer/*",
2110+
"file:debug/include",
21042111
],
21052112
},
21062113
},
@@ -2109,6 +2116,7 @@
21092116
# on all other os's we don't want libc specific subdirectories
21102117
"include/": [
21112118
"dependency:com.oracle.svm.native.libchelper/include/*",
2119+
"file:debug/include/*",
21122120
],
21132121
"<os>-<arch>/": [
21142122
"dependency:com.oracle.svm.native.libchelper/<os>-<arch>/default/*",
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is free software; you can redistribute it and/or modify it
6+
* under the terms of the GNU General Public License version 2 only, as
7+
* published by the Free Software Foundation. Oracle designates this
8+
* particular file as subject to the "Classpath" exception as provided
9+
* by Oracle in the LICENSE file that accompanied this code.
10+
*
11+
* This code is distributed in the hope that it will be useful, but WITHOUT
12+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14+
* version 2 for more details (a copy is included in the LICENSE file that
15+
* accompanied this code).
16+
*
17+
* You should have received a copy of the GNU General Public License version
18+
* 2 along with this work; if not, write to the Free Software Foundation,
19+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20+
*
21+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22+
* or visit www.oracle.com if you need additional information or have any
23+
* questions.
24+
*/
25+
26+
function add(a, b, test) {
27+
if (test) {
28+
a += b;
29+
}
30+
return a + b;
31+
}
32+
33+
// trigger compilation add for ints and test = true
34+
for (let i = 0; i < 1000 * 1000; i++) {
35+
add(i, i, true);
36+
}
37+
38+
// deoptimize with failed assumption in compiled method
39+
// then trigger compilation again
40+
console.log("deopt1")
41+
for (let i = 0; i < 1000 * 1000; i++) {
42+
add(i, i, false);
43+
}
44+
45+
// deoptimize with different parameter types
46+
console.log("deopt2");
47+
add({f1: "test1", f2: 2}, {x: "x", y: {test: 42}}, false);

0 commit comments

Comments
 (0)