diff --git a/substratevm/mx.substratevm/gdb_utils.py b/substratevm/mx.substratevm/gdb_utils.py new file mode 100644 index 000000000000..7ac387e8d9a6 --- /dev/null +++ b/substratevm/mx.substratevm/gdb_utils.py @@ -0,0 +1,153 @@ +# 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. +# +# 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. +# +# ---------------------------------------------------------------------------------------------------- +# pylint: skip-file +# +# python utility code for use by the gdb scripts that test native debug info + +import gdb +import re +import sys +import os + +# set various gdb operating modes to desired setting + +def configure_gdb(): + # disable prompting to continue output + execute("set pagination off") + # enable pretty printing of structures + execute("set print pretty on") + # enable demangling of symbols in assembly code listings + execute("set print asm-demangle on") + # disable printing of address symbols + execute("set print symbol off") + # ensure file listings show only the current line + execute("set listsize 1") + + +# execute a gdb command and return the resulting output as a string + +def execute(command): + print('(gdb) %s'%(command)) + try: + return gdb.execute(command, to_string=True) + except gdb.error as e: + print(e) + sys.exit(1) + +# a variety of useful regular expression patterns + +address_pattern = '0x[0-9a-f]+' +hex_digits_pattern = '[0-9a-f]+' +spaces_pattern = '[ \t]+' +maybe_spaces_pattern = '[ \t]*' +digits_pattern = '[0-9]+' +line_number_prefix_pattern = digits_pattern + ':' + spaces_pattern +package_pattern = '[a-z/]+' +package_file_pattern = '[a-zA-Z0-9_/]+\\.java' +varname_pattern = '[a-zA-Z0-9_]+' +wildcard_pattern = '.*' +no_arg_values_pattern = "\(\)" +arg_values_pattern = "\(([a-zA-Z0-9$_]+=[a-zA-Z0-9$_<> ]+)(, [a-zA-Z0-9$_]+=[a-zA-Z0-9$_<> ]+)*\)" +no_param_types_pattern = "\(\)" +param_types_pattern = "\(([a-zA-Z0-9[.*$_\]]+)(, [a-zA-Z0-9[.*$_\]]+)*\)" + +# A helper class which checks that a sequence of lines of output +# from a gdb command matches a sequence of per-line regular +# expressions + +class Checker: + # Create a checker to check gdb command output text. + # name - string to help identify the check if we have a failure. + # regexps - a list of regular expressions which must match. + # successive lines of checked + def __init__(self, name, regexps): + self.name = name + if not isinstance(regexps, list): + regexps = [regexps] + self.rexps = [re.compile(r) for r in regexps if r is not None] + + # Check that successive lines of a gdb command's output text + # match the corresponding regexp patterns provided when this + # Checker was created. + # text - the full output of a gdb comand run by calling + # gdb.execute and passing to_string = True. + # Exits with status 1 if there are less lines in the text + # than regexp patterns or if any line fails to match the + # corresponding pattern otherwise prints the text and returns + # the set of matches. + def check(self, text, skip_fails=True): + lines = text.splitlines() + rexps = self.rexps + num_lines = len(lines) + num_rexps = len(rexps) + line_idx = 0 + matches = [] + for i in range(0, (num_rexps)): + rexp = rexps[i] + match = None + while line_idx < num_lines and match is None: + line = lines[line_idx] + match = rexp.match(line) + if match is None: + if not skip_fails: + print('Checker %s: match %d failed at line %d %s\n'%(self.name, i, line_idx, line)) + print(self) + print(text) + sys.exit(1) + else: + matches.append(match) + line_idx += 1 + if len(matches) < num_rexps: + print('Checker %s: insufficient matching lines %d for regular expressions %d'%(self.name, len(matches), num_rexps)) + print(self) + print(text) + sys.exit(1) + print(text) + return matches + + # Format a Checker as a string + def __str__(self): + rexps = self.rexps + result = 'Checker %s '%(self.name) + result += '{\n' + for rexp in rexps: + result += ' %s\n'%(rexp) + result += '}\n' + return result + +def match_gdb_version(): + # obtain the gdb version + # n.b. we can only test printing in gdb 10.1 upwards + exec_string=execute("show version") + checker = Checker('show version', + r"GNU gdb %s (%s)\.(%s)%s"%(wildcard_pattern, digits_pattern, digits_pattern, wildcard_pattern)) + matches = checker.check(exec_string, skip_fails=False) + return matches[0] + +def check_print_data(major, minor): + # printing does not always work on gdb 10.x or earlier + can_print_data = major > 10 + if os.environ.get('GDB_CAN_PRINT', '') == 'True': + can_print_data = True + return can_print_data diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index 3ae6765827f3..cc4491d9785d 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -796,7 +796,9 @@ def _debuginfotest(native_image, path, build_only, with_isolates_only, args): mx.log("sourcepath=%s"%sourcepath) sourcecache = join(path, 'sources') mx.log("sourcecache=%s"%sourcecache) - + # the header file for foreign types resides at the root of the + # com.oracle.svm.test source tree + cincludepath = sourcepath javaProperties = {} for dist in suite.dists: if isinstance(dist, mx.ClasspathDependency): @@ -807,23 +809,26 @@ def _debuginfotest(native_image, path, build_only, with_isolates_only, args): for key, value in javaProperties.items(): args.append("-D" + key + "=" + value) - - native_image_args = ["--native-image-info", - '-H:+VerifyNamingConventions', - '-cp', classpath('com.oracle.svm.test'), - '-Dgraal.LogFile=graal.log', - '-g', - '-H:+SourceLevelDebug', - '-H:DebugInfoSourceSearchPath=' + sourcepath, - # We do not want to step into class initializer, so initialize everything at build time. - '--initialize-at-build-time=hello', - 'hello.Hello'] + args - - def build_debug_test(variant_name, extra_args): + # set property controlling inclusion of foreign struct header + args.append("-DbuildDebugInfoTestExample=true") + + native_image_args = [ + '--native-compiler-options=-I' + cincludepath, + '-H:CLibraryPath=' + sourcepath, + '--native-image-info', + '-H:+VerifyNamingConventions', + '-cp', classpath('com.oracle.svm.test'), + '-Dgraal.LogFile=graal.log', + '-g', + '-H:+SourceLevelDebug', + '-H:DebugInfoSourceSearchPath=' + sourcepath, + ] + args + + def build_debug_test(variant_name, image_name, extra_args): per_build_path = join(path, variant_name) mkpath(per_build_path) build_args = native_image_args + extra_args + [ - '-o', join(per_build_path, 'hello_image') + '-o', join(per_build_path, image_name) ] mx.log('native_image {}'.format(build_args)) return native_image(build_args) @@ -832,20 +837,25 @@ def build_debug_test(variant_name, extra_args): if '--libc=musl' in args: os.environ.update({'debuginfotest_musl' : 'yes'}) + gdb_utils_py = join(suite.dir, 'mx.substratevm', 'gdb_utils.py') testhello_py = join(suite.dir, 'mx.substratevm', 'testhello.py') + testhello_args = [ + # We do not want to step into class initializer, so initialize everything at build time. + '--initialize-at-build-time=hello', + 'hello.Hello' + ] + if not with_isolates_only: + hello_binary = build_debug_test('isolates_off', 'hello_image', testhello_args + ['-H:-SpawnIsolates']) + if mx.get_os() == 'linux' and not build_only: + os.environ.update({'debuginfotest_isolates' : 'no'}) + mx.run([os.environ.get('GDB_BIN', 'gdb'), '-ex', 'python "ISOLATES=False"', '-x', gdb_utils_py, '-x', testhello_py, hello_binary]) - hello_binary = build_debug_test('isolates_on', ['-H:+SpawnIsolates']) + hello_binary = build_debug_test('isolates_on', 'hello_image', testhello_args + ['-H:+SpawnIsolates']) if mx.get_os() == 'linux' and not build_only: os.environ.update({'debuginfotest_arch' : mx.get_arch()}) if mx.get_os() == 'linux' and not build_only: os.environ.update({'debuginfotest_isolates' : 'yes'}) - mx.run([os.environ.get('GDB_BIN', 'gdb'), '-ex', 'python "ISOLATES=True"', '-x', testhello_py, hello_binary]) - - if not with_isolates_only: - hello_binary = build_debug_test('isolates_off', ['-H:-SpawnIsolates']) - if mx.get_os() == 'linux' and not build_only: - os.environ.update({'debuginfotest_isolates' : 'no'}) - mx.run([os.environ.get('GDB_BIN', 'gdb'), '-ex', 'python "ISOLATES=False"', '-x', testhello_py, hello_binary]) + mx.run([os.environ.get('GDB_BIN', 'gdb'), '-ex', 'python "ISOLATES=True"', '-x', gdb_utils_py, '-x', testhello_py, hello_binary]) def _javac_image(native_image, path, args=None): args = [] if args is None else args diff --git a/substratevm/mx.substratevm/testhello.py b/substratevm/mx.substratevm/testhello.py index 80b3af609f2e..e7d51c030d42 100644 --- a/substratevm/mx.substratevm/testhello.py +++ b/substratevm/mx.substratevm/testhello.py @@ -34,132 +34,33 @@ # # Run test # -# gdb -x testhello.py /path/to/hello +# gdb -x gdb_utils.py -x testhello.py /path/to/hello # # exit status 0 means all is well 1 means test failed # # n.b. assumes the sourcefile cache is in local dir sources +# +# Note that the helper routines defined in gdb_utils.py are loaded +# using gdb -x rather than being imported. That avoids having to update +# PYTHON_PATH which gdb needs to use to locate any imported code. import re import sys import os -# A helper class which checks that a sequence of lines of output -# from a gdb command matches a sequence of per-line regular -# expressions - -class Checker: - # Create a checker to check gdb command output text. - # name - string to help identify the check if we have a failure. - # regexps - a list of regular expressions which must match. - # successive lines of checked - def __init__(self, name, regexps): - self.name = name - if not isinstance(regexps, list): - regexps = [regexps] - self.rexps = [re.compile(r) for r in regexps if r is not None] - - # Check that successive lines of a gdb command's output text - # match the corresponding regexp patterns provided when this - # Checker was created. - # text - the full output of a gdb comand run by calling - # gdb.execute and passing to_string = True. - # Exits with status 1 if there are less lines in the text - # than regexp patterns or if any line fails to match the - # corresponding pattern otherwise prints the text and returns - # the set of matches. - def check(self, text, skip_fails=True): - lines = text.splitlines() - rexps = self.rexps - num_lines = len(lines) - num_rexps = len(rexps) - line_idx = 0 - matches = [] - for i in range(0, (num_rexps)): - rexp = rexps[i] - match = None - while line_idx < num_lines and match is None: - line = lines[line_idx] - match = rexp.match(line) - if match is None: - if not skip_fails: - print('Checker %s: match %d failed at line %d %s\n'%(self.name, i, line_idx, line)) - print(self) - print(text) - sys.exit(1) - else: - matches.append(match) - line_idx += 1 - if len(matches) < num_rexps: - print('Checker %s: insufficient matching lines %d for regular expressions %d'%(self.name, len(matches), num_rexps)) - print(self) - print(text) - sys.exit(1) - print(text) - return matches - - # Format a Checker as a string - def __str__(self): - rexps = self.rexps - result = 'Checker %s '%(self.name) - result += '{\n' - for rexp in rexps: - result += ' %s\n'%(rexp) - result += '}\n' - return result - -def execute(command): - print('(gdb) %s'%(command)) - try: - return gdb.execute(command, to_string=True) - except gdb.error as e: - print(e) - sys.exit(1) - # Configure this gdb session -# ensure file listings show only the current line -execute("set listsize 1") - -# Start of actual test code -# +configure_gdb() def test(): - # define some useful patterns - address_pattern = '0x[0-9a-f]+' - hex_digits_pattern = '[0-9a-f]+' - spaces_pattern = '[ \t]+' - maybe_spaces_pattern = '[ \t]*' - digits_pattern = '[0-9]+' - line_number_prefix_pattern = digits_pattern + ':' + spaces_pattern - package_pattern = '[a-z/]+' - package_file_pattern = '[a-zA-Z0-9_/]+\\.java' - varname_pattern = '[a-zA-Z0-9_]+' - wildcard_pattern = '.*' - no_arg_values_pattern = "\(\)" - arg_values_pattern = "\(([a-zA-Z0-9$_]+=[a-zA-Z0-9$_<> ]+)(, [a-zA-Z0-9$_]+=[a-zA-Z0-9$_<> ]+)*\)" - no_param_types_pattern = "\(\)" - param_types_pattern = "\(([a-zA-Z0-9[.*$_\]]+)(, [a-zA-Z0-9[.*$_\]]+)*\)" - # obtain the gdb version - # n.b. we can only test printing in gdb 10.1 upwards - exec_string=execute("show version") - checker = Checker('show version', - r"GNU gdb %s (%s)\.(%s)%s"%(wildcard_pattern, digits_pattern, digits_pattern, wildcard_pattern)) - matches = checker.check(exec_string, skip_fails=False) + match = match_gdb_version() # n.b. can only get back here with one match - match = matches[0] major = int(match.group(1)) minor = int(match.group(2)) - # printing object data requires a patched gdb - # once the patch is in we can check for a suitable - # range of major.minor versions - # for now we use an env setting print("Found gdb version %s.%s"%(major, minor)) - # printing does not always work on gdb 10.x or earlier - can_print_data = major > 10 - if os.environ.get('GDB_CAN_PRINT', '') == 'True': - can_print_data = True + # check if we can print object data + can_print_data = check_print_data(major, minor) musl = os.environ.get('debuginfotest_musl', 'no') == 'yes' @@ -198,9 +99,9 @@ def test(): checker.check(exec_string) # set a break point at hello.Hello::main - # expect "Breakpoint 1 at 0x[0-9a-f]+: file hello.Hello.java, line 76." + # expect "Breakpoint 1 at 0x[0-9a-f]+: file hello.Hello.java, line 77." exec_string = execute("break hello.Hello::main") - rexp = r"Breakpoint 1 at %s: file hello/Hello\.java, line 76\."%address_pattern + rexp = r"Breakpoint 1 at %s: file hello/Hello\.java, line 77\."%address_pattern checker = Checker('break main', rexp) checker.check(exec_string) @@ -209,16 +110,16 @@ def test(): execute("delete breakpoints") # list the line at the breakpoint - # expect "76 Greeter greeter = Greeter.greeter(args);" + # expect "77 Greeter greeter = Greeter.greeter(args);" exec_string = execute("list") - checker = Checker(r"list bp 1", "76%sGreeter greeter = Greeter\.greeter\(args\);"%spaces_pattern) + checker = Checker(r"list bp 1", "77%sGreeter greeter = Greeter\.greeter\(args\);"%spaces_pattern) checker.check(exec_string, skip_fails=False) # run a backtrace - # expect "#0 hello.Hello.main(java.lang.String[] *).* at hello.Hello.java:76" + # expect "#0 hello.Hello.main(java.lang.String[] *).* at hello.Hello.java:77" # expect "#1 0x[0-9a-f]+ in com.oracle.svm.core.code.IsolateEnterStub.JavaMainWrapper_run_.* at [a-z/]+/JavaMainWrapper.java:[0-9]+" exec_string = execute("backtrace") - stacktraceRegex = [r"#0%shello\.Hello::main%s %s at hello/Hello\.java:76"%(spaces_pattern, param_types_pattern, arg_values_pattern), + stacktraceRegex = [r"#0%shello\.Hello::main%s %s at hello/Hello\.java:77"%(spaces_pattern, param_types_pattern, arg_values_pattern), r"#1%s%s in com\.oracle\.svm\.core\.JavaMainWrapper::runCore0%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern, package_pattern), r"#2%s%s in com\.oracle\.svm\.core\.JavaMainWrapper::runCore%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern, package_pattern), r"#3%scom\.oracle\.svm\.core\.JavaMainWrapper::doRun%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, param_types_pattern, arg_values_pattern, package_pattern), @@ -362,7 +263,7 @@ def test(): exec_string = execute("info func greet") rexp = [r'All functions matching regular expression "greet":', r"File hello/Hello\.java:", - r"71:%svoid hello.Hello\$NamedGreeter::greet\(%s\);"%(maybe_spaces_pattern,wildcard_pattern), + r"72:%svoid hello.Hello\$NamedGreeter::greet\(%s\);"%(maybe_spaces_pattern,wildcard_pattern), r"File hello/Target_hello_Hello_DefaultGreeter\.java:", r"48:%svoid hello.Hello\$DefaultGreeter::greet\(%s\);"%(maybe_spaces_pattern,wildcard_pattern)] checker = Checker("info func greet", rexp) @@ -374,7 +275,7 @@ def test(): # list current line # expect "37 if (args.length == 0) {" exec_string = execute("list") - rexp = r"37%sif \(args\.length == 0\) {"%spaces_pattern + rexp = r"38%sif \(args\.length == 0\) {"%spaces_pattern checker = Checker('list hello.Hello$Greeter.greeter', rexp) checker.check(exec_string, skip_fails=False) @@ -461,8 +362,8 @@ def test(): # run a backtrace exec_string = execute("backtrace") - stacktraceRegex = [r"#0%shello\.Hello\$Greeter::greeter%s %s at hello/Hello\.java:37"%(spaces_pattern, param_types_pattern, arg_values_pattern), - r"#1%s%s in hello\.Hello::main%s %s at hello/Hello\.java:76"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern), + stacktraceRegex = [r"#0%shello\.Hello\$Greeter::greeter%s %s at hello/Hello\.java:38"%(spaces_pattern, param_types_pattern, arg_values_pattern), + r"#1%s%s in hello\.Hello::main%s %s at hello/Hello\.java:77"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern), r"#2%s%s in com\.oracle\.svm\.core\.JavaMainWrapper::runCore0%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern, package_pattern), r"#3%s%s in com\.oracle\.svm\.core\.JavaMainWrapper::runCore%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern, package_pattern), r"#4%scom\.oracle\.svm\.core\.JavaMainWrapper::doRun%s %s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, param_types_pattern, arg_values_pattern, package_pattern), @@ -590,12 +491,12 @@ def test(): # list inlineIs and inlineA and check that the listing maps to the inlined code instead of the actual code, # although not ideal this is how GDB treats inlined code in C/C++ as well - rexp = [r"128%sinlineA\(\);"%spaces_pattern] + rexp = [r"133%sinlineA\(\);"%spaces_pattern] checker = Checker('list inlineIs', rexp) checker.check(execute("list inlineIs")) # List inlineA may actually return more locations dependening on inlining decisions, but noInlineTest # always needs to be listed - rexp = [r"133%snoInlineTest\(\);"%spaces_pattern] + rexp = [r"138%snoInlineTest\(\);"%spaces_pattern] checker = Checker('list inlineA', rexp) checker.check(execute("list inlineA")) @@ -603,91 +504,91 @@ def test(): # Set breakpoint at inlined method and step through its nested inline methods exec_string = execute("break hello.Hello::inlineIs") # Dependening on inlining decisions, there are either two or one locations - rexp = r"Breakpoint %s at %s: (hello\.Hello::inlineIs\. \(2 locations\)|file hello/Hello\.java, line 128\.)"%(digits_pattern, address_pattern) + rexp = r"Breakpoint %s at %s: (hello\.Hello::inlineIs\. \(2 locations\)|file hello/Hello\.java, line 133\.)"%(digits_pattern, address_pattern) checker = Checker('break inlineIs', rexp) checker.check(exec_string, skip_fails=False) execute("continue") exec_string = execute("list") - rexp = [r"128%sinlineA\(\);"%spaces_pattern] + rexp = [r"133%sinlineA\(\);"%spaces_pattern] checker = Checker('hit break at inlineIs', rexp) checker.check(exec_string, skip_fails=False) execute("step") exec_string = execute("list") - rexp = [r"133%snoInlineTest\(\);"%spaces_pattern] + rexp = [r"138%snoInlineTest\(\);"%spaces_pattern] checker = Checker('step in inlineA', rexp) checker.check(exec_string, skip_fails=False) exec_string = execute("backtrace 4") - rexp = [r"#0%shello\.Hello::inlineA%s %s at hello/Hello\.java:133"%(spaces_pattern, no_param_types_pattern, no_arg_values_pattern), - r"#1%shello\.Hello::inlineIs%s %s at hello/Hello\.java:128"%(spaces_pattern, no_param_types_pattern, no_arg_values_pattern), - r"#2%shello\.Hello::noInlineThis%s %s at hello/Hello\.java:123"%(spaces_pattern, no_param_types_pattern, no_arg_values_pattern), - r"#3%s%s in hello\.Hello::main%s %s at hello/Hello\.java:93"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern)] + rexp = [r"#0%shello\.Hello::inlineA%s %s at hello/Hello\.java:138"%(spaces_pattern, no_param_types_pattern, no_arg_values_pattern), + r"#1%shello\.Hello::inlineIs%s %s at hello/Hello\.java:133"%(spaces_pattern, no_param_types_pattern, no_arg_values_pattern), + r"#2%shello\.Hello::noInlineThis%s %s at hello/Hello\.java:128"%(spaces_pattern, no_param_types_pattern, no_arg_values_pattern), + r"#3%s%s in hello\.Hello::main%s %s at hello/Hello\.java:94"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern)] checker = Checker('backtrace inlineMee', rexp) checker.check(exec_string, skip_fails=False) execute("delete breakpoints") exec_string = execute("break hello.Hello::noInlineTest") - rexp = r"Breakpoint %s at %s: file hello/Hello\.java, line 138\."%(digits_pattern, address_pattern) + rexp = r"Breakpoint %s at %s: file hello/Hello\.java, line 143\."%(digits_pattern, address_pattern) checker = Checker('break noInlineTest', rexp) checker.check(exec_string, skip_fails=False) execute("continue") exec_string = execute("list") - rexp = r"138%sSystem.out.println\(\"This is a test\"\);"%spaces_pattern + rexp = r"143%sSystem.out.println\(\"This is a test\"\);"%spaces_pattern checker = Checker('hit breakpoint in noInlineTest', rexp) checker.check(exec_string, skip_fails=False) exec_string = execute("backtrace 5") - rexp = [r"#0%shello\.Hello::noInlineTest%s %s at hello/Hello\.java:138"%(spaces_pattern, no_param_types_pattern, no_arg_values_pattern), - r"#1%s%s in hello\.Hello::inlineA%s %s at hello/Hello\.java:133"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern), - r"#2%shello\.Hello::inlineIs%s %s at hello/Hello\.java:128"%(spaces_pattern, no_param_types_pattern, no_arg_values_pattern), - r"#3%shello\.Hello::noInlineThis%s %s at hello/Hello\.java:123"%(spaces_pattern, no_param_types_pattern, no_arg_values_pattern), - r"#4%s%s in hello\.Hello::main%s %s at hello/Hello\.java:93"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern)] + rexp = [r"#0%shello\.Hello::noInlineTest%s %s at hello/Hello\.java:143"%(spaces_pattern, no_param_types_pattern, no_arg_values_pattern), + r"#1%s%s in hello\.Hello::inlineA%s %s at hello/Hello\.java:138"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern), + r"#2%shello\.Hello::inlineIs%s %s at hello/Hello\.java:133"%(spaces_pattern, no_param_types_pattern, no_arg_values_pattern), + r"#3%shello\.Hello::noInlineThis%s %s at hello/Hello\.java:128"%(spaces_pattern, no_param_types_pattern, no_arg_values_pattern), + r"#4%s%s in hello\.Hello::main%s %s at hello/Hello\.java:94"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern)] checker = Checker('backtrace in inlineMethod', rexp) checker.check(exec_string, skip_fails=False) execute("delete breakpoints") # Set breakpoint at method with inline and not-inlined invocation in same line exec_string = execute("break hello.Hello::inlineFrom") - rexp = r"Breakpoint %s at %s: file hello/Hello\.java, line 144."%(digits_pattern, address_pattern) + rexp = r"Breakpoint %s at %s: file hello/Hello\.java, line 149."%(digits_pattern, address_pattern) checker = Checker('break inlineFrom', rexp) checker.check(exec_string, skip_fails=False) exec_string = execute("info break 6") - rexp = [r"6%sbreakpoint%skeep%sy%s%s in hello\.Hello::inlineFrom\(\) at hello/Hello\.java:144"%(spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern, address_pattern)] + rexp = [r"6%sbreakpoint%skeep%sy%s%s in hello\.Hello::inlineFrom\(\) at hello/Hello\.java:149"%(spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern, address_pattern)] checker = Checker('info break inlineFrom', rexp) checker.check(exec_string) execute("delete breakpoints") - exec_string = execute("break Hello.java:159") - rexp = r"Breakpoint %s at %s: file hello/Hello\.java, line 160\."%(digits_pattern, address_pattern) - checker = Checker('break Hello.java:158', rexp) + exec_string = execute("break Hello.java:165") + rexp = r"Breakpoint %s at %s: file hello/Hello\.java, line 165\."%(digits_pattern, address_pattern) + checker = Checker('break Hello.java:165', rexp) checker.check(exec_string) execute("continue 5") exec_string = execute("backtrace 14") - rexp = [r"#0%shello\.Hello::inlineMixTo%s %s at hello/Hello\.java:160"%(spaces_pattern, param_types_pattern, arg_values_pattern), - r"#1%shello\.Hello::noInlineHere%s %s at hello/Hello\.java:152"%(spaces_pattern, param_types_pattern, arg_values_pattern), - r"#2%s(%s in)? hello\.Hello::inlineMixTo%s %s at hello/Hello\.java:158"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern), - r"#3%shello\.Hello::noInlineHere%s %s at hello/Hello\.java:152"%(spaces_pattern, param_types_pattern, arg_values_pattern), - r"#4%s(%s in)? hello\.Hello::inlineMixTo%s %s at hello/Hello\.java:158"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern), - r"#5%shello\.Hello::noInlineHere%s %s at hello/Hello\.java:152"%(spaces_pattern, param_types_pattern, arg_values_pattern), - r"#6%s(%s in)? hello\.Hello::inlineMixTo%s %s at hello/Hello\.java:158"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern), - r"#7%shello\.Hello::noInlineHere%s %s at hello/Hello\.java:152"%(spaces_pattern, param_types_pattern, arg_values_pattern), - r"#8%s(%s in)? hello\.Hello::inlineMixTo%s %s at hello/Hello\.java:158"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern), - r"#9%shello\.Hello::noInlineHere%s %s at hello/Hello\.java:152"%(spaces_pattern, param_types_pattern, arg_values_pattern), - r"#10%s(%s in)? hello\.Hello::inlineMixTo%s %s at hello/Hello\.java:158"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern), - r"#11%shello\.Hello::noInlineHere%s %s at hello/Hello\.java:152"%(spaces_pattern, param_types_pattern, arg_values_pattern), - r"#12%s(%s in)? hello\.Hello::inlineFrom%s %s at hello/Hello\.java:144"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern), - r"#13%shello\.Hello::main%s %s at hello/Hello\.java:94"%(spaces_pattern, param_types_pattern, arg_values_pattern)] + rexp = [r"#0%shello\.Hello::inlineMixTo%s %s at hello/Hello\.java:165"%(spaces_pattern, param_types_pattern, arg_values_pattern), + r"#1%shello\.Hello::noInlineHere%s %s at hello/Hello\.java:157"%(spaces_pattern, param_types_pattern, arg_values_pattern), + r"#2%s(%s in)? hello\.Hello::inlineMixTo%s %s at hello/Hello\.java:163"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern), + r"#3%shello\.Hello::noInlineHere%s %s at hello/Hello\.java:157"%(spaces_pattern, param_types_pattern, arg_values_pattern), + r"#4%s(%s in)? hello\.Hello::inlineMixTo%s %s at hello/Hello\.java:163"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern), + r"#5%shello\.Hello::noInlineHere%s %s at hello/Hello\.java:157"%(spaces_pattern, param_types_pattern, arg_values_pattern), + r"#6%s(%s in)? hello\.Hello::inlineMixTo%s %s at hello/Hello\.java:163"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern), + r"#7%shello\.Hello::noInlineHere%s %s at hello/Hello\.java:157"%(spaces_pattern, param_types_pattern, arg_values_pattern), + r"#8%s(%s in)? hello\.Hello::inlineMixTo%s %s at hello/Hello\.java:163"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern), + r"#9%shello\.Hello::noInlineHere%s %s at hello/Hello\.java:157"%(spaces_pattern, param_types_pattern, arg_values_pattern), + r"#10%s(%s in)? hello\.Hello::inlineMixTo%s %s at hello/Hello\.java:163"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern), + r"#11%shello\.Hello::noInlineHere%s %s at hello/Hello\.java:157"%(spaces_pattern, param_types_pattern, arg_values_pattern), + r"#12%s(%s in)? hello\.Hello::inlineFrom%s %s at hello/Hello\.java:149"%(spaces_pattern, address_pattern, no_param_types_pattern, no_arg_values_pattern), + r"#13%shello\.Hello::main%s %s at hello/Hello\.java:95"%(spaces_pattern, param_types_pattern, arg_values_pattern)] checker = Checker('backtrace in recursive inlineMixTo', rexp) checker.check(exec_string, skip_fails=False) execute("delete breakpoints") - exec_string = execute("break Hello.java:173") + exec_string = execute("break Hello.java:178") # we cannot be sure how much inlining will happen so we # specify a pattern for the number of locations - rexp = r"Breakpoint %s at %s: Hello\.java:173\. \(%s locations\)"%(digits_pattern, address_pattern, digits_pattern) - checker = Checker('break Hello.java:173', rexp) + rexp = r"Breakpoint %s at %s: Hello\.java:178\. \(%s locations\)"%(digits_pattern, address_pattern, digits_pattern) + checker = Checker('break Hello.java:178', rexp) checker.check(exec_string) execute("continue") @@ -696,29 +597,29 @@ def test(): # which means the format of the frame display may vary from # one build to the next. so we use a generic match after the # first pair. - rexp = [r"#0%shello\.Hello::inlineTo%s %s at hello/Hello\.java:173"%(spaces_pattern, param_types_pattern, arg_values_pattern), - r"#1%s(%s in)? hello\.Hello::inlineHere%s %s at hello/Hello\.java:165"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern), - r"#2%shello\.Hello::inlineTo%s %s at hello/Hello\.java:171"%(wildcard_pattern, param_types_pattern, arg_values_pattern), - r"#3%shello\.Hello::inlineHere%s %s at hello/Hello\.java:165"%(wildcard_pattern, param_types_pattern, arg_values_pattern), - r"#4%shello\.Hello::inlineTo%s %s at hello/Hello\.java:171"%(wildcard_pattern, param_types_pattern, arg_values_pattern), - r"#5%shello\.Hello::inlineHere%s %s at hello/Hello\.java:165"%(wildcard_pattern, param_types_pattern, arg_values_pattern), - r"#6%shello\.Hello::inlineTo%s %s at hello/Hello\.java:171"%(wildcard_pattern, param_types_pattern, arg_values_pattern), - r"#7%shello\.Hello::inlineHere%s %s at hello/Hello\.java:165"%(wildcard_pattern, param_types_pattern, arg_values_pattern), - r"#8%shello\.Hello::inlineTo%s %s at hello/Hello\.java:171"%(wildcard_pattern, param_types_pattern, arg_values_pattern), - r"#9%shello\.Hello::inlineHere%s %s at hello/Hello\.java:165"%(wildcard_pattern, param_types_pattern, arg_values_pattern), - r"#10%shello\.Hello::inlineTo%s %s at hello/Hello\.java:171"%(wildcard_pattern, param_types_pattern, arg_values_pattern), - r"#11%shello\.Hello::inlineHere%s %s at hello/Hello\.java:165"%(wildcard_pattern, param_types_pattern, arg_values_pattern), - r"#12%shello\.Hello::inlineFrom%s %s at hello/Hello\.java:146"%(spaces_pattern, no_param_types_pattern,no_arg_values_pattern), - r"#13%shello\.Hello::main%s %s at hello/Hello\.java:94"%(spaces_pattern, param_types_pattern, arg_values_pattern)] + rexp = [r"#0%shello\.Hello::inlineTo%s %s at hello/Hello\.java:178"%(spaces_pattern, param_types_pattern, arg_values_pattern), + r"#1%s(%s in)? hello\.Hello::inlineHere%s %s at hello/Hello\.java:170"%(spaces_pattern, address_pattern, param_types_pattern, arg_values_pattern), + r"#2%shello\.Hello::inlineTo%s %s at hello/Hello\.java:176"%(wildcard_pattern, param_types_pattern, arg_values_pattern), + r"#3%shello\.Hello::inlineHere%s %s at hello/Hello\.java:170"%(wildcard_pattern, param_types_pattern, arg_values_pattern), + r"#4%shello\.Hello::inlineTo%s %s at hello/Hello\.java:176"%(wildcard_pattern, param_types_pattern, arg_values_pattern), + r"#5%shello\.Hello::inlineHere%s %s at hello/Hello\.java:170"%(wildcard_pattern, param_types_pattern, arg_values_pattern), + r"#6%shello\.Hello::inlineTo%s %s at hello/Hello\.java:176"%(wildcard_pattern, param_types_pattern, arg_values_pattern), + r"#7%shello\.Hello::inlineHere%s %s at hello/Hello\.java:170"%(wildcard_pattern, param_types_pattern, arg_values_pattern), + r"#8%shello\.Hello::inlineTo%s %s at hello/Hello\.java:176"%(wildcard_pattern, param_types_pattern, arg_values_pattern), + r"#9%shello\.Hello::inlineHere%s %s at hello/Hello\.java:170"%(wildcard_pattern, param_types_pattern, arg_values_pattern), + r"#10%shello\.Hello::inlineTo%s %s at hello/Hello\.java:176"%(wildcard_pattern, param_types_pattern, arg_values_pattern), + r"#11%shello\.Hello::inlineHere%s %s at hello/Hello\.java:170"%(wildcard_pattern, param_types_pattern, arg_values_pattern), + r"#12%shello\.Hello::inlineFrom%s %s at hello/Hello\.java:151"%(spaces_pattern, no_param_types_pattern,no_arg_values_pattern), + r"#13%shello\.Hello::main%s %s at hello/Hello\.java:95"%(spaces_pattern, param_types_pattern, arg_values_pattern)] checker = Checker('backtrace in recursive inlineTo', rexp) checker.check(exec_string, skip_fails=False) execute("delete breakpoints") - exec_string = execute("break Hello.java:179") + exec_string = execute("break Hello.java:184") # we cannot be sure how much inlining will happen so we # specify a pattern for the number of locations - rexp = r"Breakpoint %s at %s: Hello\.java:179\. \(%s locations\)"%(digits_pattern, address_pattern, digits_pattern) - checker = Checker('break Hello.java:179', rexp) + rexp = r"Breakpoint %s at %s: Hello\.java:184\. \(%s locations\)"%(digits_pattern, address_pattern, digits_pattern) + checker = Checker('break Hello.java:184', rexp) checker.check(exec_string) execute("continue 5") @@ -727,15 +628,15 @@ def test(): # which means the format of the frame display may vary from # one build to the next. so we use a generic match after the # first one. - rexp = [r"#0%shello\.Hello::inlineTailRecursion%s %s at hello/Hello\.java:179"%(spaces_pattern, param_types_pattern, arg_values_pattern), - r"#1%shello\.Hello::inlineTailRecursion%s %s at hello/Hello\.java:182"%(wildcard_pattern, param_types_pattern, arg_values_pattern), - r"#2%shello\.Hello::inlineTailRecursion%s %s at hello/Hello\.java:182"%(wildcard_pattern, param_types_pattern, arg_values_pattern), - r"#3%shello\.Hello::inlineTailRecursion%s %s at hello/Hello\.java:182"%(wildcard_pattern, param_types_pattern, arg_values_pattern), - r"#4%shello\.Hello::inlineTailRecursion%s %s at hello/Hello\.java:182"%(wildcard_pattern, param_types_pattern, arg_values_pattern), - r"#5%shello\.Hello::inlineTailRecursion%s %s at hello/Hello\.java:182"%(wildcard_pattern, param_types_pattern, arg_values_pattern), - r"#6%shello\.Hello::inlineFrom%s %s at hello/Hello\.java:147"%(spaces_pattern, no_param_types_pattern, no_arg_values_pattern), - r"#7%shello\.Hello::main%s %s at hello/Hello\.java:94"%(spaces_pattern, param_types_pattern, arg_values_pattern)] - checker = Checker('backtrace in recursive inlineTo', rexp) + rexp = [r"#0%shello\.Hello::inlineTailRecursion%s %s at hello/Hello\.java:184"%(spaces_pattern, param_types_pattern, arg_values_pattern), + r"#1%shello\.Hello::inlineTailRecursion%s %s at hello/Hello\.java:187"%(wildcard_pattern, param_types_pattern, arg_values_pattern), + r"#2%shello\.Hello::inlineTailRecursion%s %s at hello/Hello\.java:187"%(wildcard_pattern, param_types_pattern, arg_values_pattern), + r"#3%shello\.Hello::inlineTailRecursion%s %s at hello/Hello\.java:187"%(wildcard_pattern, param_types_pattern, arg_values_pattern), + r"#4%shello\.Hello::inlineTailRecursion%s %s at hello/Hello\.java:187"%(wildcard_pattern, param_types_pattern, arg_values_pattern), + r"#5%shello\.Hello::inlineTailRecursion%s %s at hello/Hello\.java:187"%(wildcard_pattern, param_types_pattern, arg_values_pattern), + r"#6%shello\.Hello::inlineFrom%s %s at hello/Hello\.java:152"%(spaces_pattern, no_param_types_pattern, no_arg_values_pattern), + r"#7%shello\.Hello::main%s %s at hello/Hello\.java:95"%(spaces_pattern, param_types_pattern, arg_values_pattern)] + checker = Checker('backtrace in recursive inlineTailRecursion', rexp) checker.check(exec_string, skip_fails=False) # on aarch64 the initial break occurs at the stack push @@ -754,10 +655,10 @@ def test(): # exec_string = execute("break hello.Hello::noInlineManyArgs") exec_string = execute("break *0x%x"%bp_address) - rexp = r"Breakpoint %s at %s: file hello/Hello\.java, line 188\."%(digits_pattern, address_pattern) + rexp = r"Breakpoint %s at %s: file hello/Hello\.java, line 193\."%(digits_pattern, address_pattern) checker = Checker(r"break *0x%x"%bp_address, rexp) checker.check(exec_string) - #rexp = r"Breakpoint %s at %s: file hello/Hello\.java, line 188\."%(digits_pattern, address_pattern) + #rexp = r"Breakpoint %s at %s: file hello/Hello\.java, line 193\."%(digits_pattern, address_pattern) #checker = Checker('break hello.Hello::noInlineManyArgs', rexp) #checker.check(exec_string) execute("continue") @@ -882,6 +783,109 @@ def test(): checker = Checker('x/s t->value->data', rexp) checker.check(exec_string, skip_fails=True) + execute("delete breakpoints"); + + ### Now check foreign debug type info + + # check type information is reported correctly + + exec_string=execute("info types com.oracle.svm.test.debug.CStructTests\$") + rexp = [r"%stypedef composite_struct \* com\.oracle\.svm\.test\.debug\.CStructTests\$CompositeStruct;"%spaces_pattern, + r"%stypedef int32_t \* com\.oracle\.svm\.test\.debug\.CStructTests\$MyCIntPointer;"%spaces_pattern, + r"%stypedef simple_struct \* com\.oracle\.svm\.test\.debug\.CStructTests\$SimpleStruct;"%spaces_pattern, + r"%stypedef simple_struct2 \* com\.oracle\.svm\.test\.debug\.CStructTests\$SimpleStruct2;"%spaces_pattern, + r"%stypedef weird \* com\.oracle\.svm\.test\.debug\.CStructTests\$Weird;"%spaces_pattern] + checker = Checker("info types com.oracle.svm.test.debug.CStructTests\$", rexp) + checker.check(exec_string) + + # Print various foreign struct types and check they have the correct layout + exec_string = execute("ptype /o 'com.oracle.svm.test.debug.CStructTests$CompositeStruct'") + rexp = [r"type = struct composite_struct {", + r"/\*%s0%s\|%s1%s\*/%sbyte c1;"%(spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern), + r"/\*%sXXX%s3-byte hole%s\*/"%(spaces_pattern, spaces_pattern, spaces_pattern), + r"/\*%s4%s\|%s8%s\*/%sstruct simple_struct {"%(spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern), + r"/\*%s4%s\|%s4%s\*/%sint first;"%(spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern), + r"/\*%s8%s\|%s4%s\*/%sint second;"%(spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern), + r"%s/\* total size \(bytes\):%s8 \*/"%(spaces_pattern, spaces_pattern), + r"%s} c2;"%(spaces_pattern), + r"/\*%s12%s\|%s4%s\*/%sint c3;"%(spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern), + r"/\*%s16%s\|%s16%s\*/%sstruct simple_struct2 {"%(spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern), + r"/\*%s16%s\|%s1%s\*/%sbyte alpha;"%(spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern), + r"/\*%sXXX%s7-byte hole%s\*/"%(spaces_pattern, spaces_pattern, spaces_pattern), + r"/\*%s24%s\|%s8%s\*/%slong beta;"%(spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern), + r"%s/\* total size \(bytes\):%s16 \*/"%(spaces_pattern, spaces_pattern), + r"%s} c4;"%(spaces_pattern), + r"/\*%s32%s\|%s2%s\*/%sshort c5;"%(spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern), + r"/\*%sXXX%s6-byte padding%s\*/"%(spaces_pattern, spaces_pattern, spaces_pattern), + r"%s/\* total size \(bytes\):%s40 \*/"%(spaces_pattern, spaces_pattern), + r"%s} \*"%(spaces_pattern)] + checker = Checker("ptype 'com.oracle.svm.test.debug.CStructTests$CompositeStruct'", rexp) + checker.check(exec_string) + + exec_string = execute("ptype /o 'com.oracle.svm.test.debug.CStructTests$Weird'") + rexp = [r"type = struct weird {", + r"/\*%s0%s\|%s2%s\*/%sshort f_short;"%(spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern), + r"/\*%sXXX%s6-byte hole%s\*/"%(spaces_pattern, spaces_pattern, spaces_pattern), + r"/\*%s8%s\|%s4%s\*/%sint f_int;"%(spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern), + r"/\*%sXXX%s4-byte hole%s\*/"%(spaces_pattern, spaces_pattern, spaces_pattern), + r"/\*%s16%s\|%s8%s\*/%slong f_long;"%(spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern), + r"/\*%s24%s\|%s4%s\*/%sfloat f_float;"%(spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern), + r"/\*%sXXX%s4-byte hole%s\*/"%(spaces_pattern, spaces_pattern, spaces_pattern), + r"/\*%s32%s\|%s8%s\*/%sdouble f_double;"%(spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern), + r"/\*%s40%s\|%s32%s\*/%sint32_t a_int\[8\];"%(spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern), + r"/\*%s72%s\|%s12%s\*/%sint8_t a_char\[12\];"%(spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern, spaces_pattern), + r"/\*%sXXX%s4-byte padding%s\*/"%(spaces_pattern, spaces_pattern, spaces_pattern), + r"%s/\* total size \(bytes\):%s88 \*/"%(spaces_pattern, spaces_pattern), + r"%s} \*"%(spaces_pattern)] + checker = Checker("ptype 'com.oracle.svm.test.debug.CStructTests$Weird'", rexp) + checker.check(exec_string) + + + # check foreign data is printed correctly if we can + + if can_print_data: + # set a break point at com.oracle.svm.test.debug.CStructTests::free + exec_string = execute("break com.oracle.svm.test.debug.CStructTests::free") + rexp = r"Breakpoint %s at %s: file com/oracle/svm/test/debug/CStructTests\.java, line %s\."%(digits_pattern, address_pattern, digits_pattern) + checker = Checker('break free', rexp) + checker.check(exec_string) + + # continue the program to the breakpoint + execute("continue") + + # check the argument + exec_string = execute("print *('com.oracle.svm.test.debug.CStructTests$CompositeStruct')ptr") + rexp = [r"%s = {"%wildcard_pattern, + r" c1 = 7 '\\a',", + r" c2 = {", + r" first = 17,", + r" second = 19", + r" },", + r" c3 = 13", + r" c4 = {", + r" alpha = 3 '\\003',", + r" beta = 9223372036854775807", + r" },", + r" c5 = 32000", + r"}"] + checker = Checker('print CompositeStruct', rexp) + checker.check(exec_string) + + # run the program till the breakpoint + execute("continue") + + # check the argument again + exec_string = execute("print *('com.oracle.svm.test.debug.CStructTests$Weird')ptr") + rexp = [r" f_short = 42,", + r" f_int = 43", + r" f_long = 44", + r" f_float = 4.5", + r" f_double = 4.5999999999999996", + r" a_int = {0, 1, 2, 3, 4, 5, 6, 7},", + r' a_char = "0123456789AB"'] + checker = Checker('print Weird', rexp) + checker.check(exec_string) + print(execute("quit 0")) test() 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 dd4a51e8e259..19f7f66fbde8 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 @@ -206,11 +206,11 @@ public Stream normalCompiledEntries() { return compiledEntries(); } - private void processInterface(ResolvedJavaType interfaceType, DebugInfoBase debugInfoBase, DebugContext debugContext) { + protected void processInterface(ResolvedJavaType interfaceType, DebugInfoBase debugInfoBase, DebugContext debugContext) { String interfaceName = interfaceType.toJavaName(); debugContext.log("typename %s adding interface %s%n", typeName, interfaceName); ClassEntry entry = debugInfoBase.lookupClassEntry(interfaceType); - assert entry instanceof InterfaceClassEntry; + assert entry instanceof InterfaceClassEntry || (entry instanceof ForeignTypeEntry && this instanceof ForeignTypeEntry); InterfaceClassEntry interfaceClassEntry = (InterfaceClassEntry) entry; interfaces.add(interfaceClassEntry); interfaceClassEntry.addImplementor(this, debugContext); 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 df163def7af8..b343ec04171e 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 @@ -143,6 +143,10 @@ public abstract class DebugInfoBase { * Handle on type entry for header structure. */ private HeaderTypeEntry headerType; + /** + * Handle on type entry for void type. + */ + private TypeEntry voidType; /** * Handle on class entry for java.lang.Object. */ @@ -398,6 +402,11 @@ private TypeEntry createTypeEntry(String typeName, String fileName, Path filePat 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; } @@ -417,6 +426,9 @@ private TypeEntry addTypeEntry(ResolvedJavaType idType, String typeName, String if (typeName.equals("java.lang.Object")) { objectClass = (ClassEntry) typeEntry; } + if (typeName.equals("void")) { + voidType = typeEntry; + } if (typeEntry instanceof ClassEntry) { indexInstanceClass(idType, (ClassEntry) typeEntry); } @@ -455,6 +467,12 @@ public HeaderTypeEntry lookupHeaderType() { return headerType; } + public TypeEntry lookupVoidType() { + // this should only be looked up after all types have been notified + assert voidType != null; + return voidType; + } + public ClassEntry lookupObjectClass() { // this should only be looked up after all types have been notified assert objectClass != null; 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 1f1f06041d9a..f6e7c6a0cd40 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 @@ -30,10 +30,13 @@ public class FieldEntry extends MemberEntry { private final int size; private final int offset; - public FieldEntry(FileEntry fileEntry, String fieldName, StructureTypeEntry ownerType, TypeEntry valueType, int size, int offset, int modifiers) { + private final boolean isEmbedded; + + 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; + this.isEmbedded = isEmbedded; } public String fieldName() { @@ -47,4 +50,8 @@ public int getSize() { public int getOffset() { return offset; } + + public boolean isEmbedded() { + return isEmbedded; + } } 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 new file mode 100644 index 000000000000..825984255b86 --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignTypeEntry.java @@ -0,0 +1,144 @@ +/* + * 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; + +import com.oracle.objectfile.debuginfo.DebugInfoProvider; +import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo; +import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugForeignTypeInfo; +import jdk.vm.ci.meta.ResolvedJavaType; +import org.graalvm.compiler.debug.DebugContext; + +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; + } + + @Override + public DebugInfoProvider.DebugTypeInfo.DebugTypeKind typeKind() { + return DebugInfoProvider.DebugTypeInfo.DebugTypeKind.FOREIGN; + } + + @Override + public void addDebugInfo(DebugInfoBase debugInfoBase, DebugTypeInfo debugTypeInfo, DebugContext debugContext) { + assert debugTypeInfo instanceof DebugForeignTypeInfo; + super.addDebugInfo(debugInfoBase, debugTypeInfo, debugContext); + 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 (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 String getTypedefName() { + return typedefName; + } + + public ForeignTypeEntry getParent() { + return parent; + } + + public TypeEntry getPointerTo() { + return pointerTo; + } + + public boolean isWord() { + return (flags & FLAG_WORD) != 0; + } + + public boolean isStruct() { + return (flags & FLAG_STRUCT) != 0; + } + + public boolean isPointer() { + return (flags & FLAG_POINTER) != 0; + } + + public boolean isIntegral() { + return (flags & FLAG_INTEGRAL) != 0; + } + + public boolean isSigned() { + return (flags & FLAG_SIGNED) != 0; + } + + 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"; + } + } +} 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 a242db7c3d89..293e72332bab 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 @@ -54,6 +54,10 @@ public Stream fields() { return fields.stream(); } + 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); @@ -65,16 +69,17 @@ protected FieldEntry addField(DebugFieldInfo debugFieldInfo, DebugInfoBase debug String valueTypeName = valueType.toJavaName(); int fieldSize = debugFieldInfo.size(); int fieldoffset = debugFieldInfo.offset(); + boolean fieldIsEmbedded = debugFieldInfo.isEmbedded(); int fieldModifiers = debugFieldInfo.modifiers(); - debugContext.log("typename %s adding %s field %s type %s size %s at offset 0x%x%n", - typeName, memberModifiers(fieldModifiers), fieldName, valueTypeName, fieldSize, fieldoffset); + 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, fieldModifiers); + FieldEntry fieldEntry = new FieldEntry(fileEntry, fieldName, this, valueTypeEntry, fieldSize, fieldoffset, fieldIsEmbedded, fieldModifiers); fields.add(fieldEntry); return fieldEntry; } 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 c2d77a7b3183..e0c7b9a56564 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 @@ -32,6 +32,7 @@ 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; @@ -98,8 +99,25 @@ public boolean isEnum() { return typeKind() == ENUM; } + public boolean isForeign() { + return typeKind() == FOREIGN; + } + + /** + * 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. When it comes to encoding the model type as DWARF + * or PECOFF method {@link #isForeign()} may need to be called in order to allow foreign types + * ot be special cased. + * + * @return true if this entry is a class type otherwise false. + */ public boolean isClass() { - return isInstance() | isInterface() || isEnum(); + return isInstance() || isInterface() || isEnum() || isForeign(); } public boolean isStructure() { 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 85ec41c0402c..c3a2f489907a 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 @@ -96,7 +96,8 @@ enum DebugTypeKind { INSTANCE, INTERFACE, ARRAY, - HEADER; + HEADER, + FOREIGN; @Override public String toString() { @@ -113,6 +114,8 @@ public String toString() { return "array"; case HEADER: return "header"; + case FOREIGN: + return "foreign"; default: return "???"; } @@ -140,8 +143,6 @@ public String toString() { } interface DebugInstanceTypeInfo extends DebugTypeInfo { - int headerSize(); - String loaderName(); Stream fieldInfoProvider(); @@ -159,6 +160,26 @@ interface DebugEnumTypeInfo extends DebugInstanceTypeInfo { interface DebugInterfaceTypeInfo extends DebugInstanceTypeInfo { } + interface DebugForeignTypeInfo extends DebugInstanceTypeInfo { + String typedefName(); + + boolean isWord(); + + boolean isStruct(); + + boolean isPointer(); + + boolean isIntegral(); + + boolean isFloat(); + + boolean isSigned(); + + ResolvedJavaType parent(); + + ResolvedJavaType pointerTo(); + } + interface DebugArrayTypeInfo extends DebugTypeInfo { int baseSize(); @@ -208,6 +229,8 @@ interface DebugFieldInfo extends DebugMemberInfo { int offset(); int size(); + + boolean isEmbedded(); } interface DebugMethodInfo extends DebugMemberInfo { 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 7da1fd1f55aa..14bc1ef8246c 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 @@ -832,8 +832,13 @@ public int writeAbbrevs(DebugContext context, byte[] buffer, int p) { pos = writeInterfaceLayoutAbbrev(context, buffer, pos); pos = writeInterfaceReferenceAbbrev(context, buffer, pos); + pos = writeForeignReferenceAbbrev(context, buffer, pos); + pos = writeForeignTypedefAbbrev(context, buffer, pos); + pos = writeForeignStructAbbrev(context, buffer, pos); + pos = writeHeaderFieldAbbrev(context, buffer, pos); - pos = writeArrayDataTypeAbbrev(context, buffer, pos); + pos = writeArrayDataTypeAbbrevs(context, buffer, pos); + pos = writeArraySubrangeTypeAbbrev(context, buffer, pos); pos = writeMethodLocationAbbrev(context, buffer, pos); pos = writeStaticFieldLocationAbbrev(context, buffer, pos); pos = writeSuperReferenceAbbrev(context, buffer, pos); @@ -1243,6 +1248,63 @@ private int writeInterfaceImplementorAbbrev(@SuppressWarnings("unused") DebugCon return pos; } + private int writeForeignReferenceAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { + int pos = p; + + /* A pointer to the class struct type. */ + pos = writeAbbrevCode(DwarfDebugInfo.DW_ABBREV_CODE_foreign_pointer, buffer, pos); + pos = writeTag(DwarfDebugInfo.DW_TAG_pointer_type, buffer, pos); + pos = writeFlag(DwarfDebugInfo.DW_CHILDREN_no, buffer, pos); + pos = writeAttrType(DwarfDebugInfo.DW_AT_byte_size, buffer, pos); + pos = writeAttrForm(DwarfDebugInfo.DW_FORM_data1, buffer, pos); + pos = writeAttrType(DwarfDebugInfo.DW_AT_type, buffer, pos); + pos = writeAttrForm(DwarfDebugInfo.DW_FORM_ref_addr, buffer, pos); + /* + * Now terminate. + */ + pos = writeAttrType(DwarfDebugInfo.DW_AT_null, buffer, pos); + pos = writeAttrForm(DwarfDebugInfo.DW_FORM_null, buffer, pos); + return pos; + } + + private int writeForeignTypedefAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { + int pos = p; + + /* A pointer to the class struct type. */ + pos = writeAbbrevCode(DwarfDebugInfo.DW_ABBREV_CODE_foreign_typedef, buffer, pos); + pos = writeTag(DwarfDebugInfo.DW_TAG_typedef, buffer, pos); + pos = writeFlag(DwarfDebugInfo.DW_CHILDREN_no, buffer, pos); + pos = writeAttrType(DwarfDebugInfo.DW_AT_name, buffer, pos); + pos = writeAttrForm(DwarfDebugInfo.DW_FORM_strp, buffer, pos); + pos = writeAttrType(DwarfDebugInfo.DW_AT_type, buffer, pos); + pos = writeAttrForm(DwarfDebugInfo.DW_FORM_ref_addr, buffer, pos); + /* + * Now terminate. + */ + pos = writeAttrType(DwarfDebugInfo.DW_AT_null, buffer, pos); + pos = writeAttrForm(DwarfDebugInfo.DW_FORM_null, buffer, pos); + return pos; + } + + private int writeForeignStructAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { + int pos = p; + + /* A pointer to the class struct type. */ + pos = writeAbbrevCode(DwarfDebugInfo.DW_ABBREV_CODE_foreign_struct, buffer, pos); + pos = writeTag(DwarfDebugInfo.DW_TAG_structure_type, buffer, pos); + pos = writeFlag(DwarfDebugInfo.DW_CHILDREN_yes, buffer, pos); + pos = writeAttrType(DwarfDebugInfo.DW_AT_name, buffer, pos); + pos = writeAttrForm(DwarfDebugInfo.DW_FORM_strp, buffer, pos); + pos = writeAttrType(DwarfDebugInfo.DW_AT_byte_size, buffer, pos); + pos = writeAttrForm(DwarfDebugInfo.DW_FORM_data1, buffer, pos); + /* + * Now terminate. + */ + pos = writeAttrType(DwarfDebugInfo.DW_AT_null, buffer, pos); + pos = writeAttrForm(DwarfDebugInfo.DW_FORM_null, buffer, pos); + return pos; + } + private int writeHeaderFieldAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { int pos = p; @@ -1254,7 +1316,7 @@ private int writeHeaderFieldAbbrev(@SuppressWarnings("unused") DebugContext cont pos = writeAttrType(DwarfDebugInfo.DW_AT_type, buffer, pos); pos = writeAttrForm(DwarfDebugInfo.DW_FORM_ref_addr, buffer, pos); pos = writeAttrType(DwarfDebugInfo.DW_AT_data_member_location, buffer, pos); - pos = writeAttrForm(DwarfDebugInfo.DW_FORM_data1, buffer, pos); + pos = writeAttrForm(DwarfDebugInfo.DW_FORM_data2, buffer, pos); pos = writeAttrType(DwarfDebugInfo.DW_AT_accessibility, buffer, pos); pos = writeAttrForm(DwarfDebugInfo.DW_FORM_data1, buffer, pos); /* @@ -1265,14 +1327,24 @@ private int writeHeaderFieldAbbrev(@SuppressWarnings("unused") DebugContext cont return pos; } - private int writeArrayDataTypeAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { + private int writeArrayDataTypeAbbrevs(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { + int pos = p; + pos = writeArrayDataTypeAbbrev(context, DwarfDebugInfo.DW_ABBREV_CODE_array_data_type1, buffer, pos); + pos = writeArrayDataTypeAbbrev(context, DwarfDebugInfo.DW_ABBREV_CODE_array_data_type2, buffer, pos); + return pos; + } + + private int writeArrayDataTypeAbbrev(@SuppressWarnings("unused") DebugContext context, int abbrevCode, byte[] buffer, int p) { int pos = p; - pos = writeAbbrevCode(DwarfDebugInfo.DW_ABBREV_CODE_array_data_type, buffer, pos); + pos = writeAbbrevCode(abbrevCode, buffer, pos); pos = writeTag(DwarfDebugInfo.DW_TAG_array_type, buffer, pos); - pos = writeFlag(DwarfDebugInfo.DW_CHILDREN_no, buffer, pos); - pos = writeAttrType(DwarfDebugInfo.DW_AT_byte_size, buffer, pos); - pos = writeAttrForm(DwarfDebugInfo.DW_FORM_data1, buffer, pos); + boolean hasChildren = (abbrevCode == DwarfDebugInfo.DW_ABBREV_CODE_array_data_type2); + pos = writeFlag((hasChildren ? DwarfDebugInfo.DW_CHILDREN_yes : DwarfDebugInfo.DW_CHILDREN_no), buffer, pos); + if (abbrevCode == DwarfDebugInfo.DW_ABBREV_CODE_array_data_type2) { + pos = writeAttrType(DwarfDebugInfo.DW_AT_byte_size, buffer, pos); + pos = writeAttrForm(DwarfDebugInfo.DW_FORM_data4, buffer, pos); + } pos = writeAttrType(DwarfDebugInfo.DW_AT_type, buffer, pos); pos = writeAttrForm(DwarfDebugInfo.DW_FORM_ref_addr, buffer, pos); /* @@ -1280,6 +1352,23 @@ private int writeArrayDataTypeAbbrev(@SuppressWarnings("unused") DebugContext co */ pos = writeAttrType(DwarfDebugInfo.DW_AT_null, buffer, pos); pos = writeAttrForm(DwarfDebugInfo.DW_FORM_null, buffer, pos); + + return pos; + } + + private int writeArraySubrangeTypeAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { + int pos = p; + + pos = writeAbbrevCode(DwarfDebugInfo.DW_ABBREV_CODE_array_subrange, buffer, pos); + pos = writeTag(DwarfDebugInfo.DW_TAG_subrange_type, buffer, pos); + pos = writeFlag(DwarfDebugInfo.DW_CHILDREN_no, buffer, pos); + pos = writeAttrType(DwarfDebugInfo.DW_AT_count, buffer, pos); + pos = writeAttrForm(DwarfDebugInfo.DW_FORM_data4, buffer, pos); + /* + * Now terminate. + */ + pos = writeAttrType(DwarfDebugInfo.DW_AT_null, buffer, pos); + pos = writeAttrForm(DwarfDebugInfo.DW_FORM_null, buffer, pos); return pos; } @@ -1354,7 +1443,7 @@ private int writeIndirectLayoutAbbrev(@SuppressWarnings("unused") DebugContext c * pointer type that is used to type values that need translation to a raw address i.e. * values stored in static and instance fields. */ - /* the type ofr an indirect layout that includes address translation info */ + /* the type for an indirect layout that includes address translation info */ pos = writeAbbrevCode(DwarfDebugInfo.DW_ABBREV_CODE_indirect_layout, buffer, pos); pos = writeTag(DwarfDebugInfo.DW_TAG_class_type, buffer, pos); pos = writeFlag(DwarfDebugInfo.DW_CHILDREN_yes, 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 a7a8f66fcc9e..8683d0328579 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 @@ -79,40 +79,45 @@ public class DwarfDebugInfo extends DebugInfoBase { public static final int DW_ABBREV_CODE_class_layout1 = 6; public static final int DW_ABBREV_CODE_class_layout2 = 7; public static final int DW_ABBREV_CODE_class_pointer = 8; - public static final int DW_ABBREV_CODE_method_location = 9; - public static final int DW_ABBREV_CODE_static_field_location = 10; - public static final int DW_ABBREV_CODE_array_layout = 11; - public static final int DW_ABBREV_CODE_array_pointer = 12; - public static final int DW_ABBREV_CODE_interface_layout = 13; - public static final int DW_ABBREV_CODE_interface_pointer = 14; - public static final int DW_ABBREV_CODE_indirect_layout = 15; - public static final int DW_ABBREV_CODE_indirect_pointer = 16; + public static final int DW_ABBREV_CODE_foreign_pointer = 9; + public static final int DW_ABBREV_CODE_foreign_typedef = 10; + public static final int DW_ABBREV_CODE_foreign_struct = 11; + public static final int DW_ABBREV_CODE_method_location = 12; + public static final int DW_ABBREV_CODE_static_field_location = 13; + public static final int DW_ABBREV_CODE_array_layout = 14; + public static final int DW_ABBREV_CODE_array_pointer = 15; + public static final int DW_ABBREV_CODE_interface_layout = 16; + public static final int DW_ABBREV_CODE_interface_pointer = 17; + public static final int DW_ABBREV_CODE_indirect_layout = 18; + public static final int DW_ABBREV_CODE_indirect_pointer = 19; /* Level 2 DIEs. */ - public static final int DW_ABBREV_CODE_method_declaration = 17; - public static final int DW_ABBREV_CODE_method_declaration_static = 18; - public static final int DW_ABBREV_CODE_field_declaration1 = 19; - public static final int DW_ABBREV_CODE_field_declaration2 = 20; - public static final int DW_ABBREV_CODE_field_declaration3 = 21; - public static final int DW_ABBREV_CODE_field_declaration4 = 22; - public static final int DW_ABBREV_CODE_class_constant = 23; - public static final int DW_ABBREV_CODE_header_field = 24; - public static final int DW_ABBREV_CODE_array_data_type = 25; - public static final int DW_ABBREV_CODE_super_reference = 26; - public static final int DW_ABBREV_CODE_interface_implementor = 27; + public static final int DW_ABBREV_CODE_method_declaration = 20; + public static final int DW_ABBREV_CODE_method_declaration_static = 21; + public static final int DW_ABBREV_CODE_field_declaration1 = 22; + public static final int DW_ABBREV_CODE_field_declaration2 = 23; + public static final int DW_ABBREV_CODE_field_declaration3 = 24; + public static final int DW_ABBREV_CODE_field_declaration4 = 25; + public static final int DW_ABBREV_CODE_class_constant = 26; + public static final int DW_ABBREV_CODE_header_field = 27; + public static final int DW_ABBREV_CODE_array_data_type1 = 28; + public static final int DW_ABBREV_CODE_array_data_type2 = 29; + public static final int DW_ABBREV_CODE_array_subrange = 30; + public static final int DW_ABBREV_CODE_super_reference = 31; + public static final int DW_ABBREV_CODE_interface_implementor = 32; /* Level 2+K DIEs (where inline depth K >= 0) */ - public static final int DW_ABBREV_CODE_inlined_subroutine = 28; - public static final int DW_ABBREV_CODE_inlined_subroutine_with_children = 29; + public static final int DW_ABBREV_CODE_inlined_subroutine = 33; + public static final int DW_ABBREV_CODE_inlined_subroutine_with_children = 34; /* Level 2 DIEs. */ - public static final int DW_ABBREV_CODE_method_parameter_declaration1 = 30; - public static final int DW_ABBREV_CODE_method_parameter_declaration2 = 31; - public static final int DW_ABBREV_CODE_method_parameter_declaration3 = 32; - public static final int DW_ABBREV_CODE_method_local_declaration1 = 33; - public static final int DW_ABBREV_CODE_method_local_declaration2 = 34; + public static final int DW_ABBREV_CODE_method_parameter_declaration1 = 35; + public static final int DW_ABBREV_CODE_method_parameter_declaration2 = 36; + public static final int DW_ABBREV_CODE_method_parameter_declaration3 = 37; + public static final int DW_ABBREV_CODE_method_local_declaration1 = 38; + public static final int DW_ABBREV_CODE_method_local_declaration2 = 39; /* Level 3 DIEs. */ - public static final int DW_ABBREV_CODE_method_parameter_location1 = 35; - public static final int DW_ABBREV_CODE_method_parameter_location2 = 36; - public static final int DW_ABBREV_CODE_method_local_location1 = 37; - public static final int DW_ABBREV_CODE_method_local_location2 = 38; + public static final int DW_ABBREV_CODE_method_parameter_location1 = 40; + public static final int DW_ABBREV_CODE_method_parameter_location2 = 41; + public static final int DW_ABBREV_CODE_method_local_location1 = 42; + public static final int DW_ABBREV_CODE_method_local_location2 = 43; /* * Define all the Dwarf tags we need for our DIEs. @@ -124,8 +129,10 @@ public class DwarfDebugInfo extends DebugInfoBase { public static final int DW_TAG_pointer_type = 0x0f; public static final int DW_TAG_compile_unit = 0x11; public static final int DW_TAG_structure_type = 0x13; + public static final int DW_TAG_typedef = 0x16; public static final int DW_TAG_union_type = 0x17; public static final int DW_TAG_inheritance = 0x1c; + public static final int DW_TAG_subrange_type = 0x21; public static final int DW_TAG_base_type = 0x24; public static final int DW_TAG_constant = 0x27; public static final int DW_TAG_subprogram = 0x2e; @@ -152,6 +159,7 @@ public class DwarfDebugInfo extends DebugInfoBase { public static final int DW_AT_abstract_origin = 0x31; public static final int DW_AT_accessibility = 0x32; public static final int DW_AT_artificial = 0x34; + public static final int DW_AT_count = 0x37; public static final int DW_AT_data_member_location = 0x38; @SuppressWarnings("unused") public static final int DW_AT_decl_column = 0x39; public static final int DW_AT_decl_file = 0x3a; @@ -239,6 +247,7 @@ public class DwarfDebugInfo extends DebugInfoBase { public static final byte DW_ATE_signed = 0x5; public static final byte DW_ATE_signed_char = 0x6; public static final byte DW_ATE_unsigned = 0x7; + public static final byte DW_ATE_unsigned_char = 0x8; /* * CIE and FDE entries. @@ -573,6 +582,7 @@ private DwarfMethodProperties lookupMethodProperties(MethodEntry methodEntry) { } void setTypeIndex(TypeEntry typeEntry, int idx) { + assert idx >= 0; DwarfTypeProperties typeProperties = lookupTypeProperties(typeEntry); assert typeProperties.getTypeInfoIndex() == -1 || typeProperties.getTypeInfoIndex() == idx; typeProperties.setTypeInfoIndex(idx); @@ -589,6 +599,7 @@ int getTypeIndex(DwarfTypeProperties typeProperties) { } void setIndirectTypeIndex(TypeEntry typeEntry, int idx) { + assert idx >= 0; DwarfTypeProperties typeProperties = lookupTypeProperties(typeEntry); assert typeProperties.getIndirectTypeInfoIndex() == -1 || typeProperties.getIndirectTypeInfoIndex() == idx; typeProperties.setIndirectTypeInfoIndex(idx); @@ -605,6 +616,7 @@ int getIndirectTypeIndex(DwarfTypeProperties typeProperties) { } void setLayoutIndex(ClassEntry classEntry, int idx) { + assert idx >= 0 || idx == -1; DwarfClassProperties classProperties = lookupClassProperties(classEntry); assert classProperties.getTypeEntry() == classEntry; assert classProperties.layoutIndex == -1 || classProperties.layoutIndex == idx; @@ -620,6 +632,19 @@ int getLayoutIndex(ClassEntry classEntry) { } void setIndirectLayoutIndex(ClassEntry classEntry, int idx) { + // The layout index of a POINTER type is set to the type index of its referent. + // If the pointer type is generated before its referent that means it can be set + // with value -1 (unset) on the first sizing pass. The indirect layout will + // be reset to a positive offset on the second pass before it is used to write + // the referent of the pointer type. Hence the condition in the following assert. + assert idx >= 0 || idx == -1; + // Note however, that this possibility needs to be finessed when writing + // a foreign struct ADDRESS field of POINTER type (i.e. an embedded field). + // If the struct is generated before the POINTER type then the layout index will + // still be -1 during the second write pass when the field type needs to be + // written. This possibility is handled by typing the field using the typeIdx + // of the referent. the latter is guaranteed to have been set during the first pass. + DwarfClassProperties classProperties = lookupClassProperties(classEntry); assert classProperties.getTypeEntry() == classEntry; assert classProperties.indirectLayoutIndex == -1 || classProperties.indirectLayoutIndex == idx; 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 f456e7dca3c3..53e2cc007716 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 @@ -31,6 +31,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.stream.Stream; import org.graalvm.compiler.debug.DebugContext; @@ -40,6 +41,7 @@ import com.oracle.objectfile.debugentry.CompiledMethodEntry; 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.MethodEntry; @@ -69,9 +71,19 @@ public class DwarfInfoSectionImpl extends DwarfSectionImpl { * An info header section always contains a fixed number of bytes. */ private static final int DW_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 voidOffset; public DwarfInfoSectionImpl(DwarfDebugInfo dwarfSections) { super(dwarfSections); + // initialize to an invalid value + voidOffset = -1; } @Override @@ -267,6 +279,10 @@ public int writeVoidType(DebugContext context, PrimitiveTypeEntry primitiveTypeE * an indirect type. */ setIndirectTypeIndex(primitiveTypeEntry, pos); + // specially record void type offset for immediate use during first pass of info generation + // we need to use it as the base layout for foreign types + assert voidOffset == -1 || voidOffset == pos; + voidOffset = pos; int abbrevCode = DwarfDebugInfo.DW_ABBREV_CODE_void_type; log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode); pos = writeAbbrevCode(abbrevCode, buffer, pos); @@ -294,26 +310,63 @@ public int writeHeaderType(DebugContext context, HeaderTypeEntry headerTypeEntry pos = writeStrSectionOffset(name, buffer, pos); log(context, " [0x%08x] byte_size 0x%x", pos, size); pos = writeAttrData1(size, buffer, pos); - pos = writeHeaderFields(context, headerTypeEntry, buffer, pos); + pos = writeStructFields(context, headerTypeEntry.fields(), buffer, pos); /* * Write a terminating null attribute. */ return writeAttrNull(buffer, pos); } - private int writeHeaderFields(DebugContext context, HeaderTypeEntry headerTypeEntry, byte[] buffer, int p) { - return headerTypeEntry.fields().reduce(p, - (pos, fieldEntry) -> writeHeaderField(context, fieldEntry, buffer, pos), - (oldPos, newPos) -> newPos); + private int writeStructFields(DebugContext context, Stream fields, byte[] buffer, int p) { + Cursor cursor = new Cursor(p); + fields.forEach(fieldEntry -> { + cursor.set(writeStructField(context, fieldEntry, buffer, cursor.get())); + }); + return cursor.get(); } - private int writeHeaderField(DebugContext context, FieldEntry fieldEntry, byte[] buffer, int p) { + private int writeStructField(DebugContext context, FieldEntry fieldEntry, byte[] buffer, int p) { int pos = p; String fieldName = fieldEntry.fieldName(); TypeEntry valueType = fieldEntry.getValueType(); - /* use the indirect type for the field so pointers get translated */ - int valueTypeIdx = getIndirectTypeIndex(valueType); - log(context, " [0x%08x] header field", pos); + int valueTypeIdx; + if (fieldEntry.isEmbedded()) { + // the field type must be a foreign type + ForeignTypeEntry foreignValueType = (ForeignTypeEntry) valueType; + /* use the indirect layout type for the field */ + /* handle special case when the field is an array */ + int fieldSize = fieldEntry.getSize(); + int valueSize = foreignValueType.getSize(); + if (fieldEntry.getSize() != foreignValueType.getSize()) { + assert (fieldSize % valueSize == 0) : "embedded field size is not a multiple of value type size!"; + // declare a local array of the embedded type and use it as the value type + valueTypeIdx = pos; + pos = writeEmbeddedArrayDataType(context, foreignValueType, valueSize, fieldSize / valueSize, buffer, pos); + } else { + if (foreignValueType.isPointer()) { + TypeEntry pointerTo = foreignValueType.getPointerTo(); + assert pointerTo != null : "ADDRESS field pointer type must have a known target type"; + // type the array using the referent of the pointer type + // + // n.b it is critical for correctness to use the index of the referent rather + // than the layout type of the referring type even though the latter will + // (eventually) be set to the same value. the type index of the referent is + // guaranteed to be set on the first sizing pass before it is consumed here + // on the second writing pass. + // However, if this embedded struct field definition precedes the definition + // of the referring type and the latter precedes the definition of the + // referent type then the layout index of the referring type may still be unset + // at this point. + valueTypeIdx = getTypeIndex(pointerTo); + } else { + valueTypeIdx = getIndirectLayoutIndex(foreignValueType); + } + } + } else { + /* use the indirect type for the field so pointers get translated */ + valueTypeIdx = getIndirectTypeIndex(valueType); + } + log(context, " [0x%08x] struct field", pos); int abbrevCode = DwarfDebugInfo.DW_ABBREV_CODE_header_field; log(context, " [0x%08x] <2> Abbrev Number %d", pos, abbrevCode); pos = writeAbbrevCode(abbrevCode, buffer, pos); @@ -321,10 +374,10 @@ private int writeHeaderField(DebugContext context, FieldEntry fieldEntry, byte[] pos = writeStrSectionOffset(fieldName, buffer, pos); log(context, " [0x%08x] type 0x%x (%s)", pos, valueTypeIdx, valueType.getTypeName()); pos = writeInfoSectionOffset(valueTypeIdx, buffer, pos); - byte offset = (byte) fieldEntry.getOffset(); + short offset = (short) fieldEntry.getOffset(); int size = fieldEntry.getSize(); log(context, " [0x%08x] offset 0x%x (size 0x%x)", pos, offset, size); - pos = writeAttrData1(offset, buffer, pos); + pos = writeAttrData2(offset, buffer, pos); int modifiers = fieldEntry.getModifiers(); log(context, " [0x%08x] modifiers %s", pos, fieldEntry.getModifiersString()); return writeAttrAccessibility(modifiers, buffer, pos); @@ -353,6 +406,10 @@ private int writeInstanceClassInfo(DebugContext context, ClassEntry classEntry, InterfaceClassEntry interfaceClassEntry = (InterfaceClassEntry) classEntry; pos = writeInterfaceLayout(context, interfaceClassEntry, buffer, pos); pos = writeInterfaceType(context, interfaceClassEntry, buffer, pos); + } else if (classEntry.isForeign()) { + ForeignTypeEntry foreignTypeEntry = (ForeignTypeEntry) classEntry; + pos = writeForeignLayout(context, foreignTypeEntry, buffer, pos); + pos = writeForeignType(context, foreignTypeEntry, buffer, pos); } else { pos = writeClassLayout(context, classEntry, buffer, pos); pos = writeClassType(context, classEntry, buffer, pos); @@ -499,6 +556,7 @@ private int writeClassLayout(DebugContext context, ClassEntry classEntry, byte[] */ pos = writeAttrNull(buffer, pos); } else { + log(context, " [0x%08x] setIndirectLayoutIndex %s 0x%x", pos, classEntry.getTypeName(), pos); setIndirectLayoutIndex(classEntry, layoutIndex); } @@ -848,6 +906,169 @@ private int writeInterfaceImplementor(DebugContext context, ClassEntry classEntr return pos; } + private int writeForeignLayout(DebugContext context, ForeignTypeEntry foreignTypeEntry, byte[] buffer, int p) { + int pos = p; + int size = foreignTypeEntry.getSize(); + int layoutOffset = pos; + 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.isIntegral()) { + // 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 *' + layoutOffset = voidOffset; + String referentName = "void"; + if (foreignTypeEntry.isPointer()) { + TypeEntry pointerTo = foreignTypeEntry.getPointerTo(); + if (pointerTo != null) { + layoutOffset = getTypeIndex(foreignTypeEntry.getPointerTo()); + referentName = foreignTypeEntry.getTypeName(); + } + } + log(context, " [0x%08x] foreign pointer type %s referent 0x%x (%s)", pos, foreignTypeEntry.getTypeName(), layoutOffset, referentName); + } + setLayoutIndex(foreignTypeEntry, layoutOffset); + + /* + * Write declarations for methods of the foreign types as functions + * + * n.b. these appear as standalone declarations rather than as children of a class layout + * DIE so we don't need a terminating attribute. + */ + pos = writeMethodDeclarations(context, foreignTypeEntry, buffer, pos); + /* + * We don't need an indirect type because foreign pointers are never compressed + */ + setIndirectLayoutIndex(foreignTypeEntry, layoutOffset); + + return pos; + } + + private int writeForeignStructLayout(DebugContext context, ForeignTypeEntry foreignTypeEntry, int size, byte[] buffer, int p) { + int pos = p; + log(context, " [0x%08x] foreign struct type for %s", pos, foreignTypeEntry.getTypeName()); + int abbrevCode = DwarfDebugInfo.DW_ABBREV_CODE_foreign_struct; + log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + String typedefName = foreignTypeEntry.getTypedefName(); + if (typedefName == null) { + typedefName = "_" + foreignTypeEntry.getTypeName(); + verboseLog(context, " [0x%08x] using synthetic typedef name %s", pos, typedefName); + } + if (typedefName.startsWith("struct ")) { + // log this before correcting it so we have some hope of clearing it up + log(context, " [0x%08x] typedefName includes redundant keyword struct %s", pos, typedefName); + typedefName = typedefName.substring("struct ".length()); + } + typedefName = uniqueDebugString(typedefName); + log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(typedefName), typedefName); + pos = writeStrSectionOffset(typedefName, buffer, pos); + 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(); + if (parent != null) { + int parentOffset = getLayoutIndex(parent); + pos = writeSuperReference(context, parentOffset, parent.getTypedefName(), buffer, pos); + } + pos = writeStructFields(context, foreignTypeEntry.fields(), 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) { + int pos = p; + log(context, " [0x%08x] foreign primitive word type for %s", pos, foreignTypeEntry.getTypeName()); + /* Record the location of this type entry. */ + int abbrevCode = DwarfDebugInfo.DW_ABBREV_CODE_primitive_type; + log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + assert size >= 0; + byte byteSize = (byte) (size > 0 ? size : dwarfSections.pointerSize()); + log(context, " [0x%08x] byte_size %d", pos, byteSize); + pos = writeAttrData1(byteSize, buffer, pos); + byte bitCount = (byte) (byteSize * 8); + 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 + byte encoding = (isSigned ? DwarfDebugInfo.DW_ATE_signed : DwarfDebugInfo.DW_ATE_unsigned); + log(context, " [0x%08x] encoding 0x%x", pos, encoding); + pos = writeAttrData1(encoding, buffer, pos); + String name = uniqueDebugString(integralTypeName(byteSize, 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) { + int pos = p; + log(context, " [0x%08x] foreign primitive integral type for %s", pos, foreignTypeEntry.getTypeName()); + /* Record the location of this type entry. */ + int abbrevCode = DwarfDebugInfo.DW_ABBREV_CODE_primitive_type; + log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + assert size > 0; + byte byteSize = (byte) size; + log(context, " [0x%08x] byte_size %d", pos, byteSize); + pos = writeAttrData1(byteSize, buffer, pos); + byte bitCount = (byte) (byteSize * 8); + 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 + byte encoding = (isSigned ? DwarfDebugInfo.DW_ATE_signed : DwarfDebugInfo.DW_ATE_unsigned); + log(context, " [0x%08x] encoding 0x%x", pos, encoding); + pos = writeAttrData1(encoding, buffer, pos); + String name = uniqueDebugString(integralTypeName(byteSize, 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) { + int pos = p; + log(context, " [0x%08x] foreign primitive float type for %s", pos, foreignTypeEntry.getTypeName()); + /* Record the location of this type entry. */ + int abbrevCode = DwarfDebugInfo.DW_ABBREV_CODE_primitive_type; + log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + assert size > 0; + byte byteSize = (byte) size; + log(context, " [0x%08x] byte_size %d", pos, byteSize); + pos = writeAttrData1(byteSize, buffer, pos); + byte bitCount = (byte) (byteSize * 8); + log(context, " [0x%08x] bitCount %d", pos, bitCount); + pos = writeAttrData1(bitCount, buffer, pos); + // treat the layout as a float of the relevant size + byte encoding = DwarfDebugInfo.DW_ATE_float; + log(context, " [0x%08x] encoding 0x%x", pos, encoding); + pos = writeAttrData1(encoding, buffer, pos); + String name = uniqueDebugString(size == 4 ? "float" : (size == 8 ? "double" : "long double")); + log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); + return writeStrSectionOffset(name, buffer, pos); + } + + private static String integralTypeName(int byteSize, boolean isSigned) { + assert (byteSize & (byteSize - 1)) == 0 : "expecting a power of 2!"; + StringBuilder stringBuilder = new StringBuilder(); + if (!isSigned) { + stringBuilder.append('u'); + } + stringBuilder.append("int"); + stringBuilder.append(8 * byteSize); + stringBuilder.append("_t"); + return stringBuilder.toString(); + } + private int writeClassType(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { int pos = p; @@ -922,6 +1143,45 @@ private int writeInterfaceType(DebugContext context, InterfaceClassEntry interfa return pos; } + private int writeForeignType(DebugContext context, ForeignTypeEntry foreignTypeEntry, byte[] buffer, int p) { + int pos = p; + int layoutOffset = getLayoutIndex(foreignTypeEntry); + + // Unlike with Java we use the Java name for the pointer type rather than the + // underlying base type, or rather for a typedef that targets the pointer type. + // That ensures that e.g. CCharPointer is a typedef for char*. + + /* Define a pointer type referring to the base type */ + int refTypeIdx = pos; + log(context, " [0x%08x] foreign pointer type", pos); + int abbrevCode = DwarfDebugInfo.DW_ABBREV_CODE_foreign_pointer; + log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode); + 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, layoutOffset); + pos = writeInfoSectionOffset(layoutOffset, buffer, pos); + + /* Define a typedef for the layout type using the Java name. */ + int typedefIdx = pos; + log(context, " [0x%08x] foreign typedef", pos); + abbrevCode = DwarfDebugInfo.DW_ABBREV_CODE_foreign_typedef; + log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + String name = uniqueDebugString(foreignTypeEntry.getTypeName()); + log(context, " [0x%08x] name %s", pos, name); + pos = writeStrSectionOffset(name, buffer, pos); + log(context, " [0x%08x] type 0x%x", pos, refTypeIdx); + pos = writeInfoSectionOffset(refTypeIdx, buffer, pos); + + setTypeIndex(foreignTypeEntry, typedefIdx); + // foreign pointers are never stored compressed so don't need a separate indirect type + setIndirectTypeIndex(foreignTypeEntry, typedefIdx); + + return pos; + } + private int writeStaticFieldLocations(DebugContext context, byte[] buffer, int p) { Cursor cursor = new Cursor(p); instanceClassStream().forEach(classEntry -> { @@ -1071,12 +1331,10 @@ private int writeIndirectArrayLayout(DebugContext context, ArrayTypeEntry arrayT private int writeArrayDataType(DebugContext context, TypeEntry elementType, byte[] buffer, int p) { int pos = p; log(context, " [0x%08x] array element data type", pos); - int abbrevCode = DwarfDebugInfo.DW_ABBREV_CODE_array_data_type; + int abbrevCode = DwarfDebugInfo.DW_ABBREV_CODE_array_data_type1; log(context, " [0x%08x] <2> Abbrev Number %d", pos, abbrevCode); pos = writeAbbrevCode(abbrevCode, buffer, pos); - int size = (elementType.isPrimitive() ? elementType.getSize() : 8); - log(context, " [0x%08x] byte_size 0x%x", pos, size); - pos = writeAttrData1((byte) size, buffer, pos); + // Java arrays don't have a fixed byte_size String elementTypeName = elementType.getTypeName(); /* use the indirect type for the element type so pointers get translated */ int elementTypeIdx = getIndirectTypeIndex(elementType); @@ -1085,6 +1343,50 @@ private int writeArrayDataType(DebugContext context, TypeEntry elementType, byte return pos; } + private int writeEmbeddedArrayDataType(DebugContext context, ForeignTypeEntry foreignValueType, int valueSize, int arraySize, byte[] buffer, int p) { + int pos = p; + log(context, " [0x%08x] embedded array element data type", pos); + int abbrevCode = DwarfDebugInfo.DW_ABBREV_CODE_array_data_type2; + log(context, " [0x%08x] <2> Abbrev Number %d", pos, abbrevCode); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + // Foreign arrays have a fixed byte_size + int size = arraySize * valueSize; + log(context, " [0x%08x] byte_size 0x%x", pos, size); + pos = writeAttrData4(size, buffer, pos); + String elementTypeName = foreignValueType.getTypeName(); + int elementTypeIdx; + if (foreignValueType.isPointer()) { + TypeEntry pointerTo = foreignValueType.getPointerTo(); + assert pointerTo != null : "ADDRESS field pointer type must have a known target type"; + // type the array using the referent of the pointer type + // + // n.b it is critical for correctness to use the index of the referent rather than + // the layout type of the referring type even though the latter will (eventually) + // be set to the same value. the type index of the referent is guaranteed to be set + // on the first sizing pass before it is consumed here on the second writing pass. + // However, if this embedded struct field definition precedes the definition of the + // referring type and the latter precedes the definition of the referent type then + // the layout index of the referring type may still be unset at this point. + elementTypeIdx = getTypeIndex(pointerTo); + } else { + // type the array using the layout type + elementTypeIdx = getIndirectLayoutIndex(foreignValueType); + } + log(context, " [0x%08x] type idx 0x%x (%s)", pos, elementTypeIdx, elementTypeName); + pos = writeInfoSectionOffset(elementTypeIdx, buffer, pos); + // write subrange child DIE + log(context, " [0x%08x] embedded array element range", pos); + abbrevCode = DwarfDebugInfo.DW_ABBREV_CODE_array_subrange; + log(context, " [0x%08x] <3> Abbrev Number %d", pos, abbrevCode); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + log(context, " [0x%08x] count 0x%x", pos, arraySize); + pos = writeAttrData4(arraySize, buffer, pos); + /* + * Write a terminating null attribute. + */ + return writeAttrNull(buffer, pos); + } + private int writeArrayElementField(DebugContext context, int offset, int arrayDataTypeIdx, byte[] buffer, int p) { int pos = p; log(context, " [0x%08x] array element data field", pos); @@ -1098,7 +1400,7 @@ private int writeArrayElementField(DebugContext context, int offset, int arrayDa pos = writeInfoSectionOffset(arrayDataTypeIdx, buffer, pos); int size = 0; log(context, " [0x%08x] offset 0x%x (size 0x%x)", pos, offset, size); - pos = writeAttrData1((byte) offset, buffer, pos); + pos = writeAttrData2((short) offset, buffer, pos); int modifiers = Modifier.PUBLIC; log(context, " [0x%08x] modifiers %s", pos, "public"); return writeAttrAccessibility(modifiers, buffer, pos); 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 d9a029ee5dc9..54672e011e43 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 @@ -713,6 +713,15 @@ protected HeaderTypeEntry headerType() { return dwarfSections.lookupHeaderType(); } + /** + * Retrieve the entry for the void type. + * + * @return the entry for the void type. + */ + protected TypeEntry voidType() { + return dwarfSections.lookupVoidType(); + } + /** * Retrieve a stream of all instance classes, including interfaces and enums, notified via the * DebugTypeInfo API. @@ -783,7 +792,7 @@ protected ClassEntry lookupObjectClass() { protected int getTypeIndex(TypeEntry typeEntry) { if (!contentByteArrayCreated()) { - return 0; + return -1; } return dwarfSections.getTypeIndex(typeEntry); } 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 ddd69d76b61c..fc6901ada56e 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 @@ -123,7 +123,9 @@ CVTypeRecord buildType(TypeEntry typeEntry) { case ARRAY: case ENUM: case INSTANCE: - case INTERFACE: { + case INTERFACE: + // TODO continue treat foreign types as interfaces/classes but fix this later + case FOREIGN: { typeRecord = buildStructureTypeEntry((StructureTypeEntry) typeEntry); break; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageBFDNameProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageBFDNameProvider.java index f57880f2d59d..8a79dee2f8eb 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageBFDNameProvider.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageBFDNameProvider.java @@ -24,9 +24,11 @@ */ package com.oracle.svm.hosted.image; +import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.UniqueShortNameProvider; 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; @@ -58,8 +60,11 @@ */ class NativeImageBFDNameProvider implements UniqueShortNameProvider { + private NativeLibraries nativeLibs; + NativeImageBFDNameProvider(List ignore) { this.ignoredLoaders = ignore; + this.nativeLibs = null; } @Override @@ -251,6 +256,10 @@ public String bfdMangle(String loaderName, ResolvedJavaType declaringClass, Stri * 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 @@ -305,7 +314,17 @@ public String bfdMangle(String loaderName, ResolvedJavaType declaringClass, Stri * * The bfd Java demangler recognises this special pseudo template and translates it back into * the expected Java form i.e. decode(JArray) -> concatenate(decode(XXX), "[]"). However, - * the default bfd C++ demangler wil not perform this translation. + * 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) + * -> + * 28org.graalvm.word.PointerBaseP16java.lang.String + * + * + * Note that no type is emitted for the implicit 'this' argument * * Substitutions: * @@ -352,6 +371,17 @@ public String bfdMangle(Member m) { return new BFDMangler(this).mangle(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. + * + * @param nativeLibs the current native libraries singleton. + */ + public void setNativeLibs(NativeLibraries nativeLibs) { + this.nativeLibs = nativeLibs; + } + private static class BFDMangler { final NativeImageBFDNameProvider nameProvider; final StringBuilder sb; @@ -516,7 +546,9 @@ private void mangleType(ResolvedJavaType type) { sb.append('P'); mangleArrayType(type); } else { - sb.append('P'); + if (nameProvider.needsPointerPrefix(type)) { + sb.append('P'); + } mangleClassName(nameProvider.classLoaderNameAndId(type), type.toJavaName()); } } @@ -679,4 +711,25 @@ private int prefixIdx(String prefix) { return prefixes.indexOf(prefix); } } + + /** + * 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) { + 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; + } + } 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 c71282144ad0..f44aa4c290af 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 @@ -52,6 +52,7 @@ @AutomaticallyRegisteredFeature @SuppressWarnings("unused") class NativeImageDebugInfoFeature implements InternalFeature { + private NativeImageBFDNameProvider bfdNameProvider; @Override public boolean isInConfiguration(IsInConfigurationAccess access) { @@ -80,11 +81,23 @@ public void afterRegistration(AfterRegistrationAccess access) { assert imageLoaderParent == appLoader.getParent(); // ensure the mangle ignores prefix generation for Graal loaders List ignored = List.of(systemLoader, imageLoaderParent, appLoader, imageLoader); - ImageSingletons.add(UniqueShortNameProvider.class, new NativeImageBFDNameProvider(ignored)); + bfdNameProvider = new NativeImageBFDNameProvider(ignored); + ImageSingletons.add(UniqueShortNameProvider.class, bfdNameProvider); } } } + @Override + public void beforeAnalysis(BeforeAnalysisAccess access) { + /* + * Make the name provider aware of the native libs + */ + if (bfdNameProvider != null) { + var accessImpl = (FeatureImpl.BeforeAnalysisAccessImpl) access; + bfdNameProvider.setNativeLibs(accessImpl.getNativeLibraries()); + } + } + @Override @SuppressWarnings("try") public void beforeImageWrite(BeforeImageWriteAccess access) { @@ -94,7 +107,7 @@ public void beforeImageWrite(BeforeImageWriteAccess access) { var accessImpl = (FeatureImpl.BeforeImageWriteAccessImpl) access; var image = accessImpl.getImage(); var debugContext = new DebugContext.Builder(HostedOptionValues.singleton(), new GraalDebugHandlersFactory(GraalAccess.getOriginalSnippetReflection())).build(); - DebugInfoProvider provider = new NativeImageDebugInfoProvider(debugContext, image.getCodeCache(), image.getHeap(), accessImpl.getHostedMetaAccess()); + DebugInfoProvider provider = new NativeImageDebugInfoProvider(debugContext, image.getCodeCache(), image.getHeap(), image.getNativeLibs(), accessImpl.getHostedMetaAccess()); var objectFile = image.getObjectFile(); objectFile.installDebugInfo(provider); 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 68f59e06e495..96f870d2bfa9 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 @@ -27,6 +27,9 @@ import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFrameSizeChange.Type.CONTRACT; import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFrameSizeChange.Type.EXTEND; +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.FileSystems; @@ -44,6 +47,18 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +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.util.ClassUtil; import org.graalvm.collections.EconomicMap; import org.graalvm.compiler.code.CompilationResult; import org.graalvm.compiler.core.common.CompressEncoding; @@ -51,7 +66,6 @@ import org.graalvm.compiler.graph.NodeSourcePosition; import org.graalvm.compiler.java.StableMethodNameFormatter; import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.word.WordBase; import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider; import com.oracle.graal.pointsto.infrastructure.WrappedJavaMethod; @@ -110,6 +124,9 @@ import jdk.vm.ci.meta.ResolvedJavaType; import jdk.vm.ci.meta.Signature; import jdk.vm.ci.meta.Value; +import org.graalvm.nativeimage.c.struct.CPointerTo; +import org.graalvm.nativeimage.c.struct.RawPointerTo; +import org.graalvm.word.WordBase; /** * Implementation of the DebugInfoProvider API interface that allows type, code and heap data info @@ -119,6 +136,7 @@ class NativeImageDebugInfoProvider implements DebugInfoProvider { private final DebugContext debugContext; private final NativeImageCodeCache codeCache; private final NativeImageHeap heap; + private final NativeLibraries nativeLibs; boolean useHeapBase; int compressShift; int tagsMask; @@ -128,15 +146,16 @@ class NativeImageDebugInfoProvider implements DebugInfoProvider { int primitiveStartOffset; int referenceStartOffset; private final Set allOverrides; - HostedType wordBaseType; HostedType hubType; - HashMap javaKindToHostedType; + private final HostedType wordBaseType; + final HashMap javaKindToHostedType; - NativeImageDebugInfoProvider(DebugContext debugContext, NativeImageCodeCache codeCache, NativeImageHeap heap, HostedMetaAccess metaAccess) { + NativeImageDebugInfoProvider(DebugContext debugContext, NativeImageCodeCache codeCache, NativeImageHeap heap, NativeLibraries nativeLibs, HostedMetaAccess metaAccess) { super(); this.debugContext = debugContext; this.codeCache = codeCache; this.heap = heap; + this.nativeLibs = nativeLibs; ObjectHeader objectHeader = Heap.getHeap().getObjectHeader(); ObjectInfo primitiveFields = heap.getObjectInfo(StaticFieldsSupport.getStaticPrimitiveFields()); ObjectInfo objectFields = heap.getObjectInfo(StaticFieldsSupport.getStaticObjectFields()); @@ -161,8 +180,8 @@ class NativeImageDebugInfoProvider implements DebugInfoProvider { .flatMap(m -> Arrays.stream(m.getImplementations()) .filter(Predicate.not(m::equals))) .collect(Collectors.toSet()); - wordBaseType = metaAccess.lookupJavaType(WordBase.class); hubType = metaAccess.lookupJavaType(Class.class); + wordBaseType = metaAccess.lookupJavaType(WordBase.class); javaKindToHostedType = initJavaKindToHostedTypes(metaAccess); } @@ -247,7 +266,6 @@ static ObjectLayout getObjectLayout() { * 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. */ @@ -615,6 +633,11 @@ public int size() { return size; } + @Override + public boolean isEmbedded() { + return false; + } + @Override public int modifiers() { return modifiers; @@ -678,11 +701,6 @@ public DebugTypeKind typeKind() { return DebugTypeKind.INSTANCE; } - @Override - public int headerSize() { - return getObjectLayout().getFirstFieldOffset(); - } - @Override public String loaderName() { @@ -776,6 +794,11 @@ public int size() { return getObjectLayout().sizeInBytes(field.getType().getStorageKind()); } + @Override + public boolean isEmbedded() { + return false; + } + @Override public int modifiers() { ResolvedJavaField targetField = field.wrapped.wrapped; @@ -813,6 +836,199 @@ public DebugTypeKind typeKind() { } } + private class NativeImageDebugForeignTypeInfo extends NativeImageDebugInstanceTypeInfo implements DebugForeignTypeInfo { + + ElementInfo elementInfo; + + NativeImageDebugForeignTypeInfo(HostedType hostedType) { + this(hostedType, nativeLibs.findElementInfo(hostedType)); + } + + NativeImageDebugForeignTypeInfo(HostedType hostedType, ElementInfo elementInfo) { + super(hostedType); + assert isForeignWordType(hostedType); + this.elementInfo = elementInfo; + assert verifyElementInfo() : "unexpected element info kind"; + } + + 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; + } + } + + @Override + public DebugTypeKind typeKind() { + return DebugTypeKind.FOREIGN; + } + + @Override + public Stream 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); + } + + @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; + } + + @Override + public boolean isWord() { + return !isForeignPointerType(hostedType); + } + + @Override + public boolean isStruct() { + return elementInfo instanceof StructInfo; + } + + @Override + public boolean isPointer() { + if (elementInfo != null && elementInfo instanceof SizableInfo) { + return ((SizableInfo) elementInfo).getKind() == ElementKind.POINTER; + } else { + return false; + } + } + + @Override + public boolean isIntegral() { + if (elementInfo != null && elementInfo instanceof SizableInfo) { + return ((SizableInfo) elementInfo).getKind() == ElementKind.INTEGER; + } else { + return false; + } + } + + @Override + public boolean isFloat() { + if (elementInfo != null) { + return ((SizableInfo) elementInfo).getKind() == ElementKind.FLOAT; + } else { + return false; + } + } + + @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())); + } + + @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); + } + } + } + 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 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.getSizeInfo().getProperty(); + } + + @Override + public int offset() { + return structFieldInfo.getOffsetInfo().getProperty(); + } + + @Override + public String name() { + return structFieldInfo.getName(); + } + + @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 boolean isEmbedded() { + // this is true when the field has an ADDRESS accessor type + return fieldTypeIsEmbedded(structFieldInfo); + } + + @Override + public int modifiers() { + return 0; + } + } + private class NativeImageDebugArrayTypeInfo extends NativeImageDebugTypeInfo implements DebugArrayTypeInfo { HostedArrayClass arrayClass; List fieldInfos; @@ -911,19 +1127,273 @@ public int flags() { } } + @SuppressWarnings("try") private NativeImageDebugTypeInfo createDebugTypeInfo(HostedType hostedType) { - 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); + 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).toString()); + 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 ? "" : "")); + 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).toString()); + assert hostedType.isInterface(); + boolean isIncomplete = structInfo.isIncomplete(); + if (isIncomplete) { + debugContext.log(""); + } 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).toString()); + 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 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); + } + + private 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)); + } + + private int structFieldComparator(StructFieldInfo f1, StructFieldInfo f2) { + int offset1 = f1.getOffsetInfo().getProperty(); + int offset2 = f2.getOffsetInfo().getProperty(); + return offset1 - offset2; + } + + private static int elementSize(ElementInfo elementInfo) { + if (elementInfo == null || !(elementInfo instanceof SizableInfo)) { + return 0; + } + if (elementInfo instanceof StructInfo && ((StructInfo) elementInfo).isIncomplete()) { + return 0; + } + Integer size = ((SizableInfo) elementInfo).getSizeInfo().getProperty(); + assert size != null; + return size; + } + + private static String elementName(ElementInfo elementInfo) { + if (elementInfo == null) { + return ""; + } else { + return elementInfo.getName(); + } + } + + private static ElementKind elementKind(SizableInfo sizableInfo) { + return sizableInfo.getKind(); + } + + private Stream 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 { - throw new RuntimeException("Unknown type kind " + hostedType.getName()); + return Stream.empty(); + } + } + + private 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; + } + + 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 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 void formatPropertyInfo(PropertyInfo propertyInfo, StringBuilder stringBuilder) { + stringBuilder.append(propertyInfo.getProperty()); + } + + private static void indentElementInfo(StringBuilder stringBuilder, int indent) { + for (int i = 0; i <= indent; i++) { + stringBuilder.append(" "); } } @@ -1136,7 +1606,7 @@ private List createParamInfo(ResolvedJavaMethod method, int line ResolvedJavaType ownerType = method.getDeclaringClass(); if (!method.isStatic()) { JavaKind kind = ownerType.getJavaKind(); - JavaKind storageKind = isPseudoObjectType(ownerType, ownerType) ? JavaKind.Long : kind; + 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(); @@ -1146,7 +1616,7 @@ private List createParamInfo(ResolvedJavaMethod method, int line String name = (local != null ? local.getName() : "__" + i); ResolvedJavaType paramType = (ResolvedJavaType) signature.getParameterType(i, null); JavaKind kind = paramType.getJavaKind(); - JavaKind storageKind = isPseudoObjectType(paramType, ownerType) ? JavaKind.Long : kind; + JavaKind storageKind = isForeignWordType(paramType, ownerType) ? JavaKind.Long : kind; paramInfos.add(new NativeImageDebugLocalInfo(name, storageKind, paramType, slot, line)); slot += kind.getSlotCount(); } @@ -1154,16 +1624,37 @@ private List createParamInfo(ResolvedJavaMethod method, int line } /** - * Identify a pseudo-object Java type which is used only to model a memory word, pointer or - * foreign opaque type. - * + * 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 is a pseudo object type + * @return true if the type models a foreign memory word or pointer type */ - private boolean isPseudoObjectType(JavaType type, ResolvedJavaType accessingType) { - ResolvedJavaType resolvedJavaType = type.resolve(accessingType); - return (wordBaseType.isAssignableFrom(resolvedJavaType)); + private 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 + */ + private 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 + */ + private boolean isForeignPointerType(HostedType hostedType) { + // unwrap because native libs operates on the analysis type universe + return nativeLibs.isPointerBase(hostedType.getWrapped()); } private static boolean isIntegralKindPromotion(JavaKind promoted, JavaKind original) { @@ -1458,7 +1949,7 @@ public void apply(FrameNode node, Object... args) { /** * Report whether a call node has any children. - * + * * @param callNode the node to check * @return true if it has any children otherwise false. */ @@ -1475,7 +1966,7 @@ public void apply(FrameNode node, Object... args) { /** * Create a location info record for a leaf subrange. - * + * * @param node is a simple FrameNode * @return the newly created location info record */ @@ -1489,7 +1980,7 @@ private NativeImageDebugLocationInfo createLeafLocationInfo(FrameNode node, Nati /** * 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 */ @@ -1505,7 +1996,7 @@ private NativeImageDebugLocationInfo createCallLocationInfo(CallNode callNode, N * 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 @@ -1557,7 +2048,7 @@ private boolean isBadLeaf(FrameNode node, NativeImageDebugLocationInfo callerLoc /** * 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 */ @@ -1569,7 +2060,7 @@ private boolean skipPos(BytecodePosition pos) { * 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. */ @@ -1585,7 +2076,7 @@ private BytecodePosition realCaller(CallNode node) { * 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. */ @@ -1597,9 +2088,9 @@ private boolean skipNode(FrameNode node) { * 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. */ @@ -1624,7 +2115,7 @@ private boolean embedWithChildren(CallNode parent, FrameNode firstChild) { /** * 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 @@ -1650,7 +2141,7 @@ private NativeImageDebugLocationInfo tryMerge(NativeImageDebugLocationInfo newLe /** * 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 @@ -1662,7 +2153,7 @@ private void initMerge(NativeImageDebugLocationInfo lastLeaf, Object[] args) { /** * 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 */ @@ -1813,7 +2304,7 @@ private List initLocalInfoList(BytecodePosition bcpos, int // only add the local if the kinds match if ((storageKind == kind) || isIntegralKindPromotion(storageKind, kind) || - (isPseudoObjectType(type, ownerType) && kind == JavaKind.Object && storageKind == JavaKind.Long)) { + (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()); @@ -1836,7 +2327,7 @@ private List initSyntheticInfoList(ParamLocationProducer lo if (!method.isStatic()) { String name = "this"; JavaKind kind = ownerType.getJavaKind(); - JavaKind storageKind = isPseudoObjectType(ownerType, ownerType) ? JavaKind.Long : kind; + JavaKind storageKind = isForeignWordType(ownerType, ownerType) ? JavaKind.Long : kind; assert kind == JavaKind.Object : "must be an object"; NativeImageDebugLocalValue value = locProducer.nextLocation(kind); debugContext.log(DebugContext.DETAILED_LEVEL, "locals[%d] %s type %s slot %d", localIdx, name, ownerType.getName(), slot); @@ -1850,7 +2341,7 @@ private List initSyntheticInfoList(ParamLocationProducer lo String name = (local != null ? local.getName() : "__" + i); ResolvedJavaType paramType = (ResolvedJavaType) signature.getParameterType(i, ownerType); JavaKind kind = paramType.getJavaKind(); - JavaKind storageKind = isPseudoObjectType(paramType, ownerType) ? JavaKind.Long : kind; + JavaKind storageKind = isForeignWordType(paramType, ownerType) ? JavaKind.Long : kind; NativeImageDebugLocalValue value = locProducer.nextLocation(kind); 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); @@ -1934,7 +2425,7 @@ private int localsSize() { * 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 */ diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/CInterfaceDebugTestDirectives.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/CInterfaceDebugTestDirectives.java new file mode 100644 index 000000000000..1f8b60223542 --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/CInterfaceDebugTestDirectives.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023, 2023, 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; + +import java.util.List; + +import org.graalvm.nativeimage.c.CContext; + +public class CInterfaceDebugTestDirectives implements CContext.Directives { + + @Override + public boolean isInConfiguration() { + return "true".equals(System.getProperty("buildDebugInfoTestExample")); + } + + @Override + public List getHeaderFiles() { + return List.of(""); + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/CStructTests.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/CStructTests.java new file mode 100644 index 000000000000..8b0d23f22922 --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/CStructTests.java @@ -0,0 +1,230 @@ +/* + * Copyright (c) 2023, 2023, 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; + +import org.graalvm.nativeimage.UnmanagedMemory; +import org.graalvm.nativeimage.c.CContext; +import org.graalvm.nativeimage.c.struct.CField; +import org.graalvm.nativeimage.c.struct.CFieldAddress; +import org.graalvm.nativeimage.c.struct.CPointerTo; +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.CTypeConversion; +import org.graalvm.nativeimage.c.type.CTypeConversion.CCharPointerHolder; +import org.graalvm.word.PointerBase; +import org.graalvm.word.SignedWord; + +import com.oracle.svm.core.NeverInline; + +@CContext(CInterfaceDebugTestDirectives.class) +public class CStructTests { + @CPointerTo(nameOfCType = "int") + public interface MyCIntPointer extends PointerBase { + void write(int value); + + void write(int index, int value); + + void write(SignedWord index, int value); + + MyCIntPointer addressOf(int index); + + MyCIntPointer addressOf(SignedWord index); + } + + // Checkstyle: stop + @CStruct("struct weird") + interface Weird extends PointerBase { + + @CField + short getf_short(); + + @CField + void setf_short(short value); + + @CField + int getf_int(); + + @CField + void setf_int(int value); + + @CField + long getf_long(); + + @CField + void setf_long(long value); + + @CField + float getf_float(); + + @CField + void setf_float(float value); + + @CField + double getf_double(); + + @CField + void setf_double(double value); + + @CFieldAddress + MyCIntPointer addressOfa_int(); + + @CFieldAddress + CCharPointer addressOfa_char(); + + Weird getElement(int index); + } + + // Checkstyle: resume + + /*- + checkedBreakpoint('com.oracle.svm.enterprise.debug.test.CStructTests.free', [ + {'frame#' : 1, 'eval' : [ + ('wd', [ + chkVal('CStruct com.oracle.svm.enterprise.debug.test.CStructTests$Weird = {' + ' a_char = byte[12] = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, ...},' + ' a_int = int[8] = {0, 1, 2, 3, 4, 5, 6, 7},' + ' f_double = 4.5999999999999996,' + ' f_float = 4.5,' + ' f_int = 43,' + ' f_long = 44,' + ' f_short = 42' + '}', castFn=str) + ]) + ]} + ]) + */ + public void weird() { + Weird wd = UnmanagedMemory.malloc(SizeOf.get(Weird.class)); + + wd.setf_short((short) 42); + wd.setf_int(43); + wd.setf_long(44); + wd.setf_float(4.5F); + wd.setf_double(4.6); + + final int intArraySize = 8; + MyCIntPointer pi = wd.addressOfa_int(); + for (int i = 0; i < intArraySize; i++) { + pi.write(i, i); + } + + final String text = "0123456789AB"; + try (CCharPointerHolder pin = CTypeConversion.toCString(text)) { + CCharPointer cstring = pin.get(); + long size = text.length(); + + CCharPointer field = wd.addressOfa_char(); + for (int i = 0; i < size; i++) { + field.write(i, cstring.read(i)); + } + + free(wd); + } + } + + // Checkstyle: stop + @CStruct("struct simple_struct") + interface SimpleStruct extends PointerBase { + @CField("first") + void setFirst(int value); + + @CField("second") + void setSecond(int value); + }// Checkstyle: resume + + // Checkstyle: stop + @CStruct("struct simple_struct2") + interface SimpleStruct2 extends PointerBase { + @CField("alpha") + void setAlpha(byte value); + + @CField("beta") + void setBeta(long value); + }// Checkstyle: resume + + // Checkstyle: stop + @CStruct("struct composite_struct") + interface CompositeStruct extends PointerBase { + @CField("c1") + void setC1(byte value); + + @CFieldAddress("c2") + SimpleStruct getC2(); + + @CField("c3") + void setC3(int value); + + @CFieldAddress("c4") + SimpleStruct2 getC4(); + + @CField("c5") + void setC5(short value); + + }// Checkstyle: resume + + /*- + checkedBreakpoint('com.oracle.svm.enterprise.debug.test.CStructTests.free', [ + {'frame#' : 1, 'eval' : [ + ('cs', [ + chkVal('CStruct com.oracle.svm.enterprise.debug.test.CStructTests$CompositeStruct = {' + ' c1 = 7,' + ' c2 = {' + ' first = 17,' + ' second = 19' + ' },' + ' c3 = 13,' + ' c4 = {' + ' alpha = 3,' + ' beta = 9223372036854775807' + ' },' + ' c5 = 32000}', castFn=str) + ]) + ]} + ]) + */ + public void composite() { + CompositeStruct cs = UnmanagedMemory.malloc(3 * SizeOf.get(CompositeStruct.class)); + cs.setC1((byte) 7); + cs.setC3(13); + cs.setC5((short) 32000); + cs.getC2().setFirst(17); + cs.getC2().setSecond(19); + cs.getC4().setAlpha((byte) 3); + cs.getC4().setBeta(Long.MAX_VALUE); + free(cs); + } + + public static void main(String[] args) { + CStructTests tests = new CStructTests(); + tests.composite(); + tests.weird(); + } + + @NeverInline("Used as a hook to inspect the caller frame in GDB") + static void free(PointerBase ptr) { + UnmanagedMemory.free(ptr); + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/hello/Hello.java b/substratevm/src/com.oracle.svm.test/src/hello/Hello.java index bcd6936efe71..d7ff7b2b9c7e 100644 --- a/substratevm/src/com.oracle.svm.test/src/hello/Hello.java +++ b/substratevm/src/com.oracle.svm.test/src/hello/Hello.java @@ -30,6 +30,7 @@ import com.oracle.svm.core.AlwaysInline; import com.oracle.svm.core.NeverInline; +import com.oracle.svm.test.debug.CStructTests; public class Hello { public abstract static class Greeter { @@ -95,6 +96,10 @@ public static void main(String[] args) { noInlineManyArgs(0, 1, 2, 3, true, 5, 6, 7, 8, 9, 0.0F, 1.125F, 2.25F, 3.375F, 4.5F, 5.625F, 6.75F, 7.875F, 9.0F, 10.125D, false, 12.375F); noInlinePassConstants(); + // create and manipulate some foreign types + CStructTests tests = new CStructTests(); + tests.composite(); + tests.weird(); System.exit(0); } diff --git a/substratevm/src/com.oracle.svm.test/src/systemjava_debugtest.h b/substratevm/src/com.oracle.svm.test/src/systemjava_debugtest.h new file mode 100644 index 000000000000..52bbb6345ace --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/systemjava_debugtest.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2023, 2023, 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 SYSTEMJAVA_TEST_H_INCLUDED +#define SYSTEMJAVA_TEST_H_INCLUDED + +struct simple_struct { + int first; + int second; +}; + +struct simple_struct2 { + char alpha; + long long beta; +}; + +struct composite_struct { + char c1; + struct simple_struct c2; + int c3; + struct simple_struct2 c4; + short c5; +}; + +struct weird { + short f_short; + unsigned int f_uint; + int f_int; + long f_long; + float f_float; + double f_double; + int a_int[8]; + char a_char[12]; +}; + +#include + +#endif // SYSTEMJAVA_TEST_H_INCLUDED