Skip to content

Commit f9fff9f

Browse files
Add debug info tests for layered images and adapt testing.
Adapt testhello for the layered image test. Add layered debuginfotests to gate. Use default optimization level for all debuginfo tests
1 parent 5b60585 commit f9fff9f

File tree

2 files changed

+162
-74
lines changed

2 files changed

+162
-74
lines changed

substratevm/mx.substratevm/mx_substratevm.py

Lines changed: 145 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,14 @@ def svm_gate_body(args, tasks):
423423
with native_image_context(IMAGE_ASSERTION_FLAGS) as native_image:
424424
debuginfotest(['--output-path', svmbuild_dir()] + args.extra_image_builder_arguments)
425425

426+
with Task('image layereddebuginfotest', tasks, tags=[GraalTags.debuginfotest]) as t:
427+
if t:
428+
if mx.is_windows():
429+
mx.warn('layereddebuginfotest does not work on Windows')
430+
else:
431+
with native_image_context(IMAGE_ASSERTION_FLAGS) as native_image:
432+
layereddebuginfotest(['--output-path', svmbuild_dir()] + args.extra_image_builder_arguments)
433+
426434
with Task('image debughelpertest', tasks, tags=[GraalTags.debuginfotest]) as t:
427435
if t:
428436
if mx.is_windows():
@@ -678,14 +686,7 @@ def batched(iterable, n):
678686

679687
def _native_junit(native_image, unittest_args, build_args=None, run_args=None, blacklist=None, whitelist=None, preserve_image=False, test_classes_per_run=None):
680688
build_args = build_args or []
681-
javaProperties = {}
682-
for dist in suite.dists:
683-
if isinstance(dist, mx.ClasspathDependency):
684-
for cpEntry in mx.classpath_entries(dist):
685-
if hasattr(cpEntry, "getJavaProperties"):
686-
for key, value in cpEntry.getJavaProperties().items():
687-
javaProperties[key] = value
688-
for key, value in javaProperties.items():
689+
for key, value in get_java_properties().items():
689690
build_args.append("-D" + key + "=" + value)
690691

691692
build_args.append('--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED')
@@ -913,6 +914,17 @@ class HelloWorld {
913914
}
914915

915916

917+
def get_java_properties():
918+
javaProperties = {}
919+
for dist in suite.dists:
920+
if isinstance(dist, mx.ClasspathDependency):
921+
for cpEntry in mx.classpath_entries(dist):
922+
if hasattr(cpEntry, "getJavaProperties"):
923+
for key, value in cpEntry.getJavaProperties().items():
924+
javaProperties[key] = value
925+
return javaProperties
926+
927+
916928
def _helloworld(native_image, javac_command, path, build_only, args, variant=list(_helloworld_variants.keys())[0]):
917929
mx_util.ensure_dir_exists(path)
918930
hello_file = os.path.join(path, 'HelloWorld.java')
@@ -923,14 +935,7 @@ def _helloworld(native_image, javac_command, path, build_only, args, variant=lis
923935
fp.flush()
924936
mx.run(javac_command + [hello_file])
925937

926-
javaProperties = {}
927-
for dist in suite.dists:
928-
if isinstance(dist, mx.ClasspathDependency):
929-
for cpEntry in mx.classpath_entries(dist):
930-
if hasattr(cpEntry, "getJavaProperties"):
931-
for key, value in cpEntry.getJavaProperties().items():
932-
javaProperties[key] = value
933-
for key, value in javaProperties.items():
938+
for key, value in get_java_properties().items():
934939
args.append("-D" + key + "=" + value)
935940

936941
binary_path = join(path, "helloworld")
@@ -977,39 +982,18 @@ def _collector(x):
977982
raise Exception('Unexpected output: ' + str(actual_output) + " != " + str(expected_output))
978983

979984
def _debuginfotest(native_image, path, build_only, with_isolates_only, args):
980-
mx.log(f"path={path}")
981985
sourcepath = mx.project('com.oracle.svm.test').source_dirs()[0]
982-
mx.log(f"sourcepath={sourcepath}")
983-
sourcecache = join(path, 'sources')
984-
mx.log(f"sourcecache={sourcecache}")
985986
# the header file for foreign types resides at the root of the
986987
# com.oracle.svm.test source tree
987988
cincludepath = sourcepath
988-
javaProperties = {}
989-
for dist in suite.dists:
990-
if isinstance(dist, mx.ClasspathDependency):
991-
for cpEntry in mx.classpath_entries(dist):
992-
if hasattr(cpEntry, "getJavaProperties"):
993-
for key, value in cpEntry.getJavaProperties().items():
994-
javaProperties[key] = value
995-
for key, value in javaProperties.items():
989+
for key, value in get_java_properties().items():
996990
args.append("-D" + key + "=" + value)
997991

998-
# set property controlling inclusion of foreign struct header
999-
args.append("-DbuildDebugInfoTestExample=true")
1000-
1001-
native_image_args = [
1002-
'--native-compiler-options=-I' + cincludepath,
1003-
'-H:CLibraryPath=' + sourcepath,
1004-
'--native-image-info',
1005-
'-cp', classpath('com.oracle.svm.test'),
1006-
'-Djdk.graal.LogFile=graal.log',
1007-
'-g',
1008-
] + svm_experimental_options([
1009-
'-H:+VerifyNamingConventions',
1010-
'-H:+SourceLevelDebug',
1011-
'-H:DebugInfoSourceSearchPath=' + sourcepath,
1012-
]) + args
992+
native_image_args = (
993+
testhello_ni_args(cincludepath, sourcepath) +
994+
svm_experimental_options(['-H:+VerifyNamingConventions']) +
995+
args
996+
)
1013997

1014998
def build_debug_test(variant_name, image_name, extra_args):
1015999
per_build_path = join(path, variant_name)
@@ -1020,9 +1004,13 @@ def build_debug_test(variant_name, image_name, extra_args):
10201004
mx.log(f'native_image {build_args}')
10211005
return native_image(build_args)
10221006

1007+
env = os.environ.copy()
10231008
# build with and without Isolates and check both work
10241009
if '--libc=musl' in args:
1025-
os.environ.update({'debuginfotest_musl': 'yes'})
1010+
env['debuginfotest_musl'] = 'yes'
1011+
1012+
# this is a non-layered build
1013+
env['debuginfotest_layered'] = 'no'
10261014

10271015
testhello_py = join(suite.dir, 'mx.substratevm', 'testhello.py')
10281016
testhello_args = [
@@ -1031,33 +1019,104 @@ def build_debug_test(variant_name, image_name, extra_args):
10311019
'hello.Hello'
10321020
]
10331021
if mx.get_os() == 'linux' and not build_only:
1034-
os.environ.update({'debuginfotest_arch': mx.get_arch()})
1022+
env['debuginfotest_arch'] = mx.get_arch()
10351023

10361024
if not with_isolates_only:
10371025
hello_binary = build_debug_test('isolates_off', 'hello_image', testhello_args + svm_experimental_options(['-H:-SpawnIsolates']))
10381026
if mx.get_os() == 'linux' and not build_only:
1039-
os.environ.update({'debuginfotest_isolates': 'no'})
1040-
mx.run([os.environ.get('GDB_BIN', 'gdb'), '--nx', '-q', '-iex', 'set pagination off', '-ex', 'python "ISOLATES=False"', '-x', testhello_py, hello_binary])
1027+
env['debuginfotest_isolates'] = 'no'
1028+
mx.run(gdb_base_command() + ['-x', testhello_py, hello_binary], env=env)
10411029

10421030
hello_binary = build_debug_test('isolates_on', 'hello_image', testhello_args + svm_experimental_options(['-H:+SpawnIsolates']))
10431031
if mx.get_os() == 'linux' and not build_only:
1044-
os.environ.update({'debuginfotest_isolates': 'yes'})
1045-
mx.run([os.environ.get('GDB_BIN', 'gdb'), '--nx', '-q', '-iex', 'set pagination off', '-ex', 'python "ISOLATES=True"', '-x', testhello_py, hello_binary])
1032+
env['debuginfotest_isolates'] = 'yes'
1033+
mx.run(gdb_base_command() + ['-x', testhello_py, hello_binary], env=env)
10461034

10471035

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
1036+
def _layereddebuginfotest(native_image, output_path, skip_base_layer, with_isolates_only, args):
1037+
sourcepath = mx.project('com.oracle.svm.test').source_dirs()[0]
1038+
cincludepath = sourcepath
1039+
1040+
for key, value in get_java_properties().items():
1041+
args.append("-D" + key + "=" + value)
1042+
1043+
# fetch arguments used in all layers
1044+
testhello_args = testhello_ni_args(cincludepath, sourcepath) + args
1045+
1046+
def build_layer(layer_path, layer_args):
1047+
# clean / create layer output directory
1048+
if exists(layer_path):
1049+
mx.rmtree(layer_path)
1050+
mx_util.ensure_dir_exists(layer_path)
1051+
# build layer
1052+
return native_image(testhello_args + layer_args)
1053+
1054+
base_layer_path = join(output_path, 'base-layer')
1055+
base_layer_name = 'libbase'
1056+
# Build base layer if missing or not skipped.
1057+
if not (skip_base_layer and exists(join(base_layer_path, base_layer_name + '.nil'))):
1058+
build_layer(base_layer_path, [
1059+
'-o', join(base_layer_path, base_layer_name),
1060+
] + svm_experimental_options([
1061+
f'-H:LayerCreate={base_layer_name}.nil,module=java.base,package=com.oracle.svm.test'
1062+
]))
1063+
1064+
app_layer_path = join(output_path, 'app-layer')
1065+
app_layer_name = 'hello_image'
1066+
# build app layer
1067+
app_layer = build_layer(app_layer_path, [
1068+
'-o', join(app_layer_path, app_layer_name),
1069+
# We do not want to step into class initializer, so initialize everything at build time.
1070+
'--initialize-at-build-time=hello',
1071+
'hello.Hello'
1072+
] + svm_experimental_options([
1073+
f'-H:LayerUse={join(base_layer_path, base_layer_name)}.nil',
1074+
]))
1075+
1076+
# prepare environment
1077+
env = os.environ.copy()
1078+
env['debuginfotest_isolates'] = 'yes'
1079+
env['debuginfotest_layered'] = 'yes'
1080+
env['LD_LIBRARY_PATH'] = base_layer_path
1081+
1082+
# fetch python test file
1083+
testhello_py = join(suite.dir, 'mx.substratevm', 'testhello.py')
1084+
1085+
# run gdb
1086+
mx.run(gdb_base_command() + ['-x', testhello_py, app_layer], cwd=app_layer_path, env=env)
1087+
1088+
1089+
def gdb_logging_command(logfile, autoload_path):
1090+
return gdb_base_command() + [
10541091
'-iex', 'set logging redirect on',
10551092
'-iex', 'set logging overwrite off',
10561093
'-iex', f"set logging file {logfile}",
10571094
'-iex', 'set logging enabled on',
10581095
'-iex', f"set auto-load safe-path {autoload_path}",
10591096
]
10601097

1098+
def gdb_base_command():
1099+
return [
1100+
os.environ.get('GDB_BIN', 'gdb'),
1101+
'--nx',
1102+
'-q', # do not print the introductory and copyright messages
1103+
'-iex', 'set pagination off', # messages from enabling logging could already cause pagination, so this must be done first
1104+
]
1105+
1106+
def testhello_ni_args(cincludepath, sourcepath):
1107+
return [
1108+
'-J-ea', '-J-esa',
1109+
'-g',
1110+
'--native-compiler-options=-I' + cincludepath,
1111+
'-H:CLibraryPath=' + sourcepath,
1112+
'--native-image-info',
1113+
'-cp', classpath('com.oracle.svm.test'),
1114+
'-Djdk.graal.LogFile=graal.log',
1115+
'-DbuildDebugInfoTestExample=true', # set property controlling inclusion of foreign struct header
1116+
] + svm_experimental_options([
1117+
'-H:+SourceLevelDebug',
1118+
'-H:DebugInfoSourceSearchPath=' + sourcepath,
1119+
])
10611120

10621121
def _gdbdebughelperstest(native_image, path, with_isolates_only, args):
10631122

@@ -1124,7 +1183,7 @@ def run_debug_test(image_name: str, testfile: str, source_path: str, with_isolat
11241183
'-H:CLibraryPath=' + source_path,
11251184
'--native-image-info',
11261185
'-Djdk.graal.LogFile=graal.log',
1127-
'-g', '-O0',
1186+
'-g',
11281187
] + svm_experimental_options([
11291188
'-H:+VerifyNamingConventions',
11301189
'-H:+SourceLevelDebug',
@@ -1160,7 +1219,7 @@ def run_debug_test(image_name: str, testfile: str, source_path: str, with_isolat
11601219
if mx.get_os() == 'linux':
11611220
logfile = join(path, pathlib.Path(testfile).stem + ('' if with_isolates else '_no_isolates') + '.log')
11621221
os.environ.update({'gdb_logfile': logfile})
1163-
gdb_command = gdb_base_command(logfile, join(build_dir, 'gdb-debughelpers.py')) + [
1222+
gdb_command = gdb_logging_command(logfile, join(build_dir, 'gdb-debughelpers.py')) + [
11641223
'-x', testfile, join(build_dir, image_name)
11651224
]
11661225
# unittest may result in different exit code, nonZeroIsFatal ensures that we can go on with other test
@@ -1209,7 +1268,7 @@ def _runtimedebuginfotest(native_image, output_path, with_isolates_only, args=No
12091268

12101269
# Build the native image from Java code
12111270
build_args = [
1212-
'-g', '-O0',
1271+
'-g',
12131272
# set property controlling inclusion of foreign struct header
12141273
'-DbuildDebugInfoTestExample=true',
12151274
'--native-compiler-options=-I' + test_source_path,
@@ -1230,7 +1289,7 @@ def _runtimedebuginfotest(native_image, output_path, with_isolates_only, args=No
12301289

12311290
logfile = join(output_path, 'test_runtime_compilation.log')
12321291
os.environ.update({'gdb_logfile': logfile})
1233-
gdb_command = gdb_base_command(logfile, join(output_path, 'gdb-debughelpers.py')) + [
1292+
gdb_command = gdb_logging_command(logfile, join(output_path, 'gdb-debughelpers.py')) + [
12341293
'-x', test_runtime_compilation_py, runtime_compile_binary
12351294
]
12361295
# unittest may result in different exit code, nonZeroIsFatal ensures that we can go on with other test
@@ -1244,12 +1303,12 @@ def run_js_test(eager: bool = False):
12441303
'-H:+RuntimeDebugInfo',
12451304
'-H:-LazyDeoptimization' if eager else '-H:+LazyDeoptimization',
12461305
]) +
1247-
['-g', '-O0', '--macro:jsvm-library']
1306+
['-g', '--macro:jsvm-library']
12481307
))
12491308
js_launcher = get_js_launcher(jslib)
12501309
logfile = join(output_path, 'test_runtime_deopt_' + ('eager' if eager else 'lazy') + '.log')
12511310
os.environ.update({'gdb_logfile': logfile})
1252-
gdb_command = gdb_base_command(logfile, join(output_path, 'gdb-debughelpers.py')) + [
1311+
gdb_command = gdb_logging_command(logfile, join(output_path, 'gdb-debughelpers.py')) + [
12531312
'-x', test_runtime_deopt_py, '--args', js_launcher, testdeopt_js
12541313
]
12551314
# unittest may result in different exit code, nonZeroIsFatal ensures that we can go on with other test
@@ -1813,6 +1872,30 @@ def debuginfotest(args, config=None):
18131872
config=config
18141873
)
18151874

1875+
@mx.command(suite_name=suite.name, command_name='layereddebuginfotest', usage_msg='[options]')
1876+
def layereddebuginfotest(args, config=None):
1877+
"""
1878+
Builds a layered native image and tests it with gdb.
1879+
Base layer: java.base, com.oracle.svm.test
1880+
App Layer: hello.Hello
1881+
"""
1882+
parser = ArgumentParser(prog='mx layereddebuginfotest')
1883+
all_args = ['--output-path', '--skip-base-layer', '--with-isolates-only']
1884+
masked_args = [_mask(arg, all_args) for arg in args]
1885+
parser.add_argument(all_args[0], metavar='<output-path>', nargs=1, help='Path of the generated image', default=[join(svmbuild_dir(), "layereddebuginfotest")])
1886+
parser.add_argument(all_args[1], action='store_true', help='Skip building the base layer if it already exists')
1887+
parser.add_argument(all_args[2], action='store_true', help='Only build and test the native image with isolates')
1888+
parser.add_argument('image_args', nargs='*', default=[])
1889+
parsed = parser.parse_args(masked_args)
1890+
output_path = unmask(parsed.output_path)[0]
1891+
skip_base_layer = parsed.skip_base_layer
1892+
with_isolates_only = parsed.with_isolates_only
1893+
native_image_context_run(
1894+
lambda native_image, a:
1895+
_layereddebuginfotest(native_image, output_path, skip_base_layer, with_isolates_only, a), unmask(parsed.image_args),
1896+
config=config
1897+
)
1898+
18161899
@mx.command(suite_name=suite.name, command_name='debuginfotestshared', usage_msg='[options]')
18171900
def debuginfotestshared(args, config=None):
18181901
"""
@@ -2424,15 +2507,8 @@ def native_image_on_jvm(args, **kwargs):
24242507
if not exists(executable):
24252508
mx.abort("Can not find " + executable + "\nDid you forget to build? Try `mx build`")
24262509

2427-
javaProperties = {}
2428-
for dist in suite.dists:
2429-
if isinstance(dist, mx.ClasspathDependency):
2430-
for cpEntry in mx.classpath_entries(dist):
2431-
if hasattr(cpEntry, "getJavaProperties"):
2432-
for key, value in cpEntry.getJavaProperties().items():
2433-
javaProperties[key] = value
24342510
if not any(arg.startswith('--help') or arg == '--version' for arg in args):
2435-
for key, value in javaProperties.items():
2511+
for key, value in get_java_properties().items():
24362512
args.append("-D" + key + "=" + value)
24372513

24382514
jacoco_args = mx_gate.get_jacoco_agent_args(agent_option_prefix='-J')

substratevm/mx.substratevm/testhello.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ def test():
7474

7575
isolates = os.environ.get('debuginfotest_isolates', 'no') == 'yes'
7676

77+
layered = os.environ.get('debuginfotest_layered', 'no') == 'yes'
78+
7779
arch = os.environ.get('debuginfotest_arch', 'amd64')
7880

7981
print(f"Testing with isolates {'enabled' if isolates else 'disabled'}!")
@@ -595,13 +597,18 @@ def test():
595597
execute("delete breakpoints")
596598
# Set breakpoint at method with inline and not-inlined invocation in same line
597599
exec_string = execute("break hello.Hello::inlineFrom")
598-
rexp = fr"Breakpoint {digits_pattern} at {address_pattern}: file hello/Hello\.java, line {digits_pattern}."
600+
if layered:
601+
rexp = fr"Breakpoint {digits_pattern} at {address_pattern}: hello\.Hello::inlineFrom\. \({digits_pattern} locations\)"
602+
else:
603+
rexp = fr"Breakpoint {digits_pattern} at {address_pattern}: file hello/Hello\.java, line {digits_pattern}."
599604
checker = Checker('break inlineFrom', rexp)
600605
checker.check(exec_string, skip_fails=False)
601606

602607
exec_string = execute("info break")
603-
rexp = [
604-
fr"{digits_pattern}{spaces_pattern}breakpoint{spaces_pattern}keep{spaces_pattern}y{spaces_pattern}{address_pattern} in hello\.Hello::inlineFrom\(\) at hello/Hello\.java:131"]
608+
if layered:
609+
rexp = [fr"{wildcard_pattern}y{spaces_pattern}{address_pattern} in hello\.Hello::inlineFrom\(\) at hello/Hello\.java:131"]
610+
else:
611+
rexp = [fr"{digits_pattern}{spaces_pattern}breakpoint{spaces_pattern}keep{spaces_pattern}y{spaces_pattern}{address_pattern} in hello\.Hello::inlineFrom\(\) at hello/Hello\.java:131"]
605612
checker = Checker('info break inlineFrom', rexp)
606613
checker.check(exec_string)
607614

@@ -661,6 +668,11 @@ def test():
661668
fr"#12{spaces_pattern}hello\.Hello::inlineFrom{no_param_types_pattern} {no_arg_values_pattern} at hello/Hello\.java:133",
662669
fr"#13{spaces_pattern}hello\.Hello::main{param_types_pattern} {arg_values_pattern} at hello/Hello\.java:{main_inlinefrom:d}"]
663670
checker = Checker('backtrace in recursive inlineTo', rexp)
671+
# This test does not work with -O0 in graal-enterprise
672+
# This is because the compilation for inlineTo contains the same source line (160) twice in what GDB figures to be the same lexical context.
673+
# GDB has an "optimization" that if it finds the same line twice in the same lexical context, it just takes the first as breakpoint and drops all other occasions of this line.
674+
# To make this work, we would need to create an artificial lexical context that splits occasions of the same source line into different lexical contexts in GDB while still having the same symbols.
675+
#
664676
checker.check(exec_string, skip_fails=False)
665677

666678
execute("delete breakpoints")
@@ -872,8 +884,8 @@ def test():
872884
exec_string = execute("backtrace 3")
873885
rexp = [
874886
fr"#0{spaces_pattern}hello\.Hello::lambda\$(static\$)?0{no_param_types_pattern} {no_arg_values_pattern} at hello/Hello\.java:222",
875-
fr"#1{spaces_pattern}{address_pattern} in hello\.Hello\$\$Lambda((\${digits_pattern}/0x)|(\$)|(\.0x|/0x))?{hex_digits_pattern}::get{wildcard_pattern} at hello/Hello\.java:259",
876-
fr"#2{spaces_pattern}hello\.Hello::main{param_types_pattern} {arg_values_pattern} at hello/Hello\.java:259"]
887+
fr"#1{spaces_pattern}{address_pattern} in hello\.Hello\$\$Lambda((\${digits_pattern}/0x)|(\$)|(\.0x|/0x))?{hex_digits_pattern}::get{wildcard_pattern}",
888+
fr"#2{spaces_pattern} hello\.Hello::main{param_types_pattern} {arg_values_pattern} at hello/Hello\.java:259"]
877889
checker = Checker('backtrace in lambda', rexp)
878890
checker.check(exec_string, skip_fails=False)
879891

0 commit comments

Comments
 (0)