diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index b99ca5c03a54..a3bcb772496d 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -31,6 +31,7 @@ This changelog summarizes major changes to GraalVM Native Image. * (GR-57827) Security providers can now be initialized at run time (instead of build time) when using the option `--future-defaults=all` or `--future-defaults=run-time-initialized-jdk`. Run-time initialization of security providers helps reduce image heap size by avoiding unnecessary objects inclusion. * (GR-48191) Enable lambda classes to be registered for reflection and serialization in _reachability-metadata.json_. The format is detailed [here](https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/ReachabilityMetadata.md). +* (GR-54697) Parallelize debug info generation and add support for run-time debug info generation. `-H:+RuntimeDebugInfo` adds a run-time debug info generator into a native image for use with GDB. ## GraalVM for JDK 24 (Internal Version 24.2.0) * (GR-59717) Added `DuringSetupAccess.registerObjectReachabilityHandler` to allow registering a callback that is executed when an object of a specified type is marked as reachable during heap scanning. diff --git a/substratevm/debug/gdbpy/gdb-debughelpers.py b/substratevm/debug/gdbpy/gdb-debughelpers.py index 4d9a5b45fbc8..8a25adbcbaed 100644 --- a/substratevm/debug/gdbpy/gdb-debughelpers.py +++ b/substratevm/debug/gdbpy/gdb-debughelpers.py @@ -66,17 +66,6 @@ def trace(msg: str) -> None: svm_debug_tracing.tracefile.flush() -def adr(obj: gdb.Value) -> int: - # use null as fallback if we cannot find the address value - adr_val = 0 - if obj.type.code == gdb.TYPE_CODE_PTR: - if int(obj) != 0: - adr_val = int(obj.dereference().address) - elif obj.address is not None: - adr_val = int(obj.address) - return adr_val - - def try_or_else(success, failure, *exceptions): try: return success() @@ -93,28 +82,12 @@ def __init__(self, static: bool, name: str, gdb_sym: gdb.Symbol): class SVMUtil: + # class fields pretty_printer_name = "SubstrateVM" hub_field_name = "hub" compressed_ref_prefix = '_z_.' - use_heap_base = try_or_else(lambda: bool(gdb.parse_and_eval('(int)__svm_use_heap_base')), True, gdb.error) - compression_shift = try_or_else(lambda: int(gdb.parse_and_eval('(int)__svm_compression_shift')), 0, gdb.error) - reserved_bits_mask = try_or_else(lambda: int(gdb.parse_and_eval('(int)__svm_reserved_bits_mask')), 0, gdb.error) - object_alignment = try_or_else(lambda: int(gdb.parse_and_eval('(int)__svm_object_alignment')), 0, gdb.error) - heap_base_regnum = try_or_else(lambda: int(gdb.parse_and_eval('(int)__svm_heap_base_regnum')), 0, gdb.error) - - string_type = gdb.lookup_type("java.lang.String") - enum_type = gdb.lookup_type("java.lang.Enum") - object_type = gdb.lookup_type("java.lang.Object") - object_header_type = gdb.lookup_type("_objhdr") - hub_type = gdb.lookup_type("Encoded$Dynamic$Hub") - null = gdb.Value(0).cast(object_type.pointer()) - classloader_type = gdb.lookup_type("java.lang.ClassLoader") - wrapper_types = [gdb.lookup_type(f'java.lang.{x}') for x in - ["Byte", "Short", "Integer", "Long", "Float", "Double", "Boolean", "Character"] - if gdb.lookup_global_symbol(f'java.lang.{x}') is not None] - pretty_print_objfiles = set() current_print_depth = 0 @@ -122,68 +95,206 @@ class SVMUtil: selfref_cycles = set() hlreps = dict() - deopt_stub_adr = 0 + # static methods + @staticmethod + def get_eager_deopt_stub_adr() -> int: + sym = gdb.lookup_global_symbol('com.oracle.svm.core.deopt.Deoptimizer::eagerDeoptStub', gdb.SYMBOL_VAR_DOMAIN) + return sym.value().address if sym is not None else -1 + + @staticmethod + def get_lazy_deopt_stub_primitive_adr() -> int: + sym = gdb.lookup_global_symbol('com.oracle.svm.core.deopt.Deoptimizer::lazyDeoptStubPrimitiveReturn', gdb.SYMBOL_VAR_DOMAIN) + return sym.value().address if sym is not None else -1 + + @staticmethod + def get_lazy_deopt_stub_object_adr() -> int: + sym = gdb.lookup_global_symbol('com.oracle.svm.core.deopt.Deoptimizer::lazyDeoptStubObjectReturn', gdb.SYMBOL_VAR_DOMAIN) + return sym.value().address if sym is not None else -1 + + @staticmethod + def get_unqualified_type_name(qualified_type_name: str) -> str: + result = qualified_type_name.split('.')[-1] + result = result.split('$')[-1] + trace(f' - get_unqualified_type_name({qualified_type_name}) = {result}') + return result + + @staticmethod + def get_symbol_adr(symbol: str) -> int: + trace(f' - get_symbol_adr({symbol})') + return gdb.parse_and_eval(symbol).address + + @staticmethod + def execout(cmd: str) -> str: + trace(f' - execout({cmd})') + return gdb.execute(cmd, False, True) + + @staticmethod + def get_basic_type(t: gdb.Type) -> gdb.Type: + trace(f' - get_basic_type({t})') + while t.code == gdb.TYPE_CODE_PTR: + t = t.target() + return t + + # class methods @classmethod - def get_heap_base(cls) -> gdb.Value: - try: - return gdb.selected_frame().read_register(cls.heap_base_regnum) - except gdb.error: - # no frame available - return cls.null + def prompt_hook(cls, current_prompt: str = None): + cls.current_print_depth = 0 + cls.parents.clear() + cls.selfref_cycles.clear() + SVMCommandPrint.cache.clear() @classmethod - def is_null(cls, obj: gdb.Value) -> bool: - return adr(obj) == 0 or (cls.use_heap_base and adr(obj) == int(cls.get_heap_base())) + # checks if node this is reachable from node other (this node is parent of other node) + def is_reachable(cls, this: hex, other: hex) -> bool: + test_nodes = [other] + trace(f' - is_reachable(this={this}, other={other}') + while True: + if len(test_nodes) == 0: + return False + if any(this == node for node in test_nodes): + return True + # create a flat list of all ancestors of each tested node + test_nodes = [parent for node in test_nodes for parent in cls.parents.get(node, [])] @classmethod - def get_uncompressed_type(cls, t: gdb.Type) -> gdb.Type: - # compressed types only exist for java type which are either struct or union - if t.code != gdb.TYPE_CODE_STRUCT and t.code != gdb.TYPE_CODE_UNION: - return t - result = cls.get_base_class(t) if (cls.is_compressed(t) and t != cls.hub_type) else t - trace(f' - get_uncompressed_type({t}) = {result}') + def is_primitive(cls, t: gdb.Type) -> bool: + result = cls.get_basic_type(t).is_scalar + trace(f' - is_primitive({t}) = {result}') return result @classmethod - def get_compressed_type(cls, t: gdb.Type) -> gdb.Type: + def get_all_fields(cls, t: gdb.Type, include_static: bool) -> list: # list[gdb.Field]: t = cls.get_basic_type(t) - # compressed types only exist for java types which are either struct or union - # do not compress types that already have the compressed prefix - if not cls.is_java_type(t) or cls.is_compressed(t): - return t + while t.code == gdb.TYPE_CODE_TYPEDEF: + t = t.target() + t = cls.get_basic_type(t) + if t.code != gdb.TYPE_CODE_STRUCT and t.code != gdb.TYPE_CODE_UNION: + return [] + for f in t.fields(): + if not include_static: + try: + f.bitpos # bitpos attribute is not available for static fields + except AttributeError: # use bitpos access exception to skip static fields + continue + if f.is_base_class: + yield from cls.get_all_fields(f.type, include_static) + else: + yield f - type_name = t.name - # java types only contain '::' if there is a classloader namespace - if '::' in type_name: - loader_namespace, _, type_name = type_name.partition('::') - type_name = loader_namespace + '::' + cls.compressed_ref_prefix + type_name - else: - type_name = cls.compressed_ref_prefix + type_name + @classmethod + def get_all_member_functions(cls, t: gdb.Type, include_static: bool, include_constructor: bool) -> set: # set[Function]: + syms = set() + try: + basic_type = cls.get_basic_type(t) + type_name = basic_type.name + members = cls.execout(f"ptype '{type_name}'") + for member in members.split('\n'): + parts = member.strip().split(' ') + is_static = parts[0] == 'static' + if not include_static and is_static: + continue + for part in parts: + if '(' in part: + func_name = part[:part.find('(')] + if include_constructor or func_name != cls.get_unqualified_type_name(type_name): + sym = gdb.lookup_global_symbol(f"{type_name}::{func_name}") + # check if symbol exists and is a function + if sym is not None and sym.type.code == gdb.TYPE_CODE_FUNC: + syms.add(Function(is_static, func_name, sym)) + break + for f in basic_type.fields(): + if f.is_base_class: + syms = syms.union(cls.get_all_member_functions(f.type, include_static, include_constructor)) + except Exception as ex: + trace(f' - get_all_member_function_names({t}) exception: {ex}') + return syms - trace(f' - get_compressed_type({t}) = {type_name}') - return gdb.lookup_type(type_name) + # instance initializer + # there will be one instance of SVMUtil per objfile that is registered + # each objfile has its own types, thus this is necessary to compare against the correct types in memory + # when reloading e.g. a shared library, the addresses of debug info in the relocatable objfile might change + def __init__(self): + self.use_heap_base = try_or_else(lambda: bool(gdb.parse_and_eval('(int)__svm_use_heap_base')), True, gdb.error) + self.compression_shift = try_or_else(lambda: int(gdb.parse_and_eval('(int)__svm_compression_shift')), 0, gdb.error) + self.reserved_bits_mask = try_or_else(lambda: int(gdb.parse_and_eval('(int)__svm_reserved_bits_mask')), 0, gdb.error) + self.object_alignment = try_or_else(lambda: int(gdb.parse_and_eval('(int)__svm_object_alignment')), 0, gdb.error) + self.heap_base_regnum = try_or_else(lambda: int(gdb.parse_and_eval('(int)__svm_heap_base_regnum')), 0, gdb.error) + self.frame_size_status_mask = try_or_else(lambda: int(gdb.parse_and_eval('(int)__svm_frame_size_status_mask')), 0, gdb.error) + self.object_type = gdb.lookup_type("java.lang.Object") + self.object_header_type = gdb.lookup_type("_objhdr") + self.stack_type = gdb.lookup_type("long") + self.hub_type = gdb.lookup_type("Encoded$Dynamic$Hub") + self.object_header_type = gdb.lookup_type("_objhdr") + self.classloader_type = gdb.lookup_type("java.lang.ClassLoader") + self.wrapper_types = [gdb.lookup_type(f'java.lang.{x}') for x in + ["Byte", "Short", "Integer", "Long", "Float", "Double", "Boolean", "Character"] + if gdb.lookup_global_symbol(f'java.lang.{x}') is not None] + self.null = gdb.Value(0).cast(self.object_type.pointer()) + + # instance methods + def get_heap_base(self) -> gdb.Value: + try: + return gdb.selected_frame().read_register(self.heap_base_regnum) + except gdb.error: + # no frame available, return 0 + return 0 - @classmethod - def get_compressed_oop(cls, obj: gdb.Value) -> int: + def get_adr(self, obj: gdb.Value) -> int: + # use null as fallback if we cannot find the address value or obj is null + adr_val = 0 + try: + if obj.type.code == gdb.TYPE_CODE_PTR: + if int(obj) == 0 or (self.use_heap_base and int(obj) == int(self.get_heap_base())): + # obj is null + pass + else: + adr_val = int(obj.dereference().address) + elif obj.address is not None: + adr_val = int(obj.address) + return adr_val + except Exception as ex: + trace(f' - get_adr(...) exception: {ex}') + # the format of the gdb.Value was unexpected, continue with null + return 0 + + def is_null(self, obj: gdb.Value) -> bool: + return self.get_adr(obj) == 0 + + def is_compressed(self, t: gdb.Type) -> bool: + # for the hub type we always want handle it as compressed as there is no clear distinction in debug info for + # the hub field, and it may always have an expression in the type's data_location attribute + if self.get_basic_type(t) == self.hub_type: + return True + + type_name = self.get_basic_type(t).name + if type_name is None: + # fallback to the GDB type printer for t + type_name = str(t) + # compressed types from a different classLoader have the format ::_z_. + result = type_name.startswith(self.compressed_ref_prefix) or ('::' + self.compressed_ref_prefix) in type_name + trace(f' - is_compressed({type_name}) = {result}') + return result + + def get_compressed_oop(self, obj: gdb.Value) -> int: # use compressed ref if available - only compute it if necessary - if obj.type.code == gdb.TYPE_CODE_PTR and cls.is_compressed(obj.type): + if obj.type.code == gdb.TYPE_CODE_PTR and self.is_compressed(obj.type): return int(obj) - obj_adr = adr(obj) + obj_adr = self.get_adr(obj) if obj_adr == 0: return obj_adr # recreate compressed oop from the object address # this reverses the uncompress expression from # com.oracle.objectfile.elf.dwarf.DwarfInfoSectionImpl#writeIndirectOopConversionExpression - is_hub = cls.get_basic_type(obj.type) == cls.hub_type - compression_shift = cls.compression_shift - num_reserved_bits = int.bit_count(cls.reserved_bits_mask) - num_alignment_bits = int.bit_count(cls.object_alignment - 1) + is_hub = self.get_basic_type(obj.type) == self.hub_type + compression_shift = self.compression_shift + num_reserved_bits = int.bit_count(self.reserved_bits_mask) + num_alignment_bits = int.bit_count(self.object_alignment - 1) compressed_oop = obj_adr - if cls.use_heap_base: - compressed_oop -= int(SVMUtil.get_heap_base()) + if self.use_heap_base: + compressed_oop -= int(self.get_heap_base()) assert compression_shift >= 0 compressed_oop = compressed_oop >> compression_shift if is_hub and num_reserved_bits != 0: @@ -196,93 +307,46 @@ def get_compressed_oop(cls, obj: gdb.Value) -> int: return compressed_oop - @classmethod - def get_unqualified_type_name(cls, qualified_type_name: str) -> str: - result = qualified_type_name.split('.')[-1] - result = result.split('$')[-1] - trace(f' - get_unqualified_type_name({qualified_type_name}) = {result}') - return result - - @classmethod - def is_compressed(cls, t: gdb.Type) -> bool: - # for the hub type we always want handle it as compressed as there is no clear distinction in debug info for - # the hub field, and it may always have an expression in the type's data_location attribute - if cls.get_basic_type(t) == cls.hub_type: - return True - - type_name = cls.get_basic_type(t).name - if type_name is None: - # fallback to the GDB type printer for t - type_name = str(t) - # compressed types from a different classLoader have the format ::_z_. - result = type_name.startswith(cls.compressed_ref_prefix) or ('::' + cls.compressed_ref_prefix) in type_name - trace(f' - is_compressed({type_name}) = {result}') - return result - - @classmethod - def adr_str(cls, obj: gdb.Value) -> str: - if not svm_print_address.absolute_adr and cls.is_compressed(obj.type): - result = f' @z({hex(cls.get_compressed_oop(obj))})' + def adr_str(self, obj: gdb.Value) -> str: + if not svm_print_address.absolute_adr and self.is_compressed(obj.type): + result = f' @z({hex(self.get_compressed_oop(obj))})' else: - result = f' @({hex(adr(obj))})' - trace(f' - adr_str({hex(adr(obj))}) = {result}') + result = f' @({hex(self.get_adr(obj))})' + trace(f' - adr_str({hex(self.get_adr(obj))}) = {result}') return result - @classmethod - def prompt_hook(cls, current_prompt: str = None): - cls.current_print_depth = 0 - cls.parents.clear() - cls.selfref_cycles.clear() - SVMCommandPrint.cache.clear() - - @classmethod - def is_selfref(cls, obj: gdb.Value) -> bool: + def is_selfref(self, obj: gdb.Value) -> bool: result = (svm_check_selfref.value and - not cls.is_primitive(obj.type) and - adr(obj) in cls.selfref_cycles) - trace(f' - is_selfref({hex(adr(obj))}) = {result}') + not SVMUtil.is_primitive(obj.type) and + self.get_adr(obj) in SVMUtil.selfref_cycles) + trace(f' - is_selfref({hex(self.get_adr(obj))}) = {result}') return result - @classmethod - def add_selfref(cls, parent: gdb.Value, child: gdb.Value) -> gdb.Value: + def add_selfref(self, parent: gdb.Value, child: gdb.Value) -> gdb.Value: # filter out null references and primitives - if (child.type.code == gdb.TYPE_CODE_PTR and cls.is_null(child)) or cls.is_primitive(child.type): + if (child.type.code == gdb.TYPE_CODE_PTR and self.is_null(child)) or SVMUtil.is_primitive(child.type): return child - child_adr = adr(child) - parent_adr = adr(parent) + child_adr = self.get_adr(child) + parent_adr = self.get_adr(parent) trace(f' - add_selfref(parent={hex(parent_adr)}, child={hex(child_adr)})') - if svm_check_selfref.value and cls.is_reachable(child_adr, parent_adr): + if svm_check_selfref.value and SVMUtil.is_reachable(child_adr, parent_adr): trace(f' ') - cls.selfref_cycles.add(child_adr) + SVMUtil.selfref_cycles.add(child_adr) else: trace(f' {hex(parent_adr)}>') - if child_adr in cls.parents: - cls.parents[child_adr].append(parent_adr) + if child_adr in SVMUtil.parents: + SVMUtil.parents[child_adr].append(parent_adr) else: - cls.parents[child_adr] = [parent_adr] + SVMUtil.parents[child_adr] = [parent_adr] return child - @classmethod - # checks if node this is reachable from node other (this node is parent of other node) - def is_reachable(cls, this: hex, other: hex) -> bool: - test_nodes = [other] - trace(f' - is_reachable(this={this}, other={other}') - while True: - if len(test_nodes) == 0: - return False - if any(this == node for node in test_nodes): - return True - # create a flat list of all ancestors of each tested node - test_nodes = [parent for node in test_nodes for parent in cls.parents.get(node, [])] - - @classmethod - def get_java_string(cls, obj: gdb.Value, gdb_output_string: bool = False) -> str: - if cls.is_null(obj): + def get_java_string(self, obj: gdb.Value, gdb_output_string: bool = False) -> str: + if self.is_null(obj): return "" - trace(f' - get_java_string({hex(adr(obj))})') - coder = cls.get_int_field(obj, 'coder', None) + trace(f' - get_java_string({hex(self.get_adr(obj))})') + coder = self.get_int_field(obj, 'coder', None) if coder is None: codec = 'utf-16' # Java 8 has a char[] with utf-16 bytes_per_char = 2 @@ -295,13 +359,13 @@ def get_java_string(cls, obj: gdb.Value, gdb_output_string: bool = False) -> str }.get(coder) bytes_per_char = 1 - value = cls.get_obj_field(obj, 'value') - if cls.is_null(value): + value = self.get_obj_field(obj, 'value') + if self.is_null(value): return "" - value_content = cls.get_obj_field(value, 'data') - value_length = cls.get_int_field(value, 'len') - if cls.is_null(value_content) or value_length == 0: + value_content = self.get_obj_field(value, 'data') + value_length = self.get_int_field(value, 'len') + if self.is_null(value_content) or value_length == 0: return "" string_data = bytearray() @@ -315,75 +379,79 @@ def get_java_string(cls, obj: gdb.Value, gdb_output_string: bool = False) -> str if gdb_output_string and 0 < svm_print_string_limit.value < value_length: result += "..." - trace(f' - get_java_string({hex(adr(obj))}) = {result}') + trace(f' - get_java_string({hex(self.get_adr(obj))}) = {result}') return result - @classmethod - def get_obj_field(cls, obj: gdb.Value, field_name: str, default: gdb.Value = null) -> gdb.Value: + def get_hub_field(self, obj: gdb.Value) -> gdb.Value: + return self.get_obj_field(self.cast_to(obj, self.object_header_type), self.hub_field_name) + + def get_obj_field(self, obj: gdb.Value, field_name: str, default: gdb.Value = None) -> gdb.Value: + # Make sure we never access fields of a null value + # This is necessary because 'null' is represented by a gdb.Value with a raw value of 0x0 + if self.is_null(obj): + return self.null + try: return obj[field_name] except gdb.error: - return default + return self.null if default is None else default - @classmethod - def get_int_field(cls, obj: gdb.Value, field_name: str, default: int = 0) -> int: - field = cls.get_obj_field(obj, field_name) + def get_int_field(self, obj: gdb.Value, field_name: str, default: int = 0) -> int: + field = self.get_obj_field(obj, field_name) try: return int(field) except (gdb.error, TypeError): # TypeError if field is None already return default - @classmethod - def get_classloader_namespace(cls, obj: gdb.Value) -> str: + def get_classloader_namespace(self, obj: gdb.Value) -> str: try: - hub = cls.get_obj_field(obj, cls.hub_field_name) - if cls.is_null(hub): + hub = self.get_hub_field(obj) + if self.is_null(hub): return "" - hub_companion = cls.get_obj_field(hub, 'companion') - if cls.is_null(hub_companion): + hub_companion = self.get_obj_field(hub, 'companion') + if self.is_null(hub_companion): return "" - loader = cls.get_obj_field(hub_companion, 'classLoader') - if cls.is_null(loader): + loader = self.get_obj_field(hub_companion, 'classLoader') + if self.is_null(loader): return "" - loader = cls.cast_to(loader, cls.classloader_type) + loader = self.cast_to(loader, self.classloader_type) - loader_name = cls.get_obj_field(loader, 'nameAndId') - if cls.is_null(loader_name): + loader_name = self.get_obj_field(loader, 'nameAndId') + if self.is_null(loader_name): return "" - loader_namespace = cls.get_java_string(loader_name) + loader_namespace = self.get_java_string(loader_name) trace(f' - get_classloader_namespace loader_namespace: {loader_namespace}') # replicate steps in 'com.oracle.svm.hosted.image.NativeImageBFDNameProvider::uniqueShortLoaderName' # for recreating the loader name stored in the DWARF debuginfo - loader_namespace = cls.get_unqualified_type_name(loader_namespace) + loader_namespace = SVMUtil.get_unqualified_type_name(loader_namespace) loader_namespace = loader_namespace.replace(' @', '_').replace("'", '').replace('"', '') return loader_namespace except gdb.error: pass # ignore gdb errors here and try to continue with no classLoader return "" - @classmethod - def get_rtt(cls, obj: gdb.Value) -> gdb.Type: - static_type = cls.get_basic_type(obj.type) + def get_rtt(self, obj: gdb.Value) -> gdb.Type: + static_type = SVMUtil.get_basic_type(obj.type) - if static_type == cls.hub_type: - return cls.hub_type + if static_type == self.hub_type: + return self.hub_type # check for interfaces and cast them to Object to make the hub accessible - if cls.get_uncompressed_type(cls.get_basic_type(obj.type)).code == gdb.TYPE_CODE_UNION: - obj = cls.cast_to(obj, cls.object_type) + if self.get_uncompressed_type(SVMUtil.get_basic_type(obj.type)).code == gdb.TYPE_CODE_UNION: + obj = self.cast_to(obj, self.object_type) - hub = cls.get_obj_field(obj, cls.hub_field_name) - if cls.is_null(hub): + hub = self.get_hub_field(obj) + if self.is_null(hub): return static_type - name_field = cls.get_obj_field(hub, 'name') - if cls.is_null(name_field): + name_field = self.get_obj_field(hub, 'name') + if self.is_null(name_field): return static_type - rtt_name = cls.get_java_string(name_field) + rtt_name = self.get_java_string(name_field) if rtt_name.startswith('['): array_dimension = rtt_name.count('[') if array_dimension > 0: @@ -405,7 +473,7 @@ def get_rtt(cls, obj: gdb.Value) -> gdb.Type: for _ in range(array_dimension): rtt_name += '[]' - loader_namespace = cls.get_classloader_namespace(obj) + loader_namespace = self.get_classloader_namespace(obj) if loader_namespace != "": try: # try to apply loader namespace @@ -415,134 +483,66 @@ def get_rtt(cls, obj: gdb.Value) -> gdb.Type: else: rtt = gdb.lookup_type(rtt_name) - if cls.is_compressed(obj.type) and not cls.is_compressed(rtt): - rtt = cls.get_compressed_type(rtt) + if self.is_compressed(obj.type) and not self.is_compressed(rtt): + rtt = self.get_compressed_type(rtt) - trace(f' - get_rtt({hex(adr(obj))}) = {rtt_name}') + trace(f' - get_rtt({hex(self.get_adr(obj))}) = {rtt_name}') return rtt - @classmethod - def cast_to(cls, obj: gdb.Value, t: gdb.Type) -> gdb.Value: + def cast_to(self, obj: gdb.Value, t: gdb.Type) -> gdb.Value: if t is None: return obj # get objects address, take care of compressed oops - if cls.is_compressed(t): - obj_oop = cls.get_compressed_oop(obj) + if self.is_compressed(t): + obj_oop = self.get_compressed_oop(obj) else: - obj_oop = adr(obj) + obj_oop = self.get_adr(obj) - trace(f' - cast_to({hex(adr(obj))}, {t})') + trace(f' - cast_to({hex(self.get_adr(obj))}, {t})') if t.code != gdb.TYPE_CODE_PTR: t = t.pointer() - trace(f' - cast_to({hex(adr(obj))}, {t}) returned') + trace(f' - cast_to({hex(self.get_adr(obj))}, {t}) returned') # just use the raw pointer value and cast it instead the obj # casting the obj directly results in issues with compressed oops return obj if t == obj.type else gdb.Value(obj_oop).cast(t) - @classmethod - def get_symbol_adr(cls, symbol: str) -> int: - trace(f' - get_symbol_adr({symbol})') - return gdb.parse_and_eval(symbol).address - - @classmethod - def execout(cls, cmd: str) -> str: - trace(f' - execout({cmd})') - return gdb.execute(cmd, False, True) - - @classmethod - def get_basic_type(cls, t: gdb.Type) -> gdb.Type: - trace(f' - get_basic_type({t})') - while t.code == gdb.TYPE_CODE_PTR: - t = t.target() - return t - - @classmethod - def is_primitive(cls, t: gdb.Type) -> bool: - result = cls.get_basic_type(t).is_scalar - trace(f' - is_primitive({t}) = {result}') + def get_uncompressed_type(self, t: gdb.Type) -> gdb.Type: + # compressed types only exist for java type which are either struct or union + if t.code != gdb.TYPE_CODE_STRUCT and t.code != gdb.TYPE_CODE_UNION: + return t + result = self.get_base_class(t) if (self.is_compressed(t) and t != self.hub_type) else t + trace(f' - get_uncompressed_type({t}) = {result}') return result - @classmethod - def is_primitive_wrapper(cls, t: gdb.Type) -> bool: - result = t in cls.wrapper_types + def is_primitive_wrapper(self, t: gdb.Type) -> bool: + result = t in self.wrapper_types trace(f' - is_primitive_wrapper({t}) = {result}') return result - @classmethod - def is_enum_type(cls, t: gdb.Type) -> bool: - return cls.get_base_class(t) == cls.enum_type - - @classmethod - def get_base_class(cls, t: gdb.Type) -> gdb.Type: - return t if t == cls.object_type else \ - next((f.type for f in t.fields() if f.is_base_class), cls.object_type) + def get_base_class(self, t: gdb.Type) -> gdb.Type: + return t if t == self.object_type else \ + next((f.type for f in t.fields() if f.is_base_class), self.object_type) - @classmethod - def find_shared_types(cls, type_list: list, t: gdb.Type) -> list: # list[gdb.Type] + def find_shared_types(self, type_list: list, t: gdb.Type) -> list: # list[gdb.Type] if len(type_list) == 0: - while t != cls.object_type: + # fill type list -> java.lang.Object will be last element + while t != self.object_type: type_list += [t] - t = cls.get_base_class(t) + t = self.get_base_class(t) return type_list else: - while t != cls.object_type: + # find first type in hierarchy of t that is contained in the type list + while t != self.object_type: if t in type_list: return type_list[type_list.index(t):] - t = cls.get_base_class(t) - return [cls.object_type] - - @classmethod - def get_all_fields(cls, t: gdb.Type, include_static: bool) -> list: # list[gdb.Field]: - t = cls.get_basic_type(t) - while t.code == gdb.TYPE_CODE_TYPEDEF: - t = t.target() - t = cls.get_basic_type(t) - if t.code != gdb.TYPE_CODE_STRUCT and t.code != gdb.TYPE_CODE_UNION: - return [] - for f in t.fields(): - if not include_static: - try: - f.bitpos # bitpos attribute is not available for static fields - except AttributeError: # use bitpos access exception to skip static fields - continue - if f.is_base_class: - yield from cls.get_all_fields(f.type, include_static) - else: - yield f - - @classmethod - def get_all_member_functions(cls, t: gdb.Type, include_static: bool, include_constructor: bool) -> set: # set[Function]: - syms = set() - try: - basic_type = cls.get_basic_type(t) - type_name = basic_type.name - members = SVMUtil.execout(f"ptype '{type_name}'") - for member in members.split('\n'): - parts = member.strip().split(' ') - is_static = parts[0] == 'static' - if not include_static and is_static: - continue - for part in parts: - if '(' in part: - func_name = part[:part.find('(')] - if include_constructor or func_name != cls.get_unqualified_type_name(type_name): - sym = gdb.lookup_global_symbol(f"{type_name}::{func_name}") - # check if symbol exists and is a function - if sym is not None and sym.type.code == gdb.TYPE_CODE_FUNC: - syms.add(Function(is_static, func_name, sym)) - break - for f in basic_type.fields(): - if f.is_base_class: - syms = syms.union(cls.get_all_member_functions(f.type, include_static, include_constructor)) - except Exception as ex: - trace(f' - get_all_member_function_names({t}) exception: {ex}') - return syms + t = self.get_base_class(t) + # if nothing matches return the java.lang.Object + return [self.object_type] - @classmethod - def is_java_type(cls, t: gdb.Type) -> bool: - t = cls.get_uncompressed_type(cls.get_basic_type(t)) + def is_java_type(self, t: gdb.Type) -> bool: + t = self.get_uncompressed_type(SVMUtil.get_basic_type(t)) # Check for existing ".class" symbol (which exists for every java type in a native image) # a java class is represented by a struct, interfaces are represented by a union # only structs contain a "hub" field, thus just checking for a hub field does not work for interfaces @@ -552,45 +552,73 @@ def is_java_type(cls, t: gdb.Type) -> bool: trace(f' - is_java_obj({t}) = {result}') return result + # returns the compressed variant of t if available, otherwise returns the basic type of t (without pointers) + def get_compressed_type(self, t: gdb.Type) -> gdb.Type: + t = SVMUtil.get_basic_type(t) + # compressed types only exist for java types which are either struct or union + # do not compress types that already have the compressed prefix + if not self.is_java_type(t) or self.is_compressed(t): + return t + + type_name = t.name + # java types only contain '::' if there is a classloader namespace + if '::' in type_name: + loader_namespace, _, type_name = type_name.partition('::') + type_name = loader_namespace + '::' + SVMUtil.compressed_ref_prefix + type_name + else: + type_name = SVMUtil.compressed_ref_prefix + type_name + + try: + result_type = gdb.lookup_type(type_name) + trace(f' - could not find compressed type "{type_name}" using uncompressed type') + except gdb.error as ex: + trace(ex) + result_type = t + + trace(f' - get_compressed_type({t}) = {t.name}') + return result_type + class SVMPPString: - def __init__(self, obj: gdb.Value, java: bool = True): - trace(f' - __init__({hex(adr(obj))})') + def __init__(self, svm_util: SVMUtil, obj: gdb.Value, java: bool = True): + trace(f' - __init__({hex(svm_util.get_adr(obj))})') self.__obj = obj self.__java = java + self.__svm_util = svm_util def to_string(self) -> str: trace(' - to_string') if self.__java: try: - value = '"' + SVMUtil.get_java_string(self.__obj, True) + '"' + value = '"' + self.__svm_util.get_java_string(self.__obj, True) + '"' except gdb.error: return SVMPPConst(None) else: value = str(self.__obj) value = value[value.index('"'):] if svm_print_address.with_adr: - value += SVMUtil.adr_str(self.__obj) + value += self.__svm_util.adr_str(self.__obj) trace(f' - to_string = {value}') return value class SVMPPArray: - def __init__(self, obj: gdb.Value, java_array: bool = True): - trace(f' - __init__(obj={obj.type} @ {hex(adr(obj))}, java_array={java_array})') + def __init__(self, svm_util: SVMUtil, obj: gdb.Value, java_array: bool = True): + trace(f' - __init__(obj={obj.type} @ {hex(svm_util.get_adr(obj))}, java_array={java_array})') if java_array: - self.__length = SVMUtil.get_int_field(obj, 'len') - self.__array = SVMUtil.get_obj_field(obj, 'data', None) - if SVMUtil.is_null(self.__array): + self.__length = svm_util.get_int_field(obj, 'len') + self.__array = svm_util.get_obj_field(obj, 'data', None) + if svm_util.is_null(self.__array): self.__array = None else: self.__length = obj.type.range()[-1] + 1 self.__array = obj self.__obj = obj self.__java_array = java_array - self.__skip_children = SVMUtil.is_selfref(obj) or 0 <= svm_print_depth_limit.value <= SVMUtil.current_print_depth + self.__skip_children = svm_util.is_selfref(obj) or 0 <= svm_print_depth_limit.value <= SVMUtil.current_print_depth if not self.__skip_children: SVMUtil.current_print_depth += 1 + self.__svm_util = svm_util def display_hint(self) -> str: trace(' - display_hint = array') @@ -599,15 +627,15 @@ def display_hint(self) -> str: def to_string(self) -> str: trace(' - to_string') if self.__java_array: - rtt = SVMUtil.get_rtt(self.__obj) - value = str(SVMUtil.get_uncompressed_type(rtt)) + rtt = self.__svm_util.get_rtt(self.__obj) + value = str(self.__svm_util.get_uncompressed_type(rtt)) value = value.replace('[]', f'[{self.__length}]') else: value = str(self.__obj.type) if self.__skip_children: value += ' = {...}' if svm_print_address.with_adr: - value += SVMUtil.adr_str(self.__obj) + value += self.__svm_util.adr_str(self.__obj) trace(f' - to_string = {value}') return value @@ -627,22 +655,23 @@ def children(self) -> Iterable[object]: yield str(index), '...' return trace(f' - children[{index}]') - yield str(index), SVMUtil.add_selfref(self.__obj, elem) + yield str(index), self.__svm_util.add_selfref(self.__obj, elem) SVMUtil.current_print_depth -= 1 class SVMPPClass: - def __init__(self, obj: gdb.Value, java_class: bool = True): - trace(f' - __init__({obj.type} @ {hex(adr(obj))})') + def __init__(self, svm_util: SVMUtil, obj: gdb.Value, java_class: bool = True): + trace(f' - __init__({obj.type} @ {hex(svm_util.get_adr(obj))})') self.__obj = obj self.__java_class = java_class - self.__skip_children = SVMUtil.is_selfref(obj) or 0 <= svm_print_depth_limit.value <= SVMUtil.current_print_depth + self.__skip_children = svm_util.is_selfref(obj) or 0 <= svm_print_depth_limit.value <= SVMUtil.current_print_depth if not self.__skip_children: SVMUtil.current_print_depth += 1 + self.__svm_util = svm_util def __getitem__(self, key: str) -> gdb.Value: trace(f' - __getitem__({key})') - item = SVMUtil.get_obj_field(self.__obj, key, None) + item = self.__svm_util.get_obj_field(self.__obj, key, None) if item is None: return None pp_item = gdb.default_visualizer(item) @@ -652,14 +681,14 @@ def to_string(self) -> str: trace(' - to_string') try: if self.__java_class: - rtt = SVMUtil.get_rtt(self.__obj) - result = SVMUtil.get_uncompressed_type(rtt).name + rtt = self.__svm_util.get_rtt(self.__obj) + result = self.__svm_util.get_uncompressed_type(rtt).name else: result = "object" if self.__obj.type.name is None else self.__obj.type.name if self.__skip_children: result += ' = {...}' if svm_print_address.with_adr: - result += SVMUtil.adr_str(self.__obj) + result += self.__svm_util.adr_str(self.__obj) trace(f' - to_string = {result}') return result except gdb.error as ex: @@ -671,42 +700,44 @@ def children(self) -> Iterable[object]: if self.__skip_children: return # hide fields from the object header - fields = [str(f.name) for f in SVMUtil.get_all_fields(self.__obj.type, svm_print_static_fields.value) if f.parent_type != SVMUtil.object_header_type] + fields = [str(f.name) for f in SVMUtil.get_all_fields(self.__obj.type, svm_print_static_fields.value) if f.parent_type != self.__svm_util.object_header_type] for index, f in enumerate(fields): trace(f' - children: field "{f}"') # apply custom limit only for java objects if self.__java_class and 0 <= svm_print_field_limit.value <= index: yield f, '...' return - yield f, SVMUtil.add_selfref(self.__obj, self.__obj[f]) + yield f, self.__svm_util.add_selfref(self.__obj, self.__obj[f]) SVMUtil.current_print_depth -= 1 class SVMPPEnum: - def __init__(self, obj: gdb.Value): - trace(f' - __init__({hex(adr(obj))})') + def __init__(self, svm_util: SVMUtil, obj: gdb.Value): + trace(f' - __init__({hex(svm_util.get_adr(obj))})') self.__obj = obj - self.__name = SVMUtil.get_obj_field(self.__obj, 'name', "") - self.__ordinal = SVMUtil.get_int_field(self.__obj, 'ordinal', None) + self.__name = svm_util.get_obj_field(self.__obj, 'name', "") + self.__ordinal = svm_util.get_int_field(self.__obj, 'ordinal', None) + self.__svm_util = svm_util def to_string(self) -> str: - result = SVMUtil.get_java_string(self.__name) + f"({self.__ordinal})" + result = self.__svm_util.get_java_string(self.__name) + f"({self.__ordinal})" if svm_print_address.with_adr: - result += SVMUtil.adr_str(self.__obj) + result += self.__svm_util.adr_str(self.__obj) trace(f' - to_string = {result}') return result class SVMPPBoxedPrimitive: - def __init__(self, obj: gdb.Value): - trace(f' - __init__({obj.type} @ {hex(adr(obj))})') + def __init__(self, svm_util: SVMUtil, obj: gdb.Value): + trace(f' - __init__({obj.type} @ {hex(svm_util.get_adr(obj))})') self.__obj = obj - self.__value = SVMUtil.get_obj_field(self.__obj, 'value', obj.type.name) + self.__value = svm_util.get_obj_field(self.__obj, 'value', obj.type.name) + self.__svm_util = svm_util def to_string(self) -> str: result = str(self.__value) if svm_print_address.with_adr: - result += SVMUtil.adr_str(self.__obj) + result += self.__svm_util.adr_str(self.__obj) trace(f' - to_string = {result}') return result @@ -723,52 +754,55 @@ def to_string(self) -> str: class SVMPrettyPrinter(gdb.printing.PrettyPrinter): - def __init__(self): + def __init__(self, svm_util: SVMUtil): super().__init__(SVMUtil.pretty_printer_name) + self.enum_type = gdb.lookup_type("java.lang.Enum") + self.string_type = gdb.lookup_type("java.lang.String") + self.svm_util = svm_util def __call__(self, obj: gdb.Value): - trace(f' - __call__({obj.type} @ {hex(adr(obj))})') + trace(f' - __call__({obj.type} @ {hex(self.svm_util.get_adr(obj))})') - if not SVMUtil.is_primitive(obj.type) and SVMUtil.is_java_type(obj.type): + if not SVMUtil.is_primitive(obj.type) and self.svm_util.is_java_type(obj.type): # Filter out references to the null literal - if SVMUtil.is_null(obj): + if self.svm_util.is_null(obj): return SVMPPConst(None) - rtt = SVMUtil.get_rtt(obj) - uncompressed_rtt = SVMUtil.get_uncompressed_type(rtt) - obj = SVMUtil.cast_to(obj, rtt) + rtt = self.svm_util.get_rtt(obj) + uncompressed_rtt = self.svm_util.get_uncompressed_type(rtt) + obj = self.svm_util.cast_to(obj, rtt) # filter for primitive wrappers - if SVMUtil.is_primitive_wrapper(uncompressed_rtt): - return SVMPPBoxedPrimitive(obj) + if self.svm_util.is_primitive_wrapper(uncompressed_rtt): + return SVMPPBoxedPrimitive(self.svm_util, obj) # filter for strings - if uncompressed_rtt == SVMUtil.string_type: - return SVMPPString(obj) + if uncompressed_rtt == self.string_type: + return SVMPPString(self.svm_util, obj) # filter for arrays if uncompressed_rtt.name.endswith("[]"): - return SVMPPArray(obj) + return SVMPPArray(self.svm_util, obj) # filter for enum values - if SVMUtil.is_enum_type(uncompressed_rtt): - return SVMPPEnum(obj) + if self.svm_util.get_base_class(uncompressed_rtt) == self.enum_type: + return SVMPPEnum(self.svm_util, obj) # Any other Class ... if svm_use_hlrep.value: - pp = make_high_level_object(obj, uncompressed_rtt.name) + pp = make_high_level_object(self.svm_util, obj, uncompressed_rtt.name) else: - pp = SVMPPClass(obj) + pp = SVMPPClass(self.svm_util, obj) return pp # no complex java type -> handle foreign types for selfref checks elif obj.type.code == gdb.TYPE_CODE_PTR and obj.type.target().code != gdb.TYPE_CODE_VOID: # Filter out references to the null literal - if SVMUtil.is_null(obj): + if self.svm_util.is_null(obj): return SVMPPConst(None) return self.__call__(obj.dereference()) elif obj.type.code == gdb.TYPE_CODE_ARRAY: - return SVMPPArray(obj, False) + return SVMPPArray(self.svm_util, obj, False) elif obj.type.code == gdb.TYPE_CODE_TYPEDEF: # try to expand foreign c structs try: @@ -777,7 +811,7 @@ def __call__(self, obj: gdb.Value): except gdb.error as err: return None elif obj.type.code == gdb.TYPE_CODE_STRUCT: - return SVMPPClass(obj, False) + return SVMPPClass(self.svm_util, obj, False) elif SVMUtil.is_primitive(obj.type): if obj.type.name == "char" and obj.type.sizeof == 2: return SVMPPConst(repr(chr(obj))) @@ -801,20 +835,21 @@ def HLRep(original_class): class ArrayList: target_type = 'java.util.ArrayList' - def __init__(self, obj: gdb.Value): - trace(f' - __init__({obj.type} @ {hex(adr(obj))})') - self.__size = SVMUtil.get_int_field(obj, 'size') - element_data = SVMUtil.get_obj_field(obj, 'elementData') - if SVMUtil.is_null(element_data): + def __init__(self, svm_util: SVMUtil, obj: gdb.Value): + trace(f' - __init__({obj.type} @ {hex(svm_util.get_adr(obj))})') + self.__size = svm_util.get_int_field(obj, 'size') + element_data = svm_util.get_obj_field(obj, 'elementData') + if svm_util.is_null(element_data): self.__data = None else: - self.__data = SVMUtil.get_obj_field(element_data, 'data', None) - if self.__data is not None and SVMUtil.is_null(self.__data): + self.__data = svm_util.get_obj_field(element_data, 'data', None) + if self.__data is not None and svm_util.is_null(self.__data): self.__data = None self.__obj = obj - self.__skip_children = SVMUtil.is_selfref(obj) or 0 <= svm_print_depth_limit.value <= SVMUtil.current_print_depth + self.__skip_children = svm_util.is_selfref(obj) or 0 <= svm_print_depth_limit.value <= SVMUtil.current_print_depth if not self.__skip_children: SVMUtil.current_print_depth += 1 + self.__svm_util = svm_util def to_string(self) -> str: trace(' - to_string') @@ -827,7 +862,7 @@ def to_string(self) -> str: if self.__skip_children: res += ' = {...}' if svm_print_address.with_adr: - res += SVMUtil.adr_str(self.__obj) + res += self.__svm_util.adr_str(self.__obj) trace(f' - to_string = {res}') return res @@ -835,9 +870,11 @@ def infer_generic_types(self) -> str: elem_type: list = [] # list[gdb.Type] for i, elem in enumerate(self, 1): - if not SVMUtil.is_null(elem): # check for null values - elem_type = SVMUtil.find_shared_types(elem_type, SVMUtil.get_rtt(elem)) - if (len(elem_type) > 0 and elem_type[0] == SVMUtil.object_type) or (0 <= svm_infer_generics.value <= i): + if not self.__svm_util.is_null(elem): # check for null values + elem_type = self.__svm_util.find_shared_types(elem_type, self.__svm_util.get_rtt(elem)) + # java.lang.Object will always be the last element in a type list + # if it is the only element in the list we cannot infer more than java.lang.Object + if (len(elem_type) > 0 and elem_type[0] == self.__svm_util.object_type) or (0 <= svm_infer_generics.value <= i): break return None if len(elem_type) == 0 else SVMUtil.get_unqualified_type_name(elem_type[0].name) @@ -853,15 +890,15 @@ def __iter__(self) -> gdb.Value: yield self.__data[i] def children(self) -> Iterable[object]: - trace(f' - children({self.__obj.type} @ {hex(adr(self.__obj))})') + trace(f' - children({self.__obj.type} @ {hex(self.__svm_util.get_adr(self.__obj))})') if self.__skip_children: return for index, elem in enumerate(self): if 0 <= svm_print_element_limit.value <= index: yield str(index), '...' return - trace(f' - children({self.__obj.type} @ {hex(adr(self.__obj))})[{index}]') - yield str(index), SVMUtil.add_selfref(self.__obj, elem) + trace(f' - children({self.__obj.type} @ {hex(self.__svm_util.get_adr(self.__obj))})[{index}]') + yield str(index), self.__svm_util.add_selfref(self.__obj, elem) SVMUtil.current_print_depth -= 1 @@ -869,24 +906,25 @@ def children(self) -> Iterable[object]: class HashMap: target_type = 'java.util.HashMap' - def __init__(self, obj: gdb.Value): - trace(f' - __init__({obj.type} @ {hex(adr(obj))})') + def __init__(self, svm_util: SVMUtil, obj: gdb.Value): + trace(f' - __init__({obj.type} @ {hex(svm_util.get_adr(obj))})') - self.__size = SVMUtil.get_int_field(obj, 'size') - table = SVMUtil.get_obj_field(obj, 'table') - if SVMUtil.is_null(table): + self.__size = svm_util.get_int_field(obj, 'size') + table = svm_util.get_obj_field(obj, 'table') + if svm_util.is_null(table): self.__data = None self.__table_len = 0 else: - self.__data = SVMUtil.get_obj_field(table, 'data', None) - if self.__data is not None and SVMUtil.is_null(self.__data): + self.__data = svm_util.get_obj_field(table, 'data', None) + if self.__data is not None and svm_util.is_null(self.__data): self.__data = None - self.__table_len = SVMUtil.get_int_field(table, 'len') + self.__table_len = svm_util.get_int_field(table, 'len') self.__obj = obj - self.__skip_children = SVMUtil.is_selfref(obj) or 0 <= svm_print_depth_limit.value <= SVMUtil.current_print_depth + self.__skip_children = svm_util.is_selfref(obj) or 0 <= svm_print_depth_limit.value <= SVMUtil.current_print_depth if not self.__skip_children: SVMUtil.current_print_depth += 1 + self.__svm_util = svm_util def to_string(self) -> str: trace(' - to_string') @@ -898,7 +936,7 @@ def to_string(self) -> str: if self.__skip_children: res += ' = {...}' if svm_print_address.with_adr: - res += SVMUtil.adr_str(self.__obj) + res += self.__svm_util.adr_str(self.__obj) trace(f' - to_string = {res}') return res @@ -909,12 +947,14 @@ def infer_generic_types(self) -> tuple: # (str, str): for i, kv in enumerate(self, 1): key, value = kv # if len(*_type) = 1 we could just infer the type java.lang.Object, ignore null values - if not SVMUtil.is_null(key) and (len(key_type) == 0 or key_type[0] != SVMUtil.object_type): - key_type = SVMUtil.find_shared_types(key_type, SVMUtil.get_rtt(key)) - if not SVMUtil.is_null(value) and (len(value_type) == 0 or value_type[0] != SVMUtil.object_type): - value_type = SVMUtil.find_shared_types(value_type, SVMUtil.get_rtt(value)) - if (0 <= svm_infer_generics.value <= i) or (len(key_type) > 0 and key_type[0] == SVMUtil.object_type and - len(value_type) > 0 and value_type[0] == SVMUtil.object_type): + if not self.__svm_util.is_null(key) and (len(key_type) == 0 or key_type[0] != self.__svm_util.object_type): + key_type = self.__svm_util.find_shared_types(key_type, self.__svm_util.get_rtt(key)) + if not self.__svm_util.is_null(value) and (len(value_type) == 0 or value_type[0] != self.__svm_util.object_type): + value_type = self.__svm_util.find_shared_types(value_type, self.__svm_util.get_rtt(value)) + # java.lang.Object will always be the last element in a type list + # if it is the only element in the list we cannot infer more than java.lang.Object + if (0 <= svm_infer_generics.value <= i) or (len(key_type) > 0 and key_type[0] == self.__svm_util.object_type and + len(value_type) > 0 and value_type[0] == self.__svm_util.object_type): break key_type_name = '?' if len(key_type) == 0 else SVMUtil.get_unqualified_type_name(key_type[0].name) @@ -930,34 +970,124 @@ def __iter__(self) -> tuple: # (gdb.Value, gdb.Value): trace(' - __iter__') for i in range(self.__table_len): obj = self.__data[i] - while not SVMUtil.is_null(obj): - key = SVMUtil.get_obj_field(obj, 'key') - value = SVMUtil.get_obj_field(obj, 'value') + while not self.__svm_util.is_null(obj): + key = self.__svm_util.get_obj_field(obj, 'key') + value = self.__svm_util.get_obj_field(obj, 'value') yield key, value - obj = SVMUtil.get_obj_field(obj, 'next') + obj = self.__svm_util.get_obj_field(obj, 'next') def children(self) -> Iterable[object]: - trace(f' - children({self.__obj.type} @ {hex(adr(self.__obj))})') + trace(f' - children({self.__obj.type} @ {hex(self.__svm_util.get_adr(self.__obj))})') if self.__skip_children: return for index, (key, value) in enumerate(self): if 0 <= svm_print_element_limit.value <= index: yield str(index), '...' return - trace(f' - children({self.__obj.type} @ {hex(adr(self.__obj))})[{index}]') - yield f"key{index}", SVMUtil.add_selfref(self.__obj, key) - yield f"value{index}", SVMUtil.add_selfref(self.__obj, value) + trace(f' - children({self.__obj.type} @ {hex(self.__svm_util.get_adr(self.__obj))})[{index}]') + yield f"key{index}", self.__svm_util.add_selfref(self.__obj, key) + yield f"value{index}", self.__svm_util.add_selfref(self.__obj, value) SVMUtil.current_print_depth -= 1 -def make_high_level_object(obj: gdb.Value, rtt_name: str) -> gdb.Value: +@HLRep +class EconomicMapImpl: + target_type = 'org.graalvm.collections.EconomicMapImpl' + + def __init__(self, svm_util: SVMUtil, obj: gdb.Value): + trace(f' - __init__({obj.type} @ {hex(svm_util.get_adr(obj))})') + + self.__size = svm_util.get_int_field(obj, 'totalEntries') - svm_util.get_int_field(obj, 'deletedEntries') + entries = svm_util.get_obj_field(obj, 'entries') + if svm_util.is_null(entries): + self.__data = None + self.__array_len = 0 + else: + self.__data = svm_util.get_obj_field(entries, 'data', None) + if self.__data is not None and svm_util.is_null(self.__data): + self.__data = None + self.__array_len = svm_util.get_int_field(entries, 'len') + + self.__obj = obj + self.__skip_children = svm_util.is_selfref(obj) or 0 <= svm_print_depth_limit.value <= SVMUtil.current_print_depth + if not self.__skip_children: + SVMUtil.current_print_depth += 1 + self.__svm_util = svm_util + + def to_string(self) -> str: + trace(' - to_string') + res = self.target_type + if svm_infer_generics.value != 0: + key_type, value_type = self.infer_generic_types() + res += f"<{key_type}, {value_type}>" + res += f'({self.__size})' + if self.__skip_children: + res += ' = {...}' + if svm_print_address.with_adr: + res += self.__svm_util.adr_str(self.__obj) + trace(f' - to_string = {res}') + return res + + def infer_generic_types(self) -> tuple: # (str, str): + key_type: list = [] # list[gdb.Type] + value_type: list = [] # list[gdb.Type] + + for i, kv in enumerate(self, 1): + key, value = kv + # if len(*_type) = 1 we could just infer the type java.lang.Object, ignore null values + if not self.__svm_util.is_null(key) and (len(key_type) == 0 or key_type[0] != self.__svm_util.object_type): + key_type = self.__svm_util.find_shared_types(key_type, self.__svm_util.get_rtt(key)) + if not self.__svm_util.is_null(value) and (len(value_type) == 0 or value_type[0] != self.__svm_util.object_type): + value_type = self.__svm_util.find_shared_types(value_type, self.__svm_util.get_rtt(value)) + # java.lang.Object will always be the last element in a type list + # if it is the only element in the list we cannot infer more than java.lang.Object + if (0 <= svm_infer_generics.value <= i) or (len(key_type) > 0 and key_type[0] == self.__svm_util.object_type and + len(value_type) > 0 and value_type[0] == self.__svm_util.object_type): + break + + key_type_name = '?' if len(key_type) == 0 else SVMUtil.get_unqualified_type_name(key_type[0].name) + value_type_name = '?' if len(value_type) == 0 else SVMUtil.get_unqualified_type_name(value_type[0].name) + + return key_type_name, value_type_name + + def display_hint(self) -> str: + trace(' - display_hint = map') + return "map" + + def __iter__(self) -> tuple: # (gdb.Value, gdb.Value): + trace(' - __iter__') + key = 0 + for i in range(self.__array_len): + if i % 2 == 0: + if self.__svm_util.is_null(self.__data[i]): + break + key = self.__data[i] + else: + value = self.__data[i] + yield key, value + + def children(self) -> Iterable[object]: + trace(f' - children({self.__obj.type} @ {hex(self.__svm_util.get_adr(self.__obj))})') + if self.__skip_children: + return + for index, (key, value) in enumerate(self): + if 0 <= svm_print_element_limit.value <= index: + yield str(index), '...' + return + trace(f' - children({self.__obj.type} @ {hex(self.__svm_util.get_adr(self.__obj))})[{index}]') + yield f"key{index}", self.__svm_util.add_selfref(self.__obj, key) + yield f"value{index}", self.__svm_util.add_selfref(self.__obj, value) + SVMUtil.current_print_depth -= 1 + + +def make_high_level_object(svm_util: SVMUtil, obj: gdb.Value, rtt_name: str) -> gdb.Value: try: trace(f'try makeHighLevelObject for {rtt_name}') hl_rep_class = SVMUtil.hlreps[rtt_name] - return hl_rep_class(obj) + return hl_rep_class(svm_util, obj) except Exception as ex: trace(f' exception: {ex}') - return SVMPPClass(obj) + return SVMPPClass(svm_util, obj) class SVMPrintParam(gdb.Parameter): @@ -1217,16 +1347,16 @@ def __init__(self, complete): # complete: list[str] | int def __init__(self): super().__init__('p', gdb.COMMAND_DATA) + self.svm_util = SVMUtil() - @staticmethod - def cast_to_rtt(obj: gdb.Value, obj_str: str) -> tuple: # tuple[gdb.Value, str]: + def cast_to_rtt(self, obj: gdb.Value, obj_str: str) -> tuple: # tuple[gdb.Value, str]: static_type = SVMUtil.get_basic_type(obj.type) - rtt = SVMUtil.get_rtt(obj) - obj = SVMUtil.cast_to(obj, rtt) + rtt = self.svm_util.get_rtt(obj) + obj = self.svm_util.cast_to(obj, rtt) if static_type.name == rtt.name: return obj, obj_str else: - obj_oop = SVMUtil.get_compressed_oop(obj) if SVMUtil.is_compressed(rtt) else adr(obj) + obj_oop = self.svm_util.get_compressed_oop(obj) if self.svm_util.is_compressed(rtt) else self.svm_util.get_adr(obj) return obj, f"(('{rtt.name}' *)({obj_oop}))" # Define the token specifications @@ -1272,6 +1402,7 @@ def check(self, expected: str): raise RuntimeError(f"{expected} expected after {self.expr[:self.t.end]} but got {self.sym}") def parse(self, completion: bool = False) -> str: + self.svm_util = SVMUtil() self.scan() if self.sym == "" and completion: raise self.AutoComplete(gdb.COMPLETE_EXPRESSION) @@ -1305,8 +1436,8 @@ def object(self, completion: bool = False) -> str: # could not parse obj_str as obj -> let gdb deal with it later return self.t.val base_obj_str = obj_str - if not SVMUtil.is_primitive(obj.type) and SVMUtil.is_java_type(obj.type): - obj, obj_str = SVMCommandPrint.cast_to_rtt(obj, obj_str) + if not SVMUtil.is_primitive(obj.type) and self.svm_util.is_java_type(obj.type): + obj, obj_str = self.cast_to_rtt(obj, obj_str) self.cache[base_obj_str] = (obj, obj_str) while self.sym == "FA" or self.sym == "LPAREN" or self.sym == "LBRACK": @@ -1329,8 +1460,8 @@ def object(self, completion: bool = False) -> str: obj = obj[self.t.val] obj_str += "." + self.t.val base_obj_str = obj_str - if not SVMUtil.is_primitive(obj.type) and SVMUtil.is_java_type(obj.type): - obj, obj_str = SVMCommandPrint.cast_to_rtt(obj, obj_str) + if not SVMUtil.is_primitive(obj.type) and self.svm_util.is_java_type(obj.type): + obj, obj_str = self.cast_to_rtt(obj, obj_str) self.cache[base_obj_str] = (obj, obj_str) elif self.sym == "LPAREN": if obj.type.code != gdb.TYPE_CODE_METHOD: @@ -1348,8 +1479,8 @@ def object(self, completion: bool = False) -> str: obj_str += f"({param_str})" obj = gdb.parse_and_eval(obj_str) base_obj_str = obj_str - if not SVMUtil.is_primitive(obj.type) and SVMUtil.is_java_type(obj.type): - obj, obj_str = SVMCommandPrint.cast_to_rtt(obj, obj_str) + if not SVMUtil.is_primitive(obj.type) and self.svm_util.is_java_type(obj.type): + obj, obj_str = self.cast_to_rtt(obj, obj_str) self.cache[base_obj_str] = (obj, obj_str) elif self.sym == "LBRACK": is_array = obj.type.is_array_like or isinstance(gdb.default_visualizer(obj), SVMPPArray) @@ -1361,9 +1492,9 @@ def object(self, completion: bool = False) -> str: i_obj_str = self.array_index(completion) if self.sym == "" and completion: # handle autocompletion for array index - if SVMUtil.is_java_type(obj.type) and (i_obj_str == '' or i_obj_str.isnumeric()): + if self.svm_util.is_java_type(obj.type) and (i_obj_str == '' or i_obj_str.isnumeric()): index = 0 if i_obj_str == '' else int(i_obj_str) - length = SVMUtil.get_int_field(obj, 'len') + length = self.svm_util.get_int_field(obj, 'len') complete = [] if index < length: complete.append(f'{index}]') @@ -1379,19 +1510,19 @@ def object(self, completion: bool = False) -> str: else: i_obj = gdb.parse_and_eval(i_obj_str) self.check('RBRACK') - if is_array and SVMUtil.is_java_type(obj.type): + if is_array and self.svm_util.is_java_type(obj.type): obj_str += ".data" - obj = SVMUtil.get_obj_field(obj, 'data', obj) + obj = self.svm_util.get_obj_field(obj, 'data', obj) if isinstance(gdb.default_visualizer(i_obj), SVMPPBoxedPrimitive) or SVMUtil.is_primitive(i_obj.type): if isinstance(gdb.default_visualizer(i_obj), SVMPPBoxedPrimitive): - index = SVMUtil.get_int_field(i_obj, 'value') + index = self.svm_util.get_int_field(i_obj, 'value') else: index = int(i_obj) obj_str += f"[{index}]" obj = obj[index] base_obj_str = obj_str - if not SVMUtil.is_primitive(obj.type) and SVMUtil.is_java_type(obj.type): - obj, obj_str = SVMCommandPrint.cast_to_rtt(obj, obj_str) + if not SVMUtil.is_primitive(obj.type) and self.svm_util.is_java_type(obj.type): + obj, obj_str = self.cast_to_rtt(obj, obj_str) self.cache[base_obj_str] = (obj, obj_str) else: # let gdb figure out what to do @@ -1401,8 +1532,8 @@ def object(self, completion: bool = False) -> str: else: obj = gdb.parse_and_eval(obj_str) base_obj_str = obj_str - if not SVMUtil.is_primitive(obj.type) and SVMUtil.is_java_type(obj.type): - obj, obj_str = SVMCommandPrint.cast_to_rtt(obj, obj_str) + if not SVMUtil.is_primitive(obj.type) and self.svm_util.is_java_type(obj.type): + obj, obj_str = self.cast_to_rtt(obj, obj_str) self.cache[base_obj_str] = (obj, obj_str) if isinstance(gdb.default_visualizer(obj), SVMPPBoxedPrimitive): @@ -1422,9 +1553,9 @@ def params(self, completion: bool = False) -> str: obj_str += self.t.val obj = gdb.parse_and_eval(obj_str) # check if gdb can handle the current param - if SVMUtil.is_java_type(obj.type) and SVMUtil.is_compressed(obj.type): + if self.svm_util.is_java_type(obj.type) and self.svm_util.is_compressed(obj.type): # uncompress compressed java params - obj_str = f"(('{SVMUtil.get_uncompressed_type(SVMUtil.get_basic_type(obj.type)).name}' *)({adr(obj)}))" + obj_str = f"(('{self.svm_util.get_uncompressed_type(SVMUtil.get_basic_type(obj.type)).name}' *)({self.svm_util.get_adr(obj)}))" param_str += obj_str if self.sym == "COMMA": self.scan() @@ -1498,59 +1629,86 @@ def invoke(self, arg: str, from_tty: bool) -> None: class SVMFrameUnwinder(gdb.unwinder.Unwinder): - AMD64_RBP = 6 - AMD64_RSP = 7 - AMD64_RIP = 16 - def __init__(self): + def __init__(self, svm_util: SVMUtil): super().__init__('SubstrateVM FrameUnwinder') - self.stack_type = gdb.lookup_type('long') - self.deopt_frame_type = gdb.lookup_type('com.oracle.svm.core.deopt.DeoptimizedFrame') - - def __call__(self, pending_frame): - if SVMUtil.deopt_stub_adr == 0: - # find deopt stub after its properly loaded - SVMUtil.deopt_stub_adr = gdb.lookup_global_symbol('com.oracle.svm.core.deopt.Deoptimizer::deoptStub', - gdb.SYMBOL_VAR_DOMAIN).value().address - + self.eager_deopt_stub_adr = None + self.lazy_deopt_stub_primitive_adr = None + self.lazy_deopt_stub_object_adr = None + self.svm_util = svm_util + + def __call__(self, pending_frame: gdb.Frame): + if self.eager_deopt_stub_adr is None: + self.eager_deopt_stub_adr = SVMUtil.get_eager_deopt_stub_adr() + self.lazy_deopt_stub_primitive_adr = SVMUtil.get_lazy_deopt_stub_primitive_adr() + self.lazy_deopt_stub_object_adr = SVMUtil.get_lazy_deopt_stub_object_adr() + + sp = 0 try: - rsp = pending_frame.read_register('sp') - rip = pending_frame.read_register('pc') - if int(rip) == SVMUtil.deopt_stub_adr: - deopt_frame_stack_slot = rsp.cast(self.stack_type.pointer()).dereference() - deopt_frame = deopt_frame_stack_slot.cast(self.deopt_frame_type.pointer()) - source_frame_size = deopt_frame['sourceTotalFrameSize'] + sp = pending_frame.read_register('sp') + pc = pending_frame.read_register('pc') + if int(pc) == self.eager_deopt_stub_adr: + deopt_frame_stack_slot = sp.cast(self.svm_util.stack_type.pointer()).dereference() + deopt_frame = deopt_frame_stack_slot.cast(self.svm_util.get_compressed_type(self.svm_util.object_type).pointer()) + rtt = self.svm_util.get_rtt(deopt_frame) + deopt_frame = self.svm_util.cast_to(deopt_frame, rtt) + encoded_frame_size = self.svm_util.get_int_field(deopt_frame, 'sourceEncodedFrameSize') + source_frame_size = encoded_frame_size & ~self.svm_util.frame_size_status_mask + + # Now find the register-values for the caller frame + unwind_info = pending_frame.create_unwind_info(gdb.unwinder.FrameId(sp, pc)) + caller_sp = sp + int(source_frame_size) + unwind_info.add_saved_register('sp', gdb.Value(caller_sp)) + # try to fetch return address directly from stack + caller_pc = gdb.Value(caller_sp - 8).cast(self.svm_util.stack_type.pointer()).dereference() + unwind_info.add_saved_register('pc', gdb.Value(caller_pc)) + return unwind_info + elif int(pc) == self.lazy_deopt_stub_primitive_adr or int(pc) == self.lazy_deopt_stub_object_adr: # Now find the register-values for the caller frame - unwind_info = pending_frame.create_unwind_info(gdb.unwinder.FrameId(rsp, rip)) - caller_rsp = rsp + int(source_frame_size) - unwind_info.add_saved_register(self.AMD64_RSP, gdb.Value(caller_rsp)) - caller_rip = gdb.Value(caller_rsp - 8).cast(self.stack_type.pointer()).dereference() - unwind_info.add_saved_register(self.AMD64_RIP, gdb.Value(caller_rip)) + # We only have the original pc for lazy deoptimization -> unwind to original pc with same sp + # This is the best guess we can make without knowing the return address and frame size of the lazily deoptimized frame + unwind_info = pending_frame.create_unwind_info(gdb.unwinder.FrameId(sp, pc)) + unwind_info.add_saved_register('sp', gdb.Value(sp)) + caller_pc = sp.cast(self.svm_util.stack_type.pointer()).dereference() + unwind_info.add_saved_register('pc', gdb.Value(caller_pc)) return unwind_info - except Exception as e: - print(e) + except Exception as ex: + trace(f' - Failed to unwind frame at {hex(sp)}') + trace(ex) # Fallback to default frame unwinding via debug_frame (dwarf) return None -class SVMFrameFilter(): - def __init__(self): +class SVMFrameFilter: + def __init__(self, svm_util: SVMUtil): self.name = "SubstrateVM FrameFilter" self.priority = 100 self.enabled = True + self.eager_deopt_stub_adr = None + self.lazy_deopt_stub_primitive_adr = None + self.lazy_deopt_stub_object_adr = None + self.svm_util = svm_util + + def filter(self, frame_iter: Iterable) -> FrameDecorator: + if self.eager_deopt_stub_adr is None: + self.eager_deopt_stub_adr = SVMUtil.get_eager_deopt_stub_adr() + self.lazy_deopt_stub_primitive_adr = SVMUtil.get_lazy_deopt_stub_primitive_adr() + self.lazy_deopt_stub_object_adr = SVMUtil.get_lazy_deopt_stub_object_adr() - def filter(self, frame_iter): for frame in frame_iter: frame = frame.inferior_frame() - if SVMUtil.deopt_stub_adr and frame.pc() == SVMUtil.deopt_stub_adr: - yield SVMFrameDeopt(frame) + pc = int(frame.pc()) + if pc == self.eager_deopt_stub_adr: + yield SVMFrameEagerDeopt(self.svm_util, frame) + elif pc == self.lazy_deopt_stub_primitive_adr or pc == self.lazy_deopt_stub_object_adr: + yield SVMFrameLazyDeopt(self.svm_util, frame) else: yield SVMFrame(frame) class SVMFrame(FrameDecorator): - def function(self): + def function(self) -> str: frame = self.inferior_frame() if not frame.name(): return 'Unknown Frame at ' + hex(int(frame.read_register('sp'))) @@ -1570,17 +1728,124 @@ def function(self): return func_name + eclipse_filename -class SVMFrameDeopt(SVMFrame): - def function(self): - return '[DEOPT FRAMES ...]' +class SymValueWrapper: + + def __init__(self, symbol, value): + self.sym = symbol + self.val = value + + def value(self): + return self.val + + def symbol(self): + return self.sym + + +class SVMFrameEagerDeopt(SVMFrame): + + def __init__(self, svm_util: SVMUtil, frame: gdb.Frame): + super().__init__(frame) + + # fetch deoptimized frame from stack + sp = frame.read_register('sp') + deopt_frame_stack_slot = sp.cast(svm_util.stack_type.pointer()).dereference() + deopt_frame = deopt_frame_stack_slot.cast(svm_util.get_compressed_type(svm_util.object_type).pointer()) + rtt = svm_util.get_rtt(deopt_frame) + deopt_frame = svm_util.cast_to(deopt_frame, rtt) + self.__virtual_frame = svm_util.get_obj_field(deopt_frame, 'topFrame') + self.__frame_info = svm_util.get_obj_field(self.__virtual_frame, 'frameInfo') + self.__svm_util = svm_util + + def function(self) -> str: + if self.__frame_info is None or self.__svm_util.is_null(self.__frame_info): + # we have no more information about the frame + return '[EAGER DEOPT FRAME ...]' + + # read from deoptimized frame + source_class = self.__svm_util.get_obj_field(self.__frame_info, 'sourceClass') + if self.__svm_util.is_null(source_class): + source_class_name = '' + else: + source_class_name = str(self.__svm_util.get_obj_field(source_class, 'name'))[1:-1] + if len(source_class_name) > 0: + source_class_name = source_class_name + '::' + + source_file_name = self.filename() + if source_file_name is None or len(source_file_name) == 0: + source_file_name = '' + else: + line = self.line() + if line is not None and line != 0: + source_file_name = source_file_name + ':' + str(line) + source_file_name = '(' + source_file_name + ')' + + func_name = str(self.__svm_util.get_obj_field(self.__frame_info, 'sourceMethodName'))[1:-1] + + return '[EAGER DEOPT FRAME] ' + source_class_name + func_name + source_file_name + + def filename(self): + if self.__frame_info is None or self.__svm_util.is_null(self.__frame_info): + return None + + source_class = self.__svm_util.get_obj_field(self.__frame_info, 'sourceClass') + companion = self.__svm_util.get_obj_field(source_class, 'companion') + source_file_name = self.__svm_util.get_obj_field(companion, 'sourceFileName') + + if self.__svm_util.is_null(source_file_name): + source_file_name = '' + else: + source_file_name = str(source_file_name)[1:-1] + + return source_file_name + + def line(self): + if self.__frame_info is None or self.__svm_util.is_null(self.__frame_info): + return None + return self.__svm_util.get_int_field(self.__frame_info, 'sourceLineNumber') def frame_args(self): - return None + if self.__frame_info is None or self.__svm_util.is_null(self.__frame_info): + return None + + values = self.__svm_util.get_obj_field(self.__virtual_frame, 'values') + data = self.__svm_util.get_obj_field(values, 'data') + length = self.__svm_util.get_int_field(values, 'len') + args = [SymValueWrapper('deoptFrameValues', length)] + if self.__svm_util.is_null(data) or length == 0: + return args + + for i in range(length): + elem = data[i] + rtt = self.__svm_util.get_rtt(elem) + elem = self.__svm_util.cast_to(elem, rtt) + value = self.__svm_util.get_obj_field(elem, 'value') + args.append(SymValueWrapper(f'__{i}', value)) + + return args def frame_locals(self): return None +class SVMFrameLazyDeopt(SVMFrame): + + def __init__(self, svm_util: SVMUtil, frame: gdb.Frame): + super().__init__(frame) + + # fetch deoptimized frame from stack + sp = frame.read_register('sp') + real_pc = sp.cast(svm_util.stack_type.pointer()).dereference().cast(svm_util.stack_type.pointer()) + self.__gdb_text = str(real_pc) + self.__svm_util = svm_util + + def function(self) -> str: + if self.__gdb_text is None: + # we have no more information about the frame + return '[LAZY DEOPT FRAME ...]' + + return '[LAZY DEOPT FRAME] at ' + self.__gdb_text + + try: svminitfile = os.path.expandvars('${SVMGDBINITFILE}') exec(open(svminitfile).read()) @@ -1588,35 +1853,39 @@ def frame_locals(self): except Exception as e: trace(f'') + +def register_objfile(objfile: gdb.Objfile): + svm_util = SVMUtil() + gdb.printing.register_pretty_printer(objfile, SVMPrettyPrinter(svm_util), True) + + # deopt stub points to the wrong address at first -> fill later when needed + deopt_stub_available = gdb.lookup_global_symbol('com.oracle.svm.core.deopt.Deoptimizer', + gdb.SYMBOL_VAR_DOMAIN) is not None + if deopt_stub_available: + gdb.unwinder.register_unwinder(objfile, SVMFrameUnwinder(svm_util)) + + frame_filter = SVMFrameFilter(svm_util) + objfile.frame_filters[frame_filter.name] = frame_filter + + try: gdb.prompt_hook = SVMUtil.prompt_hook svm_objfile = gdb.current_objfile() # Only if we have an objfile and an SVM specific symbol we consider this an SVM objfile if svm_objfile and svm_objfile.lookup_global_symbol("com.oracle.svm.core.Isolates"): - gdb.printing.register_pretty_printer(svm_objfile, SVMPrettyPrinter(), True) - - # deopt stub points to the wrong address at first -> set dummy value to fill later (0 from SVMUtil) - deopt_stub_available = gdb.lookup_global_symbol('com.oracle.svm.core.deopt.Deoptimizer::deoptStub', - gdb.SYMBOL_VAR_DOMAIN) - - if deopt_stub_available: - SVMUtil.frame_unwinder = SVMFrameUnwinder() - gdb.unwinder.register_unwinder(svm_objfile, SVMUtil.frame_unwinder) - - SVMUtil.frame_filter = SVMFrameFilter() - svm_objfile.frame_filters[SVMUtil.frame_filter.name] = SVMUtil.frame_filter + register_objfile(svm_objfile) else: print(f'Warning: Load {os.path.basename(__file__)} only in the context of an SVM objfile') # fallback (e.g. if loaded manually -> look through all objfiles and attach pretty printer) - for of in gdb.objfiles(): - if of.lookup_global_symbol("com.oracle.svm.core.Isolates"): - gdb.printing.register_pretty_printer(of, SVMPrettyPrinter(), True) + for objfile in gdb.objfiles(): + if objfile.lookup_global_symbol("com.oracle.svm.core.Isolates"): + register_objfile(objfile) # save and restore SVM pretty printer for reloaded objfiles (e.g. shared libraries) def new_objectfile(new_objfile_event): objfile = new_objfile_event.new_objfile if objfile.filename in SVMUtil.pretty_print_objfiles: - gdb.printing.register_pretty_printer(objfile, SVMPrettyPrinter(), True) + register_objfile(objfile) def free_objectfile(free_objfile_event): objfile = free_objfile_event.objfile diff --git a/substratevm/debug/include/gdb_jit_compilation_interface.h b/substratevm/debug/include/gdb_jit_compilation_interface.h new file mode 100644 index 000000000000..a4b01649bd71 --- /dev/null +++ b/substratevm/debug/include/gdb_jit_compilation_interface.h @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#ifndef SVM_NATIVE_GDBJITCOMPILATIONINTERFACE_H +#define SVM_NATIVE_GDBJITCOMPILATIONINTERFACE_H + +// This header specifies the types used by the GDB JIT compilation interface (see https://sourceware.org/gdb/current/onlinedocs/gdb.html/Declarations.html#Declarations) +// The implementation of the JIT compilation interface is located in com.oracle.svm.core.debug.GdbJitInterface. + +#include + +typedef enum +{ + JIT_NOACTION = 0, + JIT_REGISTER, + JIT_UNREGISTER +} jit_actions_t; + +struct jit_code_entry +{ + struct jit_code_entry *next_entry; + struct jit_code_entry *prev_entry; + const char *symfile_addr; + uint64_t symfile_size; +}; + +struct jit_descriptor +{ + uint32_t version; + /* This type should be jit_actions_t, but we use uint32_t + to be explicit about the bitwidth. */ + uint32_t action_flag; + struct jit_code_entry *relevant_entry; + struct jit_code_entry *first_entry; +}; + +#endif diff --git a/substratevm/mx.substratevm/gdb_utils.py b/substratevm/mx.substratevm/gdb_utils.py index 1499bf28678f..47d7d9c33af6 100644 --- a/substratevm/mx.substratevm/gdb_utils.py +++ b/substratevm/mx.substratevm/gdb_utils.py @@ -110,6 +110,8 @@ def check(self, text, skip_fails=True): for i in range(0, num_rexps): rexp = rexps[i] match = None + if skip_fails: + line_idx = 0 while line_idx < num_lines and match is None: line = lines[line_idx] match = rexp.match(line) diff --git a/substratevm/mx.substratevm/mx_substratevm.py b/substratevm/mx.substratevm/mx_substratevm.py index ab9dcbf8b6e4..5057a0d3801d 100644 --- a/substratevm/mx.substratevm/mx_substratevm.py +++ b/substratevm/mx.substratevm/mx_substratevm.py @@ -1045,6 +1045,20 @@ def build_debug_test(variant_name, image_name, extra_args): mx.run([os.environ.get('GDB_BIN', 'gdb'), '--nx', '-q', '-iex', 'set pagination off', '-ex', 'python "ISOLATES=True"', '-x', testhello_py, hello_binary]) +def gdb_base_command(logfile, autoload_path): + return [ + os.environ.get('GDB_BIN', 'gdb'), + '--nx', + '-q', # do not print the introductory and copyright messages + '-iex', 'set pagination off', # messages from enabling logging could already cause pagination, so this must be done first + '-iex', 'set logging redirect on', + '-iex', 'set logging overwrite off', + '-iex', f"set logging file {logfile}", + '-iex', 'set logging enabled on', + '-iex', f"set auto-load safe-path {autoload_path}", + ] + + def _gdbdebughelperstest(native_image, path, with_isolates_only, args): # ====== check gdb version ====== @@ -1094,15 +1108,6 @@ def _gdbdebughelperstest(native_image, path, with_isolates_only, args): 'com.oracle.svm.test.debug.helper.ClassLoaderTest' ] - gdb_args = [ - os.environ.get('GDB_BIN', 'gdb'), - '--nx', - '-q', # do not print the introductory and copyright messages - '-iex', 'set pagination off', # messages from enabling logging could already cause pagination, so this must be done first - '-iex', 'set logging redirect on', - '-iex', 'set logging overwrite off', - ] - def run_debug_test(image_name: str, testfile: str, source_path: str, with_isolates: bool = True, build_cinterfacetutorial: bool = False, extra_args: list = None, skip_build: bool = False) -> int: @@ -1138,7 +1143,7 @@ def run_debug_test(image_name: str, testfile: str, source_path: str, with_isolat else: build_args += ['-o', join(build_dir, image_name)] - mx.log(f"native_image {' '.join(build_args)}") + mx.log(f"native-image {' '.join(build_args)}") native_image(build_args) if build_cinterfacetutorial: @@ -1151,18 +1156,13 @@ def run_debug_test(image_name: str, testfile: str, source_path: str, with_isolat else: c_command = ['cl', '-MD', join(tutorial_c_source_dir, 'cinterfacetutorial.c'), '-I.', 'libcinterfacetutorial.lib'] - mx.log(' '.join(c_command)) mx.run(c_command, cwd=build_dir) if mx.get_os() == 'linux': logfile = join(path, pathlib.Path(testfile).stem + ('' if with_isolates else '_no_isolates') + '.log') - os.environ.update({'gdbdebughelperstest_logfile': logfile}) - gdb_command = gdb_args + [ - '-iex', f"set logging file {logfile}", - '-iex', 'set logging enabled on', - '-iex', f"set auto-load safe-path {join(build_dir, 'gdb-debughelpers.py')}", + os.environ.update({'gdb_logfile': logfile}) + gdb_command = gdb_base_command(logfile, join(build_dir, 'gdb-debughelpers.py')) + [ '-x', testfile, join(build_dir, image_name) ] - mx.log(' '.join(gdb_command)) # unittest may result in different exit code, nonZeroIsFatal ensures that we can go on with other test return mx.run(gdb_command, cwd=build_dir, nonZeroIsFatal=False) return 0 @@ -1189,6 +1189,82 @@ def run_debug_test(image_name: str, testfile: str, source_path: str, with_isolat mx.abort(status) +def _runtimedebuginfotest(native_image, output_path, with_isolates_only, args=None): + """Build and run the runtimedebuginfotest""" + + args = [] if args is None else args + + test_proj = mx.dependency('com.oracle.svm.test') + test_source_path = test_proj.source_dirs()[0] + + test_python_source_dir = join(test_source_path, 'com', 'oracle', 'svm', 'test', 'debug', 'helper') + test_runtime_compilation_py = join(test_python_source_dir, 'test_runtime_compilation.py') + test_runtime_deopt_py = join(test_python_source_dir, 'test_runtime_deopt.py') + testdeopt_js = join(suite.dir, 'mx.substratevm', 'testdeopt.js') + + # clean / create output directory + if exists(output_path): + mx.rmtree(output_path) + mx_util.ensure_dir_exists(output_path) + + # Build the native image from Java code + build_args = [ + '-g', '-O0', + # set property controlling inclusion of foreign struct header + '-DbuildDebugInfoTestExample=true', + '--native-compiler-options=-I' + test_source_path, + '-o', join(output_path, 'runtimedebuginfotest'), + '-cp', classpath('com.oracle.svm.test'), + # We do not want to step into class initializer, so initialize everything at build time. + '--initialize-at-build-time=com.oracle.svm.test.debug.helper', + '--features=com.oracle.svm.test.debug.helper.RuntimeCompileDebugInfoTest$RegisterMethodsFeature', + 'com.oracle.svm.test.debug.helper.RuntimeCompileDebugInfoTest', + ] + svm_experimental_options([ + '-H:DebugInfoSourceSearchPath=' + test_source_path, + '-H:+SourceLevelDebug', + '-H:+RuntimeDebugInfo', + ]) + args + + mx.log(f"native-image {' '.join(build_args)}") + runtime_compile_binary = native_image(build_args) + + logfile = join(output_path, 'test_runtime_compilation.log') + os.environ.update({'gdb_logfile': logfile}) + gdb_command = gdb_base_command(logfile, join(output_path, 'gdb-debughelpers.py')) + [ + '-x', test_runtime_compilation_py, runtime_compile_binary + ] + # unittest may result in different exit code, nonZeroIsFatal ensures that we can go on with other test + status = mx.run(gdb_command, cwd=output_path, nonZeroIsFatal=False) + + def run_js_test(eager: bool = False): + jslib = mx.add_lib_suffix(native_image( + args + + svm_experimental_options([ + '-H:+SourceLevelDebug', + '-H:+RuntimeDebugInfo', + '-H:+LazyDeoptimization' if eager else '-H:-LazyDeoptimization', + ]) + + ['-g', '-O0', '--macro:jsvm-library'] + )) + js_launcher = get_js_launcher(jslib) + logfile = join(output_path, 'test_runtime_deopt_' + ('eager' if eager else 'lazy') + '.log') + os.environ.update({'gdb_logfile': logfile}) + gdb_command = gdb_base_command(logfile, join(output_path, 'gdb-debughelpers.py')) + [ + '-x', test_runtime_deopt_py, '--args', js_launcher, testdeopt_js + ] + # unittest may result in different exit code, nonZeroIsFatal ensures that we can go on with other test + return mx.run(gdb_command, cwd=output_path, nonZeroIsFatal=False) + + # G1 does not work for the jsvm library + # avoid complications with '-H:+ProtectionKeys' which is not compatible with '-H:-SpawnIsolates' and '-H:-UseCompressedReferences' + if '--gc=G1' not in args and '-H:-UseCompressedReferences' not in args and '-H:-SpawnIsolates' not in args: + status |= run_js_test() + status |= run_js_test(True) + + if status != 0: + mx.abort(status) + + def _javac_image(native_image, path, args=None): args = [] if args is None else args mx_util.ensure_dir_exists(path) @@ -1766,6 +1842,28 @@ def gdbdebughelperstest(args, config=None): config=config ) + +@mx.command(suite.name, 'runtimedebuginfotest', 'Runs the runtime debuginfo generation test') +def runtimedebuginfotest(args, config=None): + """ + runs a native image that compiles code and creates debug info at runtime. + """ + parser = ArgumentParser(prog='mx runtimedebuginfotest') + all_args = ['--output-path', '--with-isolates-only'] + masked_args = [_mask(arg, all_args) for arg in args] + parser.add_argument(all_args[0], metavar='', nargs=1, help='Path of the generated image', default=[join(svmbuild_dir(), "runtimedebuginfotest")]) + parser.add_argument(all_args[1], action='store_true', help='Only build and test the native image with isolates') + parser.add_argument('image_args', nargs='*', default=[]) + parsed = parser.parse_args(masked_args) + output_path = unmask(parsed.output_path)[0] + with_isolates_only = parsed.with_isolates_only + native_image_context_run( + lambda native_image, a: + _runtimedebuginfotest(native_image, output_path, with_isolates_only, a), unmask(parsed.image_args), + config=config + ) + + @mx.command(suite_name=suite.name, command_name='helloworld', usage_msg='[options]') def helloworld(args, config=None): """ @@ -1858,6 +1956,7 @@ def build_and_test_java_agent_image(native_image, args): native_image_context_run(build_and_test_java_agent_image, args) + @mx.command(suite.name, 'clinittest', 'Runs the ') def clinittest(args): def build_and_test_clinittest_image(native_image, args): diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index 645f76fe795f..2b34b42c5a96 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -361,6 +361,7 @@ "dependencies": [ "com.oracle.svm.common", "com.oracle.svm.shaded.org.objectweb.asm", + "com.oracle.objectfile", "SVM_CONFIGURE", "espresso-shared:ESPRESSO_SVM", ], @@ -738,7 +739,6 @@ "subDir": "src", "sourceDirs": ["src"], "dependencies": [ - "com.oracle.objectfile", "com.oracle.graal.reachability", "com.oracle.svm.core.graal.amd64", "com.oracle.svm.shaded.org.capnproto", @@ -1152,6 +1152,10 @@ "jdk.internal.misc", "sun.security.jca", ], + "jdk.internal.vm.ci" : [ + "jdk.vm.ci.code", + "jdk.vm.ci.meta", + ], }, "checkstyle": "com.oracle.svm.test", "checkstyleVersion" : "10.21.0", @@ -1971,6 +1975,8 @@ "com.oracle.objectfile", "com.oracle.objectfile.io", "com.oracle.objectfile.debuginfo", + "com.oracle.objectfile.debugentry", + "com.oracle.objectfile.debugentry.range", "com.oracle.objectfile.macho", ], @@ -2101,6 +2107,7 @@ "dependency:com.oracle.svm.native.libchelper/*", "dependency:com.oracle.svm.native.jvm.posix/*", "dependency:com.oracle.svm.native.libcontainer/*", + "file:debug/include", ], }, }, @@ -2109,6 +2116,7 @@ # on all other os's we don't want libc specific subdirectories "include/": [ "dependency:com.oracle.svm.native.libchelper/include/*", + "file:debug/include/*", ], "-/": [ "dependency:com.oracle.svm.native.libchelper/-/default/*", diff --git a/substratevm/mx.substratevm/testdeopt.js b/substratevm/mx.substratevm/testdeopt.js new file mode 100644 index 000000000000..05e3e991619c --- /dev/null +++ b/substratevm/mx.substratevm/testdeopt.js @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +function add(a, b, test) { + if (test) { + a += b; + } + return a + b; +} + +// trigger compilation add for ints and test = true +for (let i = 0; i < 1000 * 1000; i++) { + add(i, i, true); +} + +// deoptimize with failed assumption in compiled method +// then trigger compilation again +console.log("deopt1") +for (let i = 0; i < 1000 * 1000; i++) { + add(i, i, false); +} + +// deoptimize with different parameter types +console.log("deopt2"); +add({f1: "test1", f2: 2}, {x: "x", y: {test: 42}}, false); diff --git a/substratevm/mx.substratevm/testhello.py b/substratevm/mx.substratevm/testhello.py index 1eb5418a1839..046211164acc 100644 --- a/substratevm/mx.substratevm/testhello.py +++ b/substratevm/mx.substratevm/testhello.py @@ -147,25 +147,25 @@ def test(): # check incoming parameters are bound to sensible values exec_string = execute("info args") - rexp = [fr"__0 = {digits_pattern}", - fr"__1 = 0x{hex_digits_pattern}"] + rexp = [fr"__int0 = {digits_pattern}", + fr"__long1 = 0x{hex_digits_pattern}"] checker = Checker(f"info args : {method_name}", rexp) checker.check(exec_string) - exec_string = execute("p __0") + exec_string = execute("p __int0") rexp = [fr"\${digits_pattern} = 1"] - checker = Checker("p __0", rexp) + checker = Checker("p __int0", rexp) checker.check(exec_string) - exec_string = execute("p __1") + exec_string = execute("p __long1") rexp = [fr"\${digits_pattern} = \(org\.graalvm\.nativeimage\.c\.type\.CCharPointerPointer\) 0x{hex_digits_pattern}"] - checker = Checker("p __1", rexp) + checker = Checker("p __long1", rexp) checker.check(exec_string) - exec_string = execute("p __1[0]") + exec_string = execute("p __long1[0]") rexp = [ fr'\${digits_pattern} = \(org\.graalvm\.nativeimage\.c\.type\.CCharPointer\) 0x{hex_digits_pattern} "{wildcard_pattern}/hello_image"'] - checker = Checker("p __1[0]", rexp) + checker = Checker("p __long1[0]", rexp) checker.check(exec_string) # set a break point at hello.Hello::main @@ -971,7 +971,7 @@ def test(): exec_string = execute("info types com.oracle.svm.test.debug.CStructTests\$") rexp = [ fr"{spaces_pattern}typedef composite_struct \* com\.oracle\.svm\.test\.debug\.CStructTests\$CompositeStruct;", - fr"{spaces_pattern}typedef int32_t \* com\.oracle\.svm\.test\.debug\.CStructTests\$MyCIntPointer;", + fr"{spaces_pattern}typedef int \* com\.oracle\.svm\.test\.debug\.CStructTests\$MyCIntPointer;", fr"{spaces_pattern}typedef simple_struct \* com\.oracle\.svm\.test\.debug\.CStructTests\$SimpleStruct;", fr"{spaces_pattern}typedef simple_struct2 \* com\.oracle\.svm\.test\.debug\.CStructTests\$SimpleStruct2;", fr"{spaces_pattern}typedef weird \* com\.oracle\.svm\.test\.debug\.CStructTests\$Weird;"] @@ -1012,8 +1012,8 @@ def test(): fr"/\*{spaces_pattern}24{spaces_pattern}\|{spaces_pattern}4{spaces_pattern}\*/{spaces_pattern}float f_float;", fr"/\*{spaces_pattern}XXX{spaces_pattern}4-byte hole{spaces_pattern}\*/", fr"/\*{spaces_pattern}32{spaces_pattern}\|{spaces_pattern}8{spaces_pattern}\*/{spaces_pattern}double f_double;", - fr"/\*{spaces_pattern}40{spaces_pattern}\|{spaces_pattern}32{spaces_pattern}\*/{spaces_pattern}int32_t a_int\[8\];", - fr"/\*{spaces_pattern}72{spaces_pattern}\|{spaces_pattern}12{spaces_pattern}\*/{spaces_pattern}(u)?int8_t a_char\[12\];", + fr"/\*{spaces_pattern}40{spaces_pattern}\|{spaces_pattern}32{spaces_pattern}\*/{spaces_pattern}int a_int\[8\];", + fr"/\*{spaces_pattern}72{spaces_pattern}\|{spaces_pattern}12{spaces_pattern}\*/{spaces_pattern}char a_char\[12\];", fr"/\*{spaces_pattern}XXX{spaces_pattern}4-byte padding{spaces_pattern}\*/", fr"{spaces_pattern}/\* total size \(bytes\):{spaces_pattern}88 \*/", fr"{spaces_pattern}}} \*"] diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/ObjectFile.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/ObjectFile.java index 734c93a025e1..c758f06eaa8d 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/ObjectFile.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/ObjectFile.java @@ -219,16 +219,12 @@ public static Format getNativeFormat() { } private static ObjectFile getNativeObjectFile(int pageSize, boolean runtimeDebugInfoGeneration) { - switch (ObjectFile.getNativeFormat()) { - case ELF: - return new ELFObjectFile(pageSize, runtimeDebugInfoGeneration); - case MACH_O: - return new MachOObjectFile(pageSize); - case PECOFF: - return new PECoffObjectFile(pageSize); - default: - throw new AssertionError("Unreachable"); - } + return switch (ObjectFile.getNativeFormat()) { + case ELF -> new ELFObjectFile(pageSize, runtimeDebugInfoGeneration); + case MACH_O -> new MachOObjectFile(pageSize); + case PECOFF -> new PECoffObjectFile(pageSize); + case LLVM -> throw new AssertionError("Unsupported NativeObjectFile for format " + ObjectFile.getNativeFormat()); + }; } public static ObjectFile getNativeObjectFile(int pageSize) { @@ -1828,7 +1824,7 @@ public final SymbolTable getOrCreateSymbolTable() { * Temporary storage for a debug context installed in a nested scope under a call. to * {@link #withDebugContext} */ - private DebugContext debugContext = null; + protected DebugContext debugContext = DebugContext.disabled(null); /** * Allows a task to be executed with a debug context in a named subscope bound to the object diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ArrayTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ArrayTypeEntry.java index 114c585637ea..d8befe966275 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ArrayTypeEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ArrayTypeEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,40 +26,16 @@ package com.oracle.objectfile.debugentry; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugArrayTypeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind; +public final class ArrayTypeEntry extends StructureTypeEntry { + private final TypeEntry elementType; + private final LoaderEntry loader; -import jdk.graal.compiler.debug.DebugContext; -import jdk.vm.ci.meta.ResolvedJavaType; - -public class ArrayTypeEntry extends StructureTypeEntry { - private TypeEntry elementType; - private int baseSize; - private int lengthOffset; - - public ArrayTypeEntry(String typeName, int size) { - super(typeName, size); - } - - @Override - public DebugTypeKind typeKind() { - return DebugTypeKind.ARRAY; - } - - @Override - public void addDebugInfo(DebugInfoBase debugInfoBase, DebugTypeInfo debugTypeInfo, DebugContext debugContext) { - super.addDebugInfo(debugInfoBase, debugTypeInfo, debugContext); - DebugArrayTypeInfo debugArrayTypeInfo = (DebugArrayTypeInfo) debugTypeInfo; - ResolvedJavaType eltType = debugArrayTypeInfo.elementType(); - this.elementType = debugInfoBase.lookupTypeEntry(eltType); - this.baseSize = debugArrayTypeInfo.baseSize(); - this.lengthOffset = debugArrayTypeInfo.lengthOffset(); - /* Add details of fields and field types */ - debugArrayTypeInfo.fieldInfoProvider().forEach(debugFieldInfo -> this.processField(debugFieldInfo, debugInfoBase, debugContext)); - if (debugContext.isLogEnabled()) { - debugContext.log("typename %s element type %s base size %d length offset %d%n", typeName, this.elementType.getTypeName(), baseSize, lengthOffset); - } + public ArrayTypeEntry(String typeName, int size, long classOffset, long typeSignature, + long compressedTypeSignature, long layoutTypeSignature, + TypeEntry elementType, LoaderEntry loader) { + super(typeName, size, classOffset, typeSignature, compressedTypeSignature, layoutTypeSignature); + this.elementType = elementType; + this.loader = loader; } public TypeEntry getElementType() { @@ -67,13 +43,6 @@ public TypeEntry getElementType() { } public String getLoaderId() { - TypeEntry type = elementType; - while (type.isArray()) { - type = ((ArrayTypeEntry) type).elementType; - } - if (type.isClass()) { - return ((ClassEntry) type).getLoaderId(); - } - return ""; + return loader.loaderId(); } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java index 65341170fb9b..72b2067d843a 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,40 +26,22 @@ package com.oracle.objectfile.debugentry; -import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; import java.util.List; -import java.util.stream.Stream; +import java.util.Map; +import java.util.concurrent.ConcurrentSkipListSet; -import org.graalvm.collections.EconomicMap; - -import com.oracle.objectfile.debugentry.range.PrimaryRange; import com.oracle.objectfile.debugentry.range.Range; -import com.oracle.objectfile.debugentry.range.SubRange; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFieldInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFrameSizeChange; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugInstanceTypeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugMethodInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugRangeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind; - -import jdk.graal.compiler.debug.DebugContext; -import jdk.vm.ci.meta.ResolvedJavaMethod; -import jdk.vm.ci.meta.ResolvedJavaType; /** * Track debug info associated with a Java class. */ -public class ClassEntry extends StructureTypeEntry { +public sealed class ClassEntry extends StructureTypeEntry permits EnumClassEntry, InterfaceClassEntry { /** * Details of this class's superclass. */ - protected ClassEntry superClass; - /** - * Details of this class's interfaces. - */ - protected final List interfaces = new ArrayList<>(); + private final ClassEntry superClass; /** * Details of the associated file. */ @@ -67,116 +49,94 @@ public class ClassEntry extends StructureTypeEntry { /** * Details of the associated loader. */ - private LoaderEntry loader; + private final LoaderEntry loader; /** * Details of methods located in this instance. */ - protected final List methods = new ArrayList<>(); - /** - * An index of all currently known methods keyed by the unique, associated, identifying - * ResolvedJavaMethod. - */ - private final EconomicMap methodsIndex = EconomicMap.create(); + private final ConcurrentSkipListSet methods; /** * A list recording details of all normal compiled methods included in this class sorted by * ascending address range. Note that the associated address ranges are disjoint and contiguous. */ - private final List compiledEntries = new ArrayList<>(); - /** - * An index identifying ranges for compiled method which have already been encountered. - */ - private final EconomicMap compiledMethodIndex = EconomicMap.create(); + private final ConcurrentSkipListSet compiledMethods; /** * A list of all files referenced from info associated with this class, including info detailing * inline method ranges. */ - private final ArrayList files; + private final ConcurrentSkipListSet files; + private final Map indexedFiles = new HashMap<>(); + /** * A list of all directories referenced from info associated with this class, including info * detailing inline method ranges. */ - private final ArrayList dirs; - /** - * An index identifying the file table position of every file referenced from info associated - * with this class, including info detailing inline method ranges. - */ - private EconomicMap fileIndex; - /** - * An index identifying the dir table position of every directory referenced from info - * associated with this class, including info detailing inline method ranges. - */ - private EconomicMap dirIndex; - - public ClassEntry(String className, FileEntry fileEntry, int size) { - super(className, size); + private final ConcurrentSkipListSet dirs; + private final Map indexedDirs = new HashMap<>(); + + public ClassEntry(String typeName, int size, long classOffset, long typeSignature, + long compressedTypeSignature, long layoutTypeSignature, + ClassEntry superClass, FileEntry fileEntry, LoaderEntry loader) { + super(typeName, size, classOffset, typeSignature, compressedTypeSignature, layoutTypeSignature); + this.superClass = superClass; this.fileEntry = fileEntry; - this.loader = null; - // file and dir lists/indexes are populated after all DebugInfo API input has - // been received and are only created on demand - files = new ArrayList<>(); - dirs = new ArrayList<>(); - // create these on demand using the size of the file and dir lists - this.fileIndex = null; - this.dirIndex = null; - } + this.loader = loader; + this.methods = new ConcurrentSkipListSet<>(Comparator.comparingInt(MethodEntry::getModifiers).thenComparingInt(MethodEntry::getLine).thenComparing(MethodEntry::getSymbolName)); + this.compiledMethods = new ConcurrentSkipListSet<>(Comparator.comparing(CompiledMethodEntry::primary)); + this.files = new ConcurrentSkipListSet<>(Comparator.comparing(FileEntry::fileName).thenComparing(file -> file.dirEntry().path())); + this.dirs = new ConcurrentSkipListSet<>(Comparator.comparing(DirEntry::path)); - @Override - public DebugTypeKind typeKind() { - return DebugTypeKind.INSTANCE; + addFile(fileEntry); } - @Override - public void addDebugInfo(DebugInfoBase debugInfoBase, DebugTypeInfo debugTypeInfo, DebugContext debugContext) { - super.addDebugInfo(debugInfoBase, debugTypeInfo, debugContext); - assert debugTypeInfo.typeName().equals(typeName); - DebugInstanceTypeInfo debugInstanceTypeInfo = (DebugInstanceTypeInfo) debugTypeInfo; - /* Add details of super and interface classes */ - ResolvedJavaType superType = debugInstanceTypeInfo.superClass(); - if (debugContext.isLogEnabled()) { - debugContext.log("typename %s adding super %s%n", typeName, superType != null ? superType.toJavaName() : ""); - } - if (superType != null) { - this.superClass = debugInfoBase.lookupClassEntry(superType); - } - String loaderName = debugInstanceTypeInfo.loaderName(); - if (!loaderName.isEmpty()) { - this.loader = debugInfoBase.ensureLoaderEntry(loaderName); + private void addFile(FileEntry addFileEntry) { + if (addFileEntry != null && !addFileEntry.fileName().isEmpty()) { + files.add(addFileEntry); + DirEntry addDirEntry = addFileEntry.dirEntry(); + if (addDirEntry != null && !addDirEntry.getPathString().isEmpty()) { + dirs.add(addDirEntry); + } } - debugInstanceTypeInfo.interfaces().forEach(interfaceType -> processInterface(interfaceType, debugInfoBase, debugContext)); - /* Add details of fields and field types */ - debugInstanceTypeInfo.fieldInfoProvider().forEach(debugFieldInfo -> this.processField(debugFieldInfo, debugInfoBase, debugContext)); - /* Add details of methods and method types */ - debugInstanceTypeInfo.methodInfoProvider().forEach(debugMethodInfo -> this.processMethod(debugMethodInfo, debugInfoBase, debugContext)); } - public CompiledMethodEntry indexPrimary(PrimaryRange primary, List frameSizeInfos, int frameSize) { - assert compiledMethodIndex.get(primary) == null : "repeat of primary range [0x%x, 0x%x]!".formatted(primary.getLo(), primary.getHi()); - CompiledMethodEntry compiledEntry = new CompiledMethodEntry(primary, frameSizeInfos, frameSize, this); - compiledMethodIndex.put(primary, compiledEntry); - compiledEntries.add(compiledEntry); - return compiledEntry; + /** + * Add a field to the class entry and store its file entry. + * + * @param field the {@code FieldEntry} to add + */ + @Override + public void addField(FieldEntry field) { + addFile(field.getFileEntry()); + super.addField(field); } - public void indexSubRange(SubRange subrange) { - Range primary = subrange.getPrimary(); - /* The subrange should belong to a primary range. */ - assert primary != null; - CompiledMethodEntry compiledEntry = compiledMethodIndex.get(primary); - /* We should already have seen the primary range. */ - assert compiledEntry != null; - assert compiledEntry.getClassEntry() == this; + /** + * Add a method to the class entry and store its file entry. + * + * @param methodEntry the {@code MethodEntry} to add + */ + public void addMethod(MethodEntry methodEntry) { + addFile(methodEntry.getFileEntry()); + methods.add(methodEntry); } - private void indexMethodEntry(MethodEntry methodEntry, ResolvedJavaMethod idMethod) { - assert methodsIndex.get(idMethod) == null : methodEntry.getSymbolName(); - methods.add(methodEntry); - methodsIndex.put(idMethod, methodEntry); + /** + * Add a compiled method to the class entry and store its file entry and the file entries of + * inlined methods. + * + * @param compiledMethodEntry the {@code CompiledMethodEntry} to add + */ + public void addCompiledMethod(CompiledMethodEntry compiledMethodEntry) { + addFile(compiledMethodEntry.primary().getFileEntry()); + for (Range range : compiledMethodEntry.topDownRangeStream().toList()) { + addFile(range.getFileEntry()); + } + compiledMethods.add(compiledMethodEntry); } public String getFileName() { if (fileEntry != null) { - return fileEntry.getFileName(); + return fileEntry.fileName(); } else { return ""; } @@ -207,18 +167,44 @@ public int getFileIdx() { return getFileIdx(this.getFileEntry()); } + public int getDirIdx() { + return getDirIdx(this.getFileEntry()); + } + + /** + * Returns the file index of a given file entry within this class entry. + * + *

+ * The first time a file entry is fetched, this produces a file index that is used for further + * index lookups. The file index is only created once. Therefore, this method must be used only + * after debug info generation is finished and no more file entries can be added to this class + * entry. + * + * @param file the given file entry + * @return the index of the file entry + */ public int getFileIdx(FileEntry file) { - if (file == null || fileIndex == null) { + if (file == null || files.isEmpty() || !files.contains(file)) { return 0; } - return fileIndex.get(file); + + // Create a file index for all files in this class entry + if (indexedFiles.isEmpty()) { + int index = 1; + for (FileEntry f : getFiles()) { + indexedFiles.put(f, index); + index++; + } + } + + return indexedFiles.get(file); } - public DirEntry getDirEntry(FileEntry file) { + private static DirEntry getDirEntry(FileEntry file) { if (file == null) { return null; } - return file.getDirEntry(); + return file.dirEntry(); } public int getDirIdx(FileEntry file) { @@ -226,136 +212,69 @@ public int getDirIdx(FileEntry file) { return getDirIdx(dirEntry); } - public int getDirIdx(DirEntry dir) { - if (dir == null || dir.getPathString().isEmpty() || dirIndex == null) { - return 0; - } - return dirIndex.get(dir); - } - - public String getLoaderId() { - return (loader != null ? loader.getLoaderId() : ""); - } - /** - * Retrieve a stream of all compiled method entries for this class. + * Returns the dir index of a given dir entry within this class entry. * - * @return a stream of all compiled method entries for this class. + *

+ * The first time a dir entry is fetched, this produces a dir index that is used for further + * index lookups. The dir index is only created once. Therefore, this method must be used only + * after debug info generation is finished and no more dir entries can be added to this class + * entry. + * + * @param dir the given dir entry + * @return the index of the dir entry */ - public Stream compiledEntries() { - return compiledEntries.stream(); - } - - protected void processInterface(ResolvedJavaType interfaceType, DebugInfoBase debugInfoBase, DebugContext debugContext) { - if (debugContext.isLogEnabled()) { - debugContext.log("typename %s adding interface %s%n", typeName, interfaceType.toJavaName()); - } - ClassEntry entry = debugInfoBase.lookupClassEntry(interfaceType); - assert entry instanceof InterfaceClassEntry || (entry instanceof ForeignTypeEntry && this instanceof ForeignTypeEntry); - InterfaceClassEntry interfaceClassEntry = (InterfaceClassEntry) entry; - interfaces.add(interfaceClassEntry); - interfaceClassEntry.addImplementor(this, debugContext); - } - - protected MethodEntry processMethod(DebugMethodInfo debugMethodInfo, DebugInfoBase debugInfoBase, DebugContext debugContext) { - String methodName = debugMethodInfo.name(); - int line = debugMethodInfo.line(); - ResolvedJavaType resultType = debugMethodInfo.valueType(); - int modifiers = debugMethodInfo.modifiers(); - DebugLocalInfo[] paramInfos = debugMethodInfo.getParamInfo(); - DebugLocalInfo thisParam = debugMethodInfo.getThisParamInfo(); - int paramCount = paramInfos.length; - if (debugContext.isLogEnabled()) { - String resultTypeName = resultType.toJavaName(); - debugContext.log("typename %s adding %s method %s %s(%s)%n", - typeName, memberModifiers(modifiers), resultTypeName, methodName, formatParams(paramInfos)); - } - TypeEntry resultTypeEntry = debugInfoBase.lookupTypeEntry(resultType); - TypeEntry[] typeEntries = new TypeEntry[paramCount]; - for (int i = 0; i < paramCount; i++) { - typeEntries[i] = debugInfoBase.lookupTypeEntry(paramInfos[i].valueType()); + public int getDirIdx(DirEntry dir) { + if (dir == null || dir.getPathString().isEmpty() || dirs.isEmpty() || !dirs.contains(dir)) { + return 0; } - /* - * n.b. the method file may differ from the owning class file when the method is a - * substitution - */ - FileEntry methodFileEntry = debugInfoBase.ensureFileEntry(debugMethodInfo); - MethodEntry methodEntry = new MethodEntry(debugInfoBase, debugMethodInfo, methodFileEntry, line, methodName, - this, resultTypeEntry, typeEntries, paramInfos, thisParam); - indexMethodEntry(methodEntry, debugMethodInfo.idMethod()); - - return methodEntry; - } - - @Override - protected FieldEntry createField(DebugFieldInfo debugFieldInfo, DebugInfoBase debugInfoBase, DebugContext debugContext) { - FieldEntry fieldEntry = super.createField(debugFieldInfo, debugInfoBase, debugContext); - return fieldEntry; - } - private static String formatParams(DebugLocalInfo[] paramInfo) { - if (paramInfo.length == 0) { - return ""; - } - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < paramInfo.length; i++) { - if (i > 0) { - builder.append(", "); + // Create a dir index for all dirs in this class entry + if (indexedDirs.isEmpty()) { + int index = 1; + for (DirEntry d : getDirs()) { + indexedDirs.put(d, index); + index++; } - builder.append(paramInfo[i].typeName()); - builder.append(' '); - builder.append(paramInfo[i].name()); } - return builder.toString(); + return indexedDirs.get(dir); } - public int compiledEntryCount() { - return compiledEntries.size(); + public String getLoaderId() { + return loader.loaderId(); } - public boolean hasCompiledEntries() { - return compiledEntryCount() != 0; + /** + * Retrieve a list of all compiled method entries for this class. + * + * @return a list of all compiled method entries for this class. + */ + public List compiledMethods() { + return List.copyOf(compiledMethods); } - public int compiledEntriesBase() { - assert hasCompiledEntries(); - return compiledEntries.get(0).getPrimary().getLo(); + public boolean hasCompiledMethods() { + return !compiledMethods.isEmpty(); } public ClassEntry getSuperClass() { return superClass; } - public MethodEntry ensureMethodEntryForDebugRangeInfo(DebugRangeInfo debugRangeInfo, DebugInfoBase debugInfoBase, DebugContext debugContext) { - - MethodEntry methodEntry = methodsIndex.get(debugRangeInfo.idMethod()); - if (methodEntry == null) { - methodEntry = processMethod(debugRangeInfo, debugInfoBase, debugContext); - } else { - methodEntry.updateRangeInfo(debugInfoBase, debugRangeInfo); - } - return methodEntry; - } - public List getMethods() { - return methods; + return List.copyOf(methods); } - /* - * Accessors for lo and hi bounds of this class's compiled method code ranges. See comments in - * class DebugInfoBase for an explanation of the layout of compiled method code. - */ - /** * Retrieve the lowest code section offset for compiled method code belonging to this class. It * is an error to call this for a class entry which has no compiled methods. * * @return the lowest code section offset for compiled method code belonging to this class */ - public int lowpc() { - assert hasCompiledEntries(); - return compiledEntries.get(0).getPrimary().getLo(); + public long lowpc() { + assert hasCompiledMethods(); + return compiledMethods.first().primary().getLo(); } /** @@ -365,84 +284,28 @@ public int lowpc() { * * @return the highest code section offset for compiled method code belonging to this class */ - public int hipc() { - assert hasCompiledEntries(); - return compiledEntries.get(compiledEntries.size() - 1).getPrimary().getHi(); + public long hipc() { + assert hasCompiledMethods(); + return compiledMethods.last().primary().getHi(); } /** - * Add a file to the list of files referenced from info associated with this class. - * - * @param file The file to be added. - */ - public void includeFile(FileEntry file) { - assert !files.contains(file) : "caller should ensure file is only included once"; - assert fileIndex == null : "cannot include files after index has been created"; - files.add(file); - } - - /** - * Add a directory to the list of firectories referenced from info associated with this class. - * - * @param dirEntry The directory to be added. - */ - public void includeDir(DirEntry dirEntry) { - assert !dirs.contains(dirEntry) : "caller should ensure dir is only included once"; - assert dirIndex == null : "cannot include dirs after index has been created"; - dirs.add(dirEntry); - } - - /** - * Populate the file and directory indexes that track positions in the file and dir tables for - * this class's line info section. - */ - public void buildFileAndDirIndexes() { - // this is a one-off operation - assert fileIndex == null && dirIndex == null : "file and indexes can only be generated once"; - if (files.isEmpty()) { - assert dirs.isEmpty() : "should not have included any dirs if we have no files"; - } - int idx = 1; - fileIndex = EconomicMap.create(files.size()); - for (FileEntry file : files) { - fileIndex.put(file, idx++); - } - dirIndex = EconomicMap.create(dirs.size()); - idx = 1; - for (DirEntry dir : dirs) { - if (!dir.getPathString().isEmpty()) { - dirIndex.put(dir, idx++); - } else { - assert idx == 1; - } - } - } - - /** - * Retrieve a stream of all files referenced from debug info for this class in line info file + * Retrieve a list of all files referenced from debug info for this class in line info file * table order, starting with the file at index 1. - * - * @return a stream of all referenced files + * + * @return a list of all referenced files */ - public Stream fileStream() { - if (!files.isEmpty()) { - return files.stream(); - } else { - return Stream.empty(); - } + public List getFiles() { + return List.copyOf(files); } /** - * Retrieve a stream of all directories referenced from debug info for this class in line info + * Retrieve a list of all directories referenced from debug info for this class in line info * directory table order, starting with the directory at index 1. * - * @return a stream of all referenced directories + * @return a list of all referenced directories */ - public Stream dirStream() { - if (!dirs.isEmpty()) { - return dirs.stream(); - } else { - return Stream.empty(); - } + public List getDirs() { + return List.copyOf(dirs); } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/CompiledMethodEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/CompiledMethodEntry.java index b8d34393677a..0b7208e4eab7 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/CompiledMethodEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/CompiledMethodEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,139 +26,53 @@ package com.oracle.objectfile.debugentry; -import com.oracle.objectfile.debugentry.range.PrimaryRange; -import com.oracle.objectfile.debugentry.range.SubRange; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFrameSizeChange; - -import java.util.ArrayDeque; -import java.util.Iterator; import java.util.List; +import java.util.stream.Stream; + +import com.oracle.objectfile.debugentry.range.PrimaryRange; +import com.oracle.objectfile.debugentry.range.Range; /** * Tracks debug info associated with a top level compiled method. + * + * @param primary The primary range detailed by this object. + * @param ownerType Details of the class owning this range. + * @param frameSizeInfos Details of compiled method frame size changes. + * @param frameSize Size of compiled method frame. */ -public class CompiledMethodEntry { - /** - * The primary range detailed by this object. - */ - private final PrimaryRange primary; - /** - * Details of the class owning this range. - */ - private final ClassEntry classEntry; - /** - * Details of of compiled method frame size changes. - */ - private final List frameSizeInfos; - /** - * Size of compiled method frame. - */ - private final int frameSize; - - public CompiledMethodEntry(PrimaryRange primary, List frameSizeInfos, int frameSize, ClassEntry classEntry) { - this.primary = primary; - this.classEntry = classEntry; - this.frameSizeInfos = frameSizeInfos; - this.frameSize = frameSize; - } - - public PrimaryRange getPrimary() { - return primary; - } - - public ClassEntry getClassEntry() { - return classEntry; - } +public record CompiledMethodEntry(PrimaryRange primary, List frameSizeInfos, int frameSize, + ClassEntry ownerType) { /** - * Returns an iterator that traverses all the callees of the method associated with this entry. - * The iterator performs a depth-first pre-order traversal of the call tree. + * Returns a stream that traverses all the callees of the method associated with this entry. The + * stream performs a depth-first pre-order traversal of the call tree. * - * @return the iterator + * @return the stream of all ranges */ - public Iterator topDownRangeIterator() { - return new Iterator<>() { - final ArrayDeque workStack = new ArrayDeque<>(); - SubRange current = primary.getFirstCallee(); - - @Override - public boolean hasNext() { - return current != null; - } - - @Override - public SubRange next() { - assert hasNext(); - SubRange result = current; - forward(); - return result; - } - - private void forward() { - SubRange sibling = current.getSiblingCallee(); - assert sibling == null || (current.getHi() <= sibling.getLo()) : current.getHi() + " > " + sibling.getLo(); - if (!current.isLeaf()) { - /* save next sibling while we process the children */ - if (sibling != null) { - workStack.push(sibling); - } - current = current.getFirstCallee(); - } else if (sibling != null) { - current = sibling; - } else { - /* - * Return back up to parents' siblings, use pollFirst instead of pop to return - * null in case the work stack is empty - */ - current = workStack.pollFirst(); - } - } - }; + public Stream topDownRangeStream() { + // skip the root of the range stream which is the primary range + return primary.rangeStream().skip(1); } /** - * Returns an iterator that traverses the callees of the method associated with this entry and - * returns only the leafs. The iterator performs a depth-first pre-order traversal of the call + * Returns a stream that traverses the callees of the method associated with this entry and + * returns only the leafs. The stream performs a depth-first pre-order traversal of the call * tree returning only ranges with no callees. * - * @return the iterator + * @return the stream of leaf ranges */ - public Iterator leafRangeIterator() { - final Iterator iter = topDownRangeIterator(); - return new Iterator<>() { - SubRange current = forwardLeaf(iter); - - @Override - public boolean hasNext() { - return current != null; - } - - @Override - public SubRange next() { - assert hasNext(); - SubRange result = current; - current = forwardLeaf(iter); - return result; - } - - private SubRange forwardLeaf(Iterator t) { - if (t.hasNext()) { - SubRange next = t.next(); - while (next != null && !next.isLeaf()) { - next = t.next(); - } - return next; - } - return null; - } - }; - } - - public List getFrameSizeInfos() { - return frameSizeInfos; + public Stream leafRangeStream() { + return topDownRangeStream().filter(Range::isLeaf); } - public int getFrameSize() { - return frameSize; + /** + * Returns a stream that traverses the callees of the method associated with this entry and + * returns only the call ranges. The stream performs a depth-first pre-order traversal of the + * call tree returning only ranges with callees. + * + * @return the stream of call ranges + */ + public Stream callRangeStream() { + return topDownRangeStream().filter(range -> !range.isLeaf()); } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ConstantValueEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ConstantValueEntry.java new file mode 100644 index 000000000000..8388f3369dca --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ConstantValueEntry.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.debugentry; + +import jdk.vm.ci.meta.JavaConstant; + +public record ConstantValueEntry(long heapOffset, JavaConstant constant) implements LocalValueEntry { + + @Override + public String toString() { + return "CONST:" + (constant != null ? constant.toValueString() : "null") + "[" + Long.toHexString(heapOffset) + "]"; + } +} 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 d2b8c827103e..de98e947f688 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java @@ -27,28 +27,10 @@ package com.oracle.objectfile.debugentry; import java.nio.ByteOrder; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import org.graalvm.collections.EconomicMap; -import org.graalvm.collections.EconomicSet; - -import com.oracle.objectfile.debugentry.range.PrimaryRange; -import com.oracle.objectfile.debugentry.range.Range; -import com.oracle.objectfile.debugentry.range.SubRange; import com.oracle.objectfile.debuginfo.DebugInfoProvider; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugCodeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFileInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalValueInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocationInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind; - -import jdk.graal.compiler.debug.DebugContext; -import jdk.vm.ci.meta.ResolvedJavaType; /** * An abstract class which indexes the information presented by the DebugInfoProvider in an @@ -89,39 +71,35 @@ public abstract class DebugInfoBase { protected ByteOrder byteOrder; /** - * A table listing all known strings, some of which may be marked for insertion into the - * debug_str section. - */ - private final StringTable stringTable = new StringTable(); - /** - * List of dirs in which files are found to reside. + * A table listing all known strings except strings in the debug line section. */ - private final List dirs = new ArrayList<>(); + private StringTable stringTable; + /** - * Index of all dirs in which files are found to reside either as part of substrate/compiler or - * user code. + * A table listing all known strings in the debug line section. */ - private final EconomicMap dirsIndex = EconomicMap.create(); + private StringTable lineStringTable; /** * List of all types present in the native image including instance classes, array classes, * primitive types and the one-off Java header struct. */ private final List types = new ArrayList<>(); - /** - * Index of already seen types keyed by the unique, associated, identifying ResolvedJavaType or, - * in the single special case of the TypeEntry for the Java header structure, by key null. - */ - private final Map typesIndex = new HashMap<>(); /** * List of all instance classes found in debug info. These classes do not necessarily have top * level or inline compiled methods. This list includes interfaces and enum types. */ private final List instanceClasses = new ArrayList<>(); - /** - * Index of already seen classes. - */ - private final EconomicMap instanceClassesIndex = EconomicMap.create(); + + private final List instanceClassesWithCompilation = new ArrayList<>(); + + private final List primitiveTypes = new ArrayList<>(); + + private final List pointerTypes = new ArrayList<>(); + + private final List foreignStructTypes = new ArrayList<>(); + + private final List arrayTypes = new ArrayList<>(); /** * Handle on type entry for header structure. */ @@ -143,30 +121,14 @@ public abstract class DebugInfoBase { * debug info API in ascending address range order. */ private final List compiledMethods = new ArrayList<>(); - /** - * List of of files which contain primary or secondary ranges. - */ - private final List files = new ArrayList<>(); - /** - * Index of files which contain primary or secondary ranges keyed by path. - */ - private final EconomicMap filesIndex = EconomicMap.create(); - - /** - * List of all loaders associated with classes included in the image. - */ - private final List loaders = new ArrayList<>(); - - /** - * Index of all loaders associated with classes included in the image. - */ - private final EconomicMap loaderIndex = EconomicMap.create(); /** * Flag set to true if heap references are stored as addresses relative to a heap base register * otherwise false. */ private boolean useHeapBase; + + private boolean isRuntimeCompilation; /** * Number of bits oops are left shifted by when using compressed oops. */ @@ -201,11 +163,28 @@ public abstract class DebugInfoBase { private String cachePath; /** - * The offset of the first byte beyond the end of the Java compiled code address range. + * A type entry for storing compilations of foreign methods. + */ + private ClassEntry foreignMethodListClassEntry; + + /** + * A prefix used to label indirect types used to ensure gdb performs oop reference --> raw + * address translation. + */ + public static final String COMPRESSED_PREFIX = "_z_."; + /** + * A prefix used for type signature generation to generate unique type signatures for type + * layout type units. */ - private int compiledCodeMax; + public static final String LAYOUT_PREFIX = "_layout_."; + + /** + * The name of the type for header field hub which needs special case processing to remove tag + * bits. + */ + public static final String HUB_TYPE_NAME = "Encoded$Dynamic$Hub"; + public static final String FOREIGN_METHOD_LIST_TYPE = "Foreign$Method$List"; - @SuppressWarnings("this-escape") public DebugInfoBase(ByteOrder byteOrder) { this.byteOrder = byteOrder; this.useHeapBase = true; @@ -216,13 +195,6 @@ public DebugInfoBase(ByteOrder byteOrder) { this.pointerSize = 0; this.objectAlignment = 0; this.numAlignmentBits = 0; - this.compiledCodeMax = 0; - // create and index an empty dir with index 0. - ensureDirEntry(EMPTY_PATH); - } - - public int compiledCodeMax() { - return compiledCodeMax; } /** @@ -238,12 +210,15 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) { * DebugTypeInfoProvider typeInfoProvider = debugInfoProvider.typeInfoProvider(); for * (DebugTypeInfo debugTypeInfo : typeInfoProvider) { install types } */ + debugInfoProvider.installDebugInfo(); /* * Track whether we need to use a heap base register. */ useHeapBase = debugInfoProvider.useHeapBase(); + this.isRuntimeCompilation = debugInfoProvider.isRuntimeCompilation(); + /* * Save count of low order tag bits that may appear in references. */ @@ -275,187 +250,37 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) { /* Reference alignment must be 8 bytes. */ assert objectAlignment == 8; - /* retrieve limit for Java code address range */ - compiledCodeMax = debugInfoProvider.compiledCodeMax(); - - /* Ensure we have a null string and cachePath in the string section. */ - String uniqueNullString = stringTable.uniqueDebugString(""); - if (debugInfoProvider.getCachePath() != null) { - cachePath = stringTable.uniqueDebugString(debugInfoProvider.getCachePath().toString()); - } else { - cachePath = uniqueNullString; // fall back to null string - } - - /* Create all the types. */ - debugInfoProvider.typeInfoProvider().forEach(debugTypeInfo -> debugTypeInfo.debugContext((debugContext) -> { - ResolvedJavaType idType = debugTypeInfo.idType(); - String typeName = debugTypeInfo.typeName(); - typeName = stringTable.uniqueDebugString(typeName); - DebugTypeKind typeKind = debugTypeInfo.typeKind(); - int byteSize = debugTypeInfo.size(); - - if (debugContext.isLogEnabled(DebugContext.INFO_LEVEL)) { - debugContext.log(DebugContext.INFO_LEVEL, "Register %s type %s ", typeKind.toString(), typeName); - } - String fileName = debugTypeInfo.fileName(); - Path filePath = debugTypeInfo.filePath(); - addTypeEntry(idType, typeName, fileName, filePath, byteSize, typeKind); - })); - debugInfoProvider.recordActivity(); - - /* Now we can cross reference static and instance field details. */ - debugInfoProvider.typeInfoProvider().forEach(debugTypeInfo -> debugTypeInfo.debugContext((debugContext) -> { - ResolvedJavaType idType = debugTypeInfo.idType(); - String typeName = debugTypeInfo.typeName(); - DebugTypeKind typeKind = debugTypeInfo.typeKind(); - - if (debugContext.isLogEnabled(DebugContext.INFO_LEVEL)) { - debugContext.log(DebugContext.INFO_LEVEL, "Process %s type %s ", typeKind.toString(), typeName); - } - TypeEntry typeEntry = (idType != null ? lookupTypeEntry(idType) : lookupHeaderType()); - typeEntry.addDebugInfo(this, debugTypeInfo, debugContext); - })); - debugInfoProvider.recordActivity(); - - debugInfoProvider.codeInfoProvider().forEach(debugCodeInfo -> debugCodeInfo.debugContext((debugContext) -> { - /* - * Primary file name and full method name need to be written to the debug_str section. - */ - String fileName = debugCodeInfo.fileName(); - Path filePath = debugCodeInfo.filePath(); - ResolvedJavaType ownerType = debugCodeInfo.ownerType(); - String methodName = debugCodeInfo.name(); - int lo = debugCodeInfo.addressLo(); - int hi = debugCodeInfo.addressHi(); - int primaryLine = debugCodeInfo.line(); - - /* Search for a method defining this primary range. */ - ClassEntry classEntry = lookupClassEntry(ownerType); - MethodEntry methodEntry = classEntry.ensureMethodEntryForDebugRangeInfo(debugCodeInfo, this, debugContext); - PrimaryRange primaryRange = Range.createPrimary(methodEntry, lo, hi, primaryLine); - if (debugContext.isLogEnabled(DebugContext.INFO_LEVEL)) { - debugContext.log(DebugContext.INFO_LEVEL, "PrimaryRange %s.%s %s %s:%d [0x%x, 0x%x]", ownerType.toJavaName(), methodName, filePath, fileName, primaryLine, lo, hi); - } - addPrimaryRange(primaryRange, debugCodeInfo, classEntry); - /* - * Record all subranges even if they have no line or file so we at least get a symbol - * for them and don't see a break in the address range. - */ - EconomicMap subRangeIndex = EconomicMap.create(); - debugCodeInfo.locationInfoProvider().forEach(debugLocationInfo -> addSubrange(debugLocationInfo, primaryRange, classEntry, subRangeIndex, debugContext)); - debugInfoProvider.recordActivity(); - })); - - debugInfoProvider.dataInfoProvider().forEach(debugDataInfo -> debugDataInfo.debugContext((debugContext) -> { - if (debugContext.isLogEnabled(DebugContext.INFO_LEVEL)) { - String provenance = debugDataInfo.getProvenance(); - String typeName = debugDataInfo.getTypeName(); - String partitionName = debugDataInfo.getPartition(); - /* Offset is relative to heap-base register. */ - long offset = debugDataInfo.getOffset(); - long size = debugDataInfo.getSize(); - debugContext.log(DebugContext.INFO_LEVEL, "Data: offset 0x%x size 0x%x type %s partition %s provenance %s ", offset, size, typeName, partitionName, provenance); - } - })); - // populate a file and dir list and associated index for each class entry - getInstanceClasses().forEach(classEntry -> { - collectFilesAndDirs(classEntry); - }); - } - - private TypeEntry createTypeEntry(String typeName, String fileName, Path filePath, int size, DebugTypeKind typeKind) { - TypeEntry typeEntry = null; - switch (typeKind) { - case INSTANCE: { - FileEntry fileEntry = addFileEntry(fileName, filePath); - typeEntry = new ClassEntry(typeName, fileEntry, size); - break; - } - case INTERFACE: { - FileEntry fileEntry = addFileEntry(fileName, filePath); - typeEntry = new InterfaceClassEntry(typeName, fileEntry, size); - break; - } - case ENUM: { - FileEntry fileEntry = addFileEntry(fileName, filePath); - typeEntry = new EnumClassEntry(typeName, fileEntry, size); - break; - } - case PRIMITIVE: - assert fileName.length() == 0; - assert filePath == null; - typeEntry = new PrimitiveTypeEntry(typeName, size); - break; - case ARRAY: - assert fileName.length() == 0; - assert filePath == null; - typeEntry = new ArrayTypeEntry(typeName, size); - break; - case HEADER: - assert fileName.length() == 0; - assert filePath == null; - typeEntry = new HeaderTypeEntry(typeName, size); - break; - case FOREIGN: { - FileEntry fileEntry = addFileEntry(fileName, filePath); - typeEntry = new ForeignTypeEntry(typeName, fileEntry, size); - break; - } - } - return typeEntry; - } + stringTable = new StringTable(); + lineStringTable = new StringTable(); + /* Create the cachePath string entry which serves as base directory for source files */ + cachePath = uniqueDebugString(debugInfoProvider.cachePath()); + uniqueDebugLineString(debugInfoProvider.cachePath()); - private TypeEntry addTypeEntry(ResolvedJavaType idType, String typeName, String fileName, Path filePath, int size, DebugTypeKind typeKind) { - TypeEntry typeEntry = (idType != null ? typesIndex.get(idType) : null); - if (typeEntry == null) { - typeEntry = createTypeEntry(typeName, fileName, filePath, size, typeKind); + compiledMethods.addAll(debugInfoProvider.compiledMethodEntries()); + debugInfoProvider.typeEntries().forEach(typeEntry -> { types.add(typeEntry); - if (idType != null) { - typesIndex.put(idType, typeEntry); - } - // track object type and header struct - if (idType == null) { - headerType = (HeaderTypeEntry) typeEntry; - } - if (typeName.equals("java.lang.Class")) { - classClass = (ClassEntry) typeEntry; - } - if (typeName.equals("java.lang.Object")) { - objectClass = (ClassEntry) typeEntry; - } - if (typeName.equals("void")) { + if (typeEntry.getTypeName().equals("void")) { voidType = typeEntry; } - if (typeEntry instanceof ClassEntry) { - indexInstanceClass(idType, (ClassEntry) typeEntry); - } - } else { - if (!(typeEntry.isClass())) { - assert ((ClassEntry) typeEntry).getFileName().equals(fileName); + switch (typeEntry) { + case ArrayTypeEntry arrayTypeEntry -> arrayTypes.add(arrayTypeEntry); + case PrimitiveTypeEntry primitiveTypeEntry -> primitiveTypes.add(primitiveTypeEntry); + case PointerToTypeEntry pointerToTypeEntry -> pointerTypes.add(pointerToTypeEntry); + case ForeignStructTypeEntry foreignStructTypeEntry -> foreignStructTypes.add(foreignStructTypeEntry); + case HeaderTypeEntry headerTypeEntry -> headerType = headerTypeEntry; + case ClassEntry classEntry -> { + instanceClasses.add(classEntry); + if (classEntry.hasCompiledMethods()) { + instanceClassesWithCompilation.add(classEntry); + } + switch (classEntry.getTypeName()) { + case "java.lang.Object" -> objectClass = classEntry; + case "java.lang.Class" -> classClass = classEntry; + case FOREIGN_METHOD_LIST_TYPE -> foreignMethodListClassEntry = classEntry; + } + } } - } - return typeEntry; - } - - public TypeEntry lookupTypeEntry(ResolvedJavaType type) { - TypeEntry typeEntry = typesIndex.get(type); - if (typeEntry == null) { - throw new RuntimeException("Type entry not found " + type.getName()); - } - return typeEntry; - } - - ClassEntry lookupClassEntry(ResolvedJavaType type) { - // lookup key should advertise itself as a resolved instance class or interface - assert type.isInstanceClass() || type.isInterface(); - // lookup target should already be included in the index - ClassEntry classEntry = instanceClassesIndex.get(type); - if (classEntry == null || !(classEntry.isClass())) { - throw new RuntimeException("Class entry not found " + type.getName()); - } - // lookup target should also be indexed in the types index - assert typesIndex.get(type) != null; - return classEntry; + }); } public HeaderTypeEntry lookupHeaderType() { @@ -482,166 +307,6 @@ public ClassEntry lookupClassClass() { return classClass; } - private void addPrimaryRange(PrimaryRange primaryRange, DebugCodeInfo debugCodeInfo, ClassEntry classEntry) { - CompiledMethodEntry compiledMethod = classEntry.indexPrimary(primaryRange, debugCodeInfo.getFrameSizeChanges(), debugCodeInfo.getFrameSize()); - indexCompiledMethod(compiledMethod); - } - - /** - * Recursively creates subranges based on DebugLocationInfo including, and appropriately - * linking, nested inline subranges. - * - * @param locationInfo - * @param primaryRange - * @param classEntry - * @param subRangeIndex - * @param debugContext - * @return the subrange for {@code locationInfo} linked with all its caller subranges up to the - * primaryRange - */ - @SuppressWarnings("try") - private Range addSubrange(DebugLocationInfo locationInfo, PrimaryRange primaryRange, ClassEntry classEntry, EconomicMap subRangeIndex, DebugContext debugContext) { - /* - * We still insert subranges for the primary method but they don't actually count as inline. - * we only need a range so that subranges for inline code can refer to the top level line - * number. - */ - DebugLocationInfo callerLocationInfo = locationInfo.getCaller(); - boolean isTopLevel = callerLocationInfo == null; - assert (!isTopLevel || (locationInfo.name().equals(primaryRange.getMethodName()) && - locationInfo.ownerType().toJavaName().equals(primaryRange.getClassName()))); - Range caller = (isTopLevel ? primaryRange : subRangeIndex.get(callerLocationInfo)); - // the frame tree is walked topdown so inline ranges should always have a caller range - assert caller != null; - - final String fileName = locationInfo.fileName(); - final Path filePath = locationInfo.filePath(); - final String fullPath = (filePath == null ? "" : filePath.toString() + "/") + fileName; - final ResolvedJavaType ownerType = locationInfo.ownerType(); - final String methodName = locationInfo.name(); - final int loOff = locationInfo.addressLo(); - final int hiOff = locationInfo.addressHi() - 1; - final int lo = primaryRange.getLo() + locationInfo.addressLo(); - final int hi = primaryRange.getLo() + locationInfo.addressHi(); - final int line = locationInfo.line(); - ClassEntry subRangeClassEntry = lookupClassEntry(ownerType); - MethodEntry subRangeMethodEntry = subRangeClassEntry.ensureMethodEntryForDebugRangeInfo(locationInfo, this, debugContext); - SubRange subRange = Range.createSubrange(subRangeMethodEntry, lo, hi, line, primaryRange, caller, locationInfo.isLeaf()); - classEntry.indexSubRange(subRange); - subRangeIndex.put(locationInfo, subRange); - if (debugContext.isLogEnabled(DebugContext.DETAILED_LEVEL)) { - debugContext.log(DebugContext.DETAILED_LEVEL, "SubRange %s.%s %d %s:%d [0x%x, 0x%x] (%d, %d)", - ownerType.toJavaName(), methodName, subRange.getDepth(), fullPath, line, lo, hi, loOff, hiOff); - } - assert (callerLocationInfo == null || (callerLocationInfo.addressLo() <= loOff && callerLocationInfo.addressHi() >= hiOff)) : "parent range should enclose subrange!"; - DebugLocalValueInfo[] localValueInfos = locationInfo.getLocalValueInfo(); - for (int i = 0; i < localValueInfos.length; i++) { - DebugLocalValueInfo localValueInfo = localValueInfos[i]; - if (debugContext.isLogEnabled(DebugContext.DETAILED_LEVEL)) { - debugContext.log(DebugContext.DETAILED_LEVEL, " locals[%d] %s:%s = %s", localValueInfo.slot(), localValueInfo.name(), localValueInfo.typeName(), localValueInfo); - } - } - subRange.setLocalValueInfo(localValueInfos); - return subRange; - } - - private void indexInstanceClass(ResolvedJavaType idType, ClassEntry classEntry) { - instanceClasses.add(classEntry); - instanceClassesIndex.put(idType, classEntry); - } - - private void indexCompiledMethod(CompiledMethodEntry compiledMethod) { - assert verifyMethodOrder(compiledMethod); - compiledMethods.add(compiledMethod); - } - - private boolean verifyMethodOrder(CompiledMethodEntry next) { - int size = compiledMethods.size(); - if (size > 0) { - CompiledMethodEntry last = compiledMethods.get(size - 1); - PrimaryRange lastRange = last.getPrimary(); - PrimaryRange nextRange = next.getPrimary(); - if (lastRange.getHi() > nextRange.getLo()) { - assert false : "methods %s [0x%x, 0x%x] and %s [0x%x, 0x%x] presented out of order".formatted(lastRange.getFullMethodName(), lastRange.getLo(), lastRange.getHi(), - nextRange.getFullMethodName(), nextRange.getLo(), nextRange.getHi()); - return false; - } - } - return true; - } - - static final Path EMPTY_PATH = Paths.get(""); - - private FileEntry addFileEntry(String fileName, Path filePath) { - assert fileName != null; - Path dirPath = filePath; - Path fileAsPath; - if (filePath != null) { - fileAsPath = dirPath.resolve(fileName); - } else { - fileAsPath = Paths.get(fileName); - dirPath = EMPTY_PATH; - } - FileEntry fileEntry = filesIndex.get(fileAsPath); - if (fileEntry == null) { - DirEntry dirEntry = ensureDirEntry(dirPath); - /* Ensure file and cachepath are added to the debug_str section. */ - uniqueDebugString(fileName); - uniqueDebugString(cachePath); - fileEntry = new FileEntry(fileName, dirEntry); - files.add(fileEntry); - /* Index the file entry by file path. */ - filesIndex.put(fileAsPath, fileEntry); - } else { - assert fileEntry.getDirEntry().getPath().equals(dirPath); - } - return fileEntry; - } - - protected FileEntry ensureFileEntry(DebugFileInfo debugFileInfo) { - String fileName = debugFileInfo.fileName(); - if (fileName == null || fileName.length() == 0) { - return null; - } - Path filePath = debugFileInfo.filePath(); - Path fileAsPath; - if (filePath == null) { - fileAsPath = Paths.get(fileName); - } else { - fileAsPath = filePath.resolve(fileName); - } - /* Reuse any existing entry. */ - FileEntry fileEntry = findFile(fileAsPath); - if (fileEntry == null) { - fileEntry = addFileEntry(fileName, filePath); - } - return fileEntry; - } - - private DirEntry ensureDirEntry(Path filePath) { - if (filePath == null) { - return null; - } - DirEntry dirEntry = dirsIndex.get(filePath); - if (dirEntry == null) { - /* Ensure dir path is entered into the debug_str section. */ - uniqueDebugString(filePath.toString()); - dirEntry = new DirEntry(filePath); - dirsIndex.put(filePath, dirEntry); - dirs.add(dirEntry); - } - return dirEntry; - } - - protected LoaderEntry ensureLoaderEntry(String loaderId) { - LoaderEntry loaderEntry = loaderIndex.get(loaderId); - if (loaderEntry == null) { - loaderEntry = new LoaderEntry(uniqueDebugString(loaderId)); - loaderIndex.put(loaderEntry.getLoaderId(), loaderEntry); - } - return loaderEntry; - } - /* Accessors to query the debug info model. */ public ByteOrder getByteOrder() { return byteOrder; @@ -651,40 +316,42 @@ public List getTypes() { return types; } - public List getInstanceClasses() { - return instanceClasses; + public List getArrayTypes() { + return arrayTypes; } - public List getCompiledMethods() { - return compiledMethods; + public List getPrimitiveTypes() { + return primitiveTypes; } - public List getFiles() { - return files; + public List getPointerTypes() { + return pointerTypes; } - public List getDirs() { - return dirs; + public List getForeignStructTypes() { + return foreignStructTypes; } - @SuppressWarnings("unused") - public FileEntry findFile(Path fullFileName) { - return filesIndex.get(fullFileName); + public List getInstanceClasses() { + return instanceClasses; } - public List getLoaders() { - return loaders; + public List getInstanceClassesWithCompilation() { + return instanceClassesWithCompilation; } - @SuppressWarnings("unused") - public LoaderEntry findLoader(String id) { - return loaderIndex.get(id); + public List getCompiledMethods() { + return compiledMethods; } public StringTable getStringTable() { return stringTable; } + public StringTable getLineStringTable() { + return lineStringTable; + } + /** * Indirects this call to the string table. * @@ -694,6 +361,15 @@ public String uniqueDebugString(String string) { return stringTable.uniqueDebugString(string); } + /** + * Indirects this call to the line string table. + * + * @param string the string whose index is required. + */ + public String uniqueDebugLineString(String string) { + return lineStringTable.uniqueDebugString(string); + } + /** * Indirects this call to the string table. * @@ -704,10 +380,24 @@ public int debugStringIndex(String string) { return stringTable.debugStringIndex(string); } + /** + * Indirects this call to the line string table. + * + * @param string the string whose index is required. + * @return the offset of the string in the .debug_line_str section. + */ + public int debugLineStringIndex(String string) { + return lineStringTable.debugStringIndex(string); + } + public boolean useHeapBase() { return useHeapBase; } + public boolean isRuntimeCompilation() { + return isRuntimeCompilation; + } + public int reservedHubBitsMask() { return reservedHubBitsMask; } @@ -740,60 +430,7 @@ public String getCachePath() { return cachePath; } - private static void collectFilesAndDirs(ClassEntry classEntry) { - // track files and dirs we have already seen so that we only add them once - EconomicSet visitedFiles = EconomicSet.create(); - EconomicSet visitedDirs = EconomicSet.create(); - // add the class's file and dir - includeOnce(classEntry, classEntry.getFileEntry(), visitedFiles, visitedDirs); - // add files for fields (may differ from class file if we have a substitution) - for (FieldEntry fieldEntry : classEntry.fields) { - includeOnce(classEntry, fieldEntry.getFileEntry(), visitedFiles, visitedDirs); - } - // add files for declared methods (may differ from class file if we have a substitution) - for (MethodEntry methodEntry : classEntry.getMethods()) { - includeOnce(classEntry, methodEntry.getFileEntry(), visitedFiles, visitedDirs); - } - // add files for top level compiled and inline methods - classEntry.compiledEntries().forEachOrdered(compiledMethodEntry -> { - includeOnce(classEntry, compiledMethodEntry.getPrimary().getFileEntry(), visitedFiles, visitedDirs); - // we need files for leaf ranges and for inline caller ranges - // - // add leaf range files first because they get searched for linearly - // during line info processing - compiledMethodEntry.leafRangeIterator().forEachRemaining(subRange -> { - includeOnce(classEntry, subRange.getFileEntry(), visitedFiles, visitedDirs); - }); - // now the non-leaf range files - compiledMethodEntry.topDownRangeIterator().forEachRemaining(subRange -> { - if (!subRange.isLeaf()) { - includeOnce(classEntry, subRange.getFileEntry(), visitedFiles, visitedDirs); - } - }); - }); - // now all files and dirs are known build an index for them - classEntry.buildFileAndDirIndexes(); - } - - /** - * Ensure the supplied file entry and associated directory entry are included, but only once, in - * a class entry's file and dir list. - * - * @param classEntry the class entry whose file and dir list may need to be updated - * @param fileEntry a file entry which may need to be added to the class entry's file list or - * whose dir may need adding to the class entry's dir list - * @param visitedFiles a set tracking current file list entries, updated if a file is added - * @param visitedDirs a set tracking current dir list entries, updated if a dir is added - */ - private static void includeOnce(ClassEntry classEntry, FileEntry fileEntry, EconomicSet visitedFiles, EconomicSet visitedDirs) { - if (fileEntry != null && !visitedFiles.contains(fileEntry)) { - visitedFiles.add(fileEntry); - classEntry.includeFile(fileEntry); - DirEntry dirEntry = fileEntry.getDirEntry(); - if (dirEntry != null && !dirEntry.getPathString().isEmpty() && !visitedDirs.contains(dirEntry)) { - visitedDirs.add(dirEntry); - classEntry.includeDir(dirEntry); - } - } + public ClassEntry getForeignMethodListClassEntry() { + return foreignMethodListClassEntry; } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DirEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DirEntry.java index 619e37d26cba..b39b8dc8ac2a 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DirEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DirEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -30,21 +30,12 @@ /** * Tracks the directory associated with one or more source files. - * + *

* This is identified separately from each FileEntry identifying files that reside in the directory. * That is necessary because the line info generator needs to collect and write out directory names * into directory tables once only rather than once per file. */ -public class DirEntry { - private final Path path; - - public DirEntry(Path path) { - this.path = path; - } - - public Path getPath() { - return path; - } +public record DirEntry(Path path) { public String getPathString() { return path.toString(); diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/EnumClassEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/EnumClassEntry.java index f46378d57486..cffe5b43fe49 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/EnumClassEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/EnumClassEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,25 +26,19 @@ package com.oracle.objectfile.debugentry; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugEnumTypeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind; +public final class EnumClassEntry extends ClassEntry { -import jdk.graal.compiler.debug.DebugContext; + // The typedef name if this is a representation of a c enum type + private final String typedefName; -public class EnumClassEntry extends ClassEntry { - public EnumClassEntry(String typeName, FileEntry fileEntry, int size) { - super(typeName, fileEntry, size); + public EnumClassEntry(String typeName, int size, long classOffset, long typeSignature, + long compressedTypeSignature, long layoutTypeSignature, + ClassEntry superClass, FileEntry fileEntry, LoaderEntry loader, String typedefName) { + super(typeName, size, classOffset, typeSignature, compressedTypeSignature, layoutTypeSignature, superClass, fileEntry, loader); + this.typedefName = typedefName; } - @Override - public DebugTypeKind typeKind() { - return DebugTypeKind.ENUM; - } - - @Override - public void addDebugInfo(DebugInfoBase debugInfoBase, DebugTypeInfo debugTypeInfo, DebugContext debugContext) { - assert debugTypeInfo instanceof DebugEnumTypeInfo; - super.addDebugInfo(debugInfoBase, debugTypeInfo, debugContext); + public String getTypedefName() { + return typedefName; } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FieldEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FieldEntry.java index f6e7c6a0cd40..d23b4e61d63d 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FieldEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FieldEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -27,12 +27,13 @@ package com.oracle.objectfile.debugentry; public class FieldEntry extends MemberEntry { + private final int size; private final int offset; - private final boolean isEmbedded; - public FieldEntry(FileEntry fileEntry, String fieldName, StructureTypeEntry ownerType, TypeEntry valueType, int size, int offset, boolean isEmbedded, int modifiers) { + public FieldEntry(FileEntry fileEntry, String fieldName, StructureTypeEntry ownerType, + TypeEntry valueType, int size, int offset, boolean isEmbedded, int modifiers) { super(fileEntry, fieldName, ownerType, valueType, modifiers); this.size = size; this.offset = offset; @@ -40,7 +41,7 @@ public FieldEntry(FileEntry fileEntry, String fieldName, StructureTypeEntry owne } public String fieldName() { - return memberName; + return getMemberName(); } public int getSize() { diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FileEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FileEntry.java index 3ae8f0a4832e..296f0ea30646 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FileEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FileEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -28,26 +28,13 @@ /** * Tracks debug info associated with a Java source file. + * + * @param fileName The name of the associated file excluding path elements. + * @param dirEntry The directory entry associated with this file entry. */ -public class FileEntry { - private final String fileName; - private final DirEntry dirEntry; - - public FileEntry(String fileName, DirEntry dirEntry) { - this.fileName = fileName; - this.dirEntry = dirEntry; - } - - /** - * The name of the associated file excluding path elements. - */ - public String getFileName() { - return fileName; - } +public record FileEntry(String fileName, DirEntry dirEntry) { public String getPathName() { - @SuppressWarnings("hiding") - DirEntry dirEntry = getDirEntry(); if (dirEntry == null) { return ""; } else { @@ -59,22 +46,15 @@ public String getFullName() { if (dirEntry == null) { return fileName; } else { - return dirEntry.getPath().resolve(getFileName()).toString(); + return dirEntry.path().resolve(fileName).toString(); } } - /** - * The directory entry associated with this file entry. - */ - public DirEntry getDirEntry() { - return dirEntry; - } - @Override public String toString() { - if (getDirEntry() == null) { - return getFileName() == null ? "-" : getFileName(); - } else if (getFileName() == null) { + if (dirEntry == null) { + return fileName == null ? "-" : fileName; + } else if (fileName == null) { return "--"; } return String.format("FileEntry(%s)", getFullName()); diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignStructTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignStructTypeEntry.java new file mode 100644 index 000000000000..3a6860121929 --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignStructTypeEntry.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.debugentry; + +public final class ForeignStructTypeEntry extends StructureTypeEntry { + + private final String typedefName; + private final ForeignStructTypeEntry parent; + + public ForeignStructTypeEntry(String typeName, int size, long classOffset, long typeSignature, + long layoutTypeSignature, String typedefName, ForeignStructTypeEntry parent) { + super(typeName, size, classOffset, typeSignature, typeSignature, layoutTypeSignature); + this.typedefName = typedefName; + this.parent = parent; + } + + public String getTypedefName() { + return typedefName; + } + + public ForeignStructTypeEntry getParent() { + return parent; + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignTypeEntry.java deleted file mode 100644 index dc3dae992f16..000000000000 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ForeignTypeEntry.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package com.oracle.objectfile.debugentry; - -import com.oracle.objectfile.debuginfo.DebugInfoProvider; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugForeignTypeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo; - -import jdk.graal.compiler.debug.DebugContext; -import jdk.vm.ci.meta.ResolvedJavaType; - -public class ForeignTypeEntry extends ClassEntry { - private static final int FLAG_WORD = 1 << 0; - private static final int FLAG_STRUCT = 1 << 1; - private static final int FLAG_POINTER = 1 << 2; - private static final int FLAG_INTEGRAL = 1 << 3; - private static final int FLAG_SIGNED = 1 << 4; - private static final int FLAG_FLOAT = 1 << 5; - private String typedefName; - private ForeignTypeEntry parent; - private TypeEntry pointerTo; - private int flags; - - public ForeignTypeEntry(String className, FileEntry fileEntry, int size) { - super(className, fileEntry, size); - typedefName = null; - parent = null; - pointerTo = null; - flags = 0; - } - - @Override - public DebugInfoProvider.DebugTypeInfo.DebugTypeKind typeKind() { - return DebugInfoProvider.DebugTypeInfo.DebugTypeKind.FOREIGN; - } - - /* - * When we process a foreign pointer type for the .debug_info section we want to reuse its type - * signature. After sizing the .debug_info the layout type signature contains the type signature - * of the type this foreign pointer points to. - */ - public void setLayoutTypeSignature(long typeSignature) { - this.layoutTypeSignature = typeSignature; - } - - @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 (debugContext.isLogEnabled()) { - if (isPointer() && pointerTo != null) { - debugContext.log("foreign type %s flags 0x%x referent %s ", typeName, flags, pointerTo.getTypeName()); - } else { - debugContext.log("foreign type %s flags 0x%x", typeName, flags); - } - } - } - - public 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/FrameSizeChangeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FrameSizeChangeEntry.java new file mode 100644 index 000000000000..b4c6d15e00cd --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/FrameSizeChangeEntry.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.debugentry; + +import com.oracle.objectfile.debuginfo.DebugInfoProvider.FrameSizeChangeType; + +public record FrameSizeChangeEntry(int offset, FrameSizeChangeType type) { + public boolean isExtend() { + return type == FrameSizeChangeType.EXTEND; + } + + public boolean isContract() { + return type == FrameSizeChangeType.CONTRACT; + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/HeaderTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/HeaderTypeEntry.java index ef6027e6c3d6..d4135fb9b5aa 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/HeaderTypeEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/HeaderTypeEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,35 +26,19 @@ package com.oracle.objectfile.debugentry; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugHeaderTypeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind; +public final class HeaderTypeEntry extends StructureTypeEntry { -import jdk.graal.compiler.debug.DebugContext; + private FieldEntry hubField; -public class HeaderTypeEntry extends StructureTypeEntry { - - FieldEntry hubField; + public HeaderTypeEntry(String typeName, int size, long typeSignature) { + super(typeName, size, -1, typeSignature, typeSignature, typeSignature); + } - public HeaderTypeEntry(String typeName, int size) { - super(typeName, size); + public void setHubField(FieldEntry hubField) { + this.hubField = hubField; } public FieldEntry getHubField() { return hubField; } - - @Override - public DebugTypeKind typeKind() { - return DebugTypeKind.HEADER; - } - - @Override - public void addDebugInfo(DebugInfoBase debugInfoBase, DebugTypeInfo debugTypeInfo, DebugContext debugContext) { - super.addDebugInfo(debugInfoBase, debugTypeInfo, debugContext); - assert debugTypeInfo.typeName().equals(typeName); - DebugHeaderTypeInfo debugHeaderTypeInfo = (DebugHeaderTypeInfo) debugTypeInfo; - debugHeaderTypeInfo.fieldInfoProvider().forEach(debugFieldInfo -> this.processField(debugFieldInfo, debugInfoBase, debugContext)); - hubField = createField(debugHeaderTypeInfo.hubField(), debugInfoBase, debugContext); - } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/InterfaceClassEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/InterfaceClassEntry.java index 42b2e7ac2d03..1aca4a2b4781 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/InterfaceClassEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/InterfaceClassEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,44 +26,26 @@ package com.oracle.objectfile.debugentry; -import java.util.ArrayList; +import java.util.Comparator; import java.util.List; -import java.util.stream.Stream; +import java.util.concurrent.ConcurrentSkipListSet; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugInterfaceTypeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind; +public final class InterfaceClassEntry extends ClassEntry { + private final ConcurrentSkipListSet implementors; -import jdk.graal.compiler.debug.DebugContext; - -public class InterfaceClassEntry extends ClassEntry { - private final List implementors; - - public InterfaceClassEntry(String className, FileEntry fileEntry, int size) { - super(className, fileEntry, size); - implementors = new ArrayList<>(); - } - - @Override - public DebugTypeKind typeKind() { - return DebugTypeKind.INTERFACE; + public InterfaceClassEntry(String typeName, int size, long classOffset, long typeSignature, + long compressedTypeSignature, long layoutTypeSignature, + ClassEntry superClass, FileEntry fileEntry, LoaderEntry loader) { + super(typeName, size, classOffset, typeSignature, compressedTypeSignature, layoutTypeSignature, superClass, fileEntry, loader); + this.implementors = new ConcurrentSkipListSet<>(Comparator.comparingLong(ClassEntry::getTypeSignature)); } - @Override - public void addDebugInfo(DebugInfoBase debugInfoBase, DebugTypeInfo debugTypeInfo, DebugContext debugContext) { - assert debugTypeInfo instanceof DebugInterfaceTypeInfo; - super.addDebugInfo(debugInfoBase, debugTypeInfo, debugContext); - } - - public void addImplementor(ClassEntry classEntry, DebugContext debugContext) { + public void addImplementor(ClassEntry classEntry) { implementors.add(classEntry); - if (debugContext.isLogEnabled()) { - debugContext.log("typename %s add implementor %s%n", typeName, classEntry.getTypeName()); - } } - public Stream implementors() { - return implementors.stream(); + public List getImplementors() { + return List.copyOf(implementors); } @Override @@ -75,7 +57,7 @@ public int getSize() { * size of the wrapper class that handles address translation for values embedded in object * fields. */ - int maxSize = super.size; + int maxSize = super.getSize(); for (ClassEntry implementor : implementors) { int nextSize = implementor.getSize(); diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LoaderEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LoaderEntry.java index f3390e29e802..ed4fbe480081 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LoaderEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LoaderEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,19 +26,10 @@ package com.oracle.objectfile.debugentry; /** - * A LoaderEntry is used to record a unique id string for the class loader asscoiated with classes + * A LoaderEntry is used to record a unique id string for the class loader associated with classes * that can be susceptible to repeat definitions. In such cases the loader id can be used when * constructing a unique linker symbol for methods and static fields of the class. That same id may * need to be embedded in debug info that identifies class and method names. */ -public class LoaderEntry { - private final String loaderId; - - public LoaderEntry(String id) { - loaderId = id; - } - - public String getLoaderId() { - return loaderId; - } +public record LoaderEntry(String loaderId) { } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalEntry.java new file mode 100644 index 000000000000..2c95bf44bd71 --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalEntry.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.debugentry; + +import java.util.concurrent.atomic.AtomicInteger; + +public record LocalEntry(String name, TypeEntry type, int slot, AtomicInteger line) { + + public LocalEntry(String name, TypeEntry type, int slot, int line) { + /* + * Use a AtomicInteger for the line number as it might change if we encounter the same local + * variable in a different frame state with a lower line number + */ + this(name, type, slot, new AtomicInteger(line)); + } + + @Override + public String toString() { + return String.format("Local(%s type=%s slot=%d line=%d)", name, type.getTypeName(), slot, getLine()); + } + + public void setLine(int newLine) { + this.line.set(newLine); + } + + public int getLine() { + return line.get(); + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalValueEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalValueEntry.java new file mode 100644 index 000000000000..a676ba2018db --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/LocalValueEntry.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.debugentry; + +public interface LocalValueEntry { +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MemberEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MemberEntry.java index 48a5bf55ffca..461f2092bdae 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MemberEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MemberEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,17 +26,19 @@ package com.oracle.objectfile.debugentry; +import java.lang.reflect.Modifier; + /** * An abstract class providing modelling a generic class member which includes behaviour and data * shared by both field and method entries. */ public abstract class MemberEntry { - protected FileEntry fileEntry; - protected final int line; - protected final String memberName; - protected final StructureTypeEntry ownerType; - protected final TypeEntry valueType; - protected final int modifiers; + private final FileEntry fileEntry; + private final int line; + private final String memberName; + private final StructureTypeEntry ownerType; + private final TypeEntry valueType; + private final int modifiers; public MemberEntry(FileEntry fileEntry, String memberName, StructureTypeEntry ownerType, TypeEntry valueType, int modifiers) { this(fileEntry, 0, memberName, ownerType, valueType, modifiers); @@ -54,7 +56,7 @@ public MemberEntry(FileEntry fileEntry, int line, String memberName, StructureTy public String getFileName() { if (fileEntry != null) { - return fileEntry.getFileName(); + return fileEntry.fileName(); } else { return ""; } @@ -64,7 +66,7 @@ public String getFullFileName() { if (fileEntry != null) { return fileEntry.getFullName(); } else { - return null; + return ""; } } @@ -81,6 +83,12 @@ public FileEntry getFileEntry() { return fileEntry; } + /** + * Fetch the file index from its owner class entry with {@link ClassEntry#getFileIdx}. The file + * entry must only be fetched for members whose owner is a {@link ClassEntry}. + * + * @return the file index of this members file in the owner class entry + */ public int getFileIdx() { if (ownerType instanceof ClassEntry) { return ((ClassEntry) ownerType).getFileIdx(fileEntry); @@ -90,11 +98,15 @@ public int getFileIdx() { return 1; } + public String getMemberName() { + return memberName; + } + public int getLine() { return line; } - public StructureTypeEntry ownerType() { + public StructureTypeEntry getOwnerType() { return ownerType; } @@ -106,7 +118,45 @@ public int getModifiers() { return modifiers; } + public static String memberModifiers(int modifiers) { + StringBuilder builder = new StringBuilder(); + if (Modifier.isPublic(modifiers)) { + builder.append("public "); + } else if (Modifier.isProtected(modifiers)) { + builder.append("protected "); + } else if (Modifier.isPrivate(modifiers)) { + builder.append("private "); + } + if (Modifier.isFinal(modifiers)) { + builder.append("final "); + } + if (Modifier.isAbstract(modifiers)) { + builder.append("abstract "); + } else if (Modifier.isVolatile(modifiers)) { + builder.append("volatile "); + } else if (Modifier.isTransient(modifiers)) { + builder.append("transient "); + } else if (Modifier.isSynchronized(modifiers)) { + builder.append("synchronized "); + } + if (Modifier.isNative(modifiers)) { + builder.append("native "); + } + if (Modifier.isStatic(modifiers)) { + builder.append("static"); + } else { + builder.append("instance"); + } + + return builder.toString(); + } + public String getModifiersString() { - return ownerType.memberModifiers(modifiers); + return memberModifiers(modifiers); + } + + @Override + public String toString() { + return String.format("Member(%s %s %s owner=%s %s:%d)", getModifiersString(), valueType.getTypeName(), memberName, ownerType.getTypeName(), getFullFileName(), line); } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MethodEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MethodEntry.java index d583784898ad..8e510d5d15d6 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MethodEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MethodEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,198 +26,183 @@ package com.oracle.objectfile.debugentry; -import java.util.ArrayList; -import java.util.ListIterator; - -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugCodeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalValueInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocationInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugMethodInfo; - -import jdk.vm.ci.meta.JavaKind; -import jdk.vm.ci.meta.ResolvedJavaType; +import java.util.Comparator; +import java.util.List; +import java.util.SortedSet; +import java.util.concurrent.ConcurrentSkipListSet; public class MethodEntry extends MemberEntry { - private final TypeEntry[] paramTypes; - private final DebugLocalInfo thisParam; - private final DebugLocalInfo[] paramInfos; - private final int firstLocalSlot; + private final LocalEntry thisParam; + private final SortedSet paramInfos; + private final int lastParamSlot; // local vars are accumulated as they are referenced sorted by slot, then name, then // type name. we don't currently deal handle references to locals with no slot. - private final ArrayList locals; - static final int DEOPT = 1 << 0; - static final int IN_RANGE = 1 << 1; - static final int INLINED = 1 << 2; - static final int IS_OVERRIDE = 1 << 3; - static final int IS_CONSTRUCTOR = 1 << 4; - private int flags; + private final ConcurrentSkipListSet locals; + private final boolean isDeopt; + private boolean isInRange; + private boolean isInlined; + private final boolean isOverride; + private final boolean isConstructor; private final int vtableOffset; private final String symbolName; - @SuppressWarnings("this-escape") - public MethodEntry(DebugInfoBase debugInfoBase, DebugMethodInfo debugMethodInfo, - FileEntry fileEntry, int line, String methodName, ClassEntry ownerType, - TypeEntry valueType, TypeEntry[] paramTypes, DebugLocalInfo[] paramInfos, DebugLocalInfo thisParam) { - super(fileEntry, line, methodName, ownerType, valueType, debugMethodInfo.modifiers()); - this.paramTypes = paramTypes; + public MethodEntry(FileEntry fileEntry, int line, String methodName, StructureTypeEntry ownerType, + TypeEntry valueType, int modifiers, SortedSet paramInfos, LocalEntry thisParam, + String symbolName, boolean isDeopt, boolean isOverride, boolean isConstructor, int vtableOffset, + int lastParamSlot, List locals) { + super(fileEntry, line, methodName, ownerType, valueType, modifiers); this.paramInfos = paramInfos; this.thisParam = thisParam; - this.symbolName = debugMethodInfo.symbolNameForMethod(); - this.flags = 0; - if (debugMethodInfo.isDeoptTarget()) { - setIsDeopt(); - } - if (debugMethodInfo.isConstructor()) { - setIsConstructor(); - } - if (debugMethodInfo.isOverride()) { - setIsOverride(); - } - vtableOffset = debugMethodInfo.vtableOffset(); - int paramCount = paramInfos.length; - if (paramCount > 0) { - DebugLocalInfo lastParam = paramInfos[paramCount - 1]; - firstLocalSlot = lastParam.slot() + lastParam.slotCount(); - } else { - firstLocalSlot = (thisParam == null ? 0 : thisParam.slotCount()); - } - locals = new ArrayList<>(); - updateRangeInfo(debugInfoBase, debugMethodInfo); + this.symbolName = symbolName; + this.isDeopt = isDeopt; + this.isOverride = isOverride; + this.isConstructor = isConstructor; + this.vtableOffset = vtableOffset; + this.lastParamSlot = lastParamSlot; + + this.locals = new ConcurrentSkipListSet<>(Comparator.comparingInt(LocalEntry::slot).thenComparing(LocalEntry::name).thenComparingLong(le -> le.type().getTypeSignature())); + /* + * Sort by line and add all locals, such that the methods locals only contain the lowest + * line number. + */ + locals.stream().sorted(Comparator.comparingInt(LocalEntry::getLine)).forEach(this.locals::add); + + /* + * Flags to identify compiled methods. We set inRange if there is a compilation for this + * method and inlined if it is encountered as a CallNode when traversing the compilation + * result frame tree. + */ + this.isInRange = false; + this.isInlined = false; } - public String methodName() { - return memberName; + public String getMethodName() { + return getMemberName(); } @Override - public ClassEntry ownerType() { + public ClassEntry getOwnerType() { + StructureTypeEntry ownerType = super.getOwnerType(); assert ownerType instanceof ClassEntry; return (ClassEntry) ownerType; } - public int getParamCount() { - return paramInfos.length; - } - - public TypeEntry getParamType(int idx) { - assert idx < paramInfos.length; - return paramTypes[idx]; - } - - public TypeEntry[] getParamTypes() { - return paramTypes; + public List getParamTypes() { + return paramInfos.stream().map(LocalEntry::type).toList(); } - public String getParamTypeName(int idx) { - assert idx < paramTypes.length; - return paramTypes[idx].getTypeName(); + public List getParams() { + return List.copyOf(paramInfos); } - public String getParamName(int idx) { - assert idx < paramInfos.length; - /* N.b. param names may be null. */ - return paramInfos[idx].name(); + public LocalEntry getThisParam() { + return thisParam; } - public int getParamLine(int idx) { - assert idx < paramInfos.length; - /* N.b. param names may be null. */ - return paramInfos[idx].line(); - } + /** + * Returns the local entry for a given name slot and type entry. Decides with the slot number + * whether this is a parameter or local variable. + * + *

+ * Local variable might not be contained in this method entry. Creates a new local entry in + * {@link #locals} with the given parameters. + *

+ * For locals, we also make sure that the line information is the lowest line encountered in + * local variable lookups. If a new lower line number is encountered for an existing local + * entry, we update the line number in the local entry. + * + * @param name the given name + * @param slot the given slot + * @param type the given {@code TypeEntry} + * @param line the given line + * @return the local entry stored in {@link #locals} + */ + public LocalEntry lookupLocalEntry(String name, int slot, TypeEntry type, int line) { + if (slot < 0) { + return null; + } - public DebugLocalInfo getParam(int i) { - assert i >= 0 && i < paramInfos.length : "bad param index"; - return paramInfos[i]; - } + if (slot <= lastParamSlot) { + if (thisParam != null) { + if (thisParam.slot() == slot && thisParam.name().equals(name) && thisParam.type().equals(type)) { + return thisParam; + } + } + for (LocalEntry param : paramInfos) { + if (param.slot() == slot && param.name().equals(name) && param.type().equals(type)) { + return param; + } + } + } else { + /* + * Add a new local entry if it was not already present in the locals list. A local is + * unique if it has different slot, name, and/or type. If the local is already + * contained, we might update the line number to the lowest occurring line number. + */ + LocalEntry local = new LocalEntry(name, type, slot, line); + if (!locals.add(local)) { + /* + * Fetch local from locals list. This iterates over all locals to create the head + * set and then takes the last value. + */ + local = locals.headSet(local, true).last(); + // Update line number if a lower one was encountered. + if (local.getLine() > line) { + local.setLine(line); + } + } + return local; + } - public DebugLocalInfo getThisParam() { - return thisParam; + /* + * The slot is within the range of the params, but none of the params exactly match. This + * might be some local value that is stored in a slot where we expect a param. We just + * ignore such values for now. + * + * This also applies to params that are inferred from frame values, as the types do not + * match most of the time. + */ + return null; } - public int getLocalCount() { - return locals.size(); + public List getLocals() { + return List.copyOf(locals); } - public DebugLocalInfo getLocal(int i) { - assert i >= 0 && i < locals.size() : "bad param index"; - return locals.get(i); + public int getLastParamSlot() { + return lastParamSlot; } - private void setIsDeopt() { - flags |= DEOPT; + public boolean isStatic() { + return thisParam == null; } public boolean isDeopt() { - return (flags & DEOPT) != 0; - } - - private void setIsInRange() { - flags |= IN_RANGE; + return isDeopt; } public boolean isInRange() { - return (flags & IN_RANGE) != 0; + return isInRange; } - private void setIsInlined() { - flags |= INLINED; + public void setInRange() { + isInRange = true; } public boolean isInlined() { - return (flags & INLINED) != 0; + return isInlined; } - private void setIsOverride() { - flags |= IS_OVERRIDE; + public void setInlined() { + isInlined = true; } public boolean isOverride() { - return (flags & IS_OVERRIDE) != 0; - } - - private void setIsConstructor() { - flags |= IS_CONSTRUCTOR; + return isOverride; } public boolean isConstructor() { - return (flags & IS_CONSTRUCTOR) != 0; - } - - /** - * Sets {@code isInRange} and ensures that the {@code fileEntry} is up to date. If the - * MethodEntry was added by traversing the DeclaredMethods of a Class its fileEntry will point - * to the original source file, thus it will be wrong for substituted methods. As a result when - * setting a MethodEntry as isInRange we also make sure that its fileEntry reflects the file - * info associated with the corresponding Range. - * - * @param debugInfoBase - * @param debugMethodInfo - */ - public void updateRangeInfo(DebugInfoBase debugInfoBase, DebugMethodInfo debugMethodInfo) { - if (debugMethodInfo instanceof DebugLocationInfo) { - DebugLocationInfo locationInfo = (DebugLocationInfo) debugMethodInfo; - if (locationInfo.getCaller() != null) { - /* this is a real inlined method */ - setIsInlined(); - } - } else if (debugMethodInfo instanceof DebugCodeInfo) { - /* this method is being notified as a top level compiled method */ - if (isInRange()) { - /* it has already been seen -- just check for consistency */ - assert fileEntry == debugInfoBase.ensureFileEntry(debugMethodInfo); - } else { - /* - * If the MethodEntry was added by traversing the DeclaredMethods of a Class its - * fileEntry may point to the original source file, which will be wrong for - * substituted methods. As a result when setting a MethodEntry as isInRange we also - * make sure that its fileEntry reflects the file info associated with the - * corresponding Range. - */ - setIsInRange(); - fileEntry = debugInfoBase.ensureFileEntry(debugMethodInfo); - } - } + return isConstructor; } public boolean isVirtual() { @@ -231,149 +216,4 @@ public int getVtableOffset() { public String getSymbolName() { return symbolName; } - - /** - * Return a unique local or parameter variable associated with the value, optionally recording - * it as a new local variable or fail, returning null, when the local value does not conform - * with existing recorded parameter or local variables. Values with invalid (negative) slots - * always fail. Values whose slot is associated with a parameter only conform if their name and - * type equal those of the parameter. Values whose slot is in the local range will always - * succeed,. either by matchign the slot and name of an existing local or by being recorded as a - * new local variable. - * - * @param localValueInfo - * @return the unique local variable with which this local value can be legitimately associated - * otherwise null. - */ - public DebugLocalInfo recordLocal(DebugLocalValueInfo localValueInfo) { - int slot = localValueInfo.slot(); - if (slot < 0) { - return null; - } else { - if (slot < firstLocalSlot) { - return matchParam(localValueInfo); - } else { - return matchLocal(localValueInfo); - } - } - } - - private DebugLocalInfo matchParam(DebugLocalValueInfo localValueInfo) { - if (thisParam != null) { - if (checkMatch(thisParam, localValueInfo)) { - return thisParam; - } - } - for (int i = 0; i < paramInfos.length; i++) { - DebugLocalInfo paramInfo = paramInfos[i]; - if (checkMatch(paramInfo, localValueInfo)) { - return paramInfo; - } - } - return null; - } - - /** - * wrapper class for a local value that stands in as a unique identifier for the associated - * local variable while allowing its line to be adjusted when earlier occurrences of the same - * local are identified. - */ - private static class DebugLocalInfoWrapper implements DebugLocalInfo { - DebugLocalValueInfo value; - int line; - - DebugLocalInfoWrapper(DebugLocalValueInfo value) { - this.value = value; - this.line = value.line(); - } - - @Override - public ResolvedJavaType valueType() { - return value.valueType(); - } - - @Override - public String name() { - return value.name(); - } - - @Override - public String typeName() { - return value.typeName(); - } - - @Override - public int slot() { - return value.slot(); - } - - @Override - public int slotCount() { - return value.slotCount(); - } - - @Override - public JavaKind javaKind() { - return value.javaKind(); - } - - @Override - public int line() { - return line; - } - - public void setLine(int line) { - this.line = line; - } - } - - private DebugLocalInfo matchLocal(DebugLocalValueInfo localValueInfo) { - ListIterator listIterator = locals.listIterator(); - while (listIterator.hasNext()) { - DebugLocalInfoWrapper next = (DebugLocalInfoWrapper) listIterator.next(); - if (checkMatch(next, localValueInfo)) { - int currentLine = next.line(); - int newLine = localValueInfo.line(); - if ((currentLine < 0 && newLine >= 0) || - (newLine >= 0 && newLine < currentLine)) { - next.setLine(newLine); - } - return next; - } else if (next.slot() > localValueInfo.slot()) { - // we have iterated just beyond the insertion point - // so wind cursor back one element - listIterator.previous(); - break; - } - } - DebugLocalInfoWrapper newLocal = new DebugLocalInfoWrapper(localValueInfo); - // add at the current cursor position - listIterator.add(newLocal); - return newLocal; - } - - boolean checkMatch(DebugLocalInfo local, DebugLocalValueInfo value) { - boolean isMatch = (local.slot() == value.slot() && - local.name().equals(value.name()) && - local.typeName().equals(value.typeName())); - assert !isMatch || verifyMatch(local, value) : "failed to verify matched var and value"; - return isMatch; - } - - private static boolean verifyMatch(DebugLocalInfo local, DebugLocalValueInfo value) { - // slot counts are normally expected to match - if (local.slotCount() == value.slotCount()) { - return true; - } - // we can have a zero count for the local or value if it is undefined - if (local.slotCount() == 0 || value.slotCount() == 0) { - return true; - } - // pseudo-object locals can appear as longs - if (local.javaKind() == JavaKind.Object && value.javaKind() == JavaKind.Long) { - return true; - } - // something is wrong - return false; - } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PointerToTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PointerToTypeEntry.java new file mode 100644 index 000000000000..85769c69a424 --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PointerToTypeEntry.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.debugentry; + +public final class PointerToTypeEntry extends TypeEntry { + private TypeEntry pointerTo; + + public PointerToTypeEntry(String typeName, int size, long classOffset, long typeSignature, TypeEntry pointerTo) { + super(typeName, size, classOffset, typeSignature, typeSignature); + this.pointerTo = pointerTo; + } + + public TypeEntry getPointerTo() { + return pointerTo; + } + + public void setPointerTo(TypeEntry pointerTo) { + this.pointerTo = pointerTo; + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PrimitiveTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PrimitiveTypeEntry.java index a60ed3b1b1fd..6330e061c415 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PrimitiveTypeEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PrimitiveTypeEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,77 +26,40 @@ package com.oracle.objectfile.debugentry; -import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugPrimitiveTypeInfo.FLAG_INTEGRAL; -import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugPrimitiveTypeInfo.FLAG_NUMERIC; -import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugPrimitiveTypeInfo.FLAG_SIGNED; +import jdk.vm.ci.meta.JavaKind; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugPrimitiveTypeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind; +public final class PrimitiveTypeEntry extends TypeEntry { -import jdk.graal.compiler.debug.DebugContext; + private final int bitCount; + private final boolean isNumericInteger; + private final boolean isNumericFloat; + private final boolean isUnsigned; -public class PrimitiveTypeEntry extends TypeEntry { - private char typeChar; - private int flags; - private int bitCount; - - public PrimitiveTypeEntry(String typeName, int size) { - super(typeName, size); - typeChar = '#'; - flags = 0; - bitCount = 0; - } - - @Override - public DebugTypeKind typeKind() { - return DebugTypeKind.PRIMITIVE; + public PrimitiveTypeEntry(String typeName, int size, long classOffset, long typeSignature, JavaKind kind) { + this(typeName, size, classOffset, typeSignature, kind == JavaKind.Void ? 0 : kind.getBitCount(), kind.isNumericInteger(), kind.isNumericFloat(), kind.isUnsigned()); } - @Override - public void addDebugInfo(DebugInfoBase debugInfoBase, DebugTypeInfo debugTypeInfo, DebugContext debugContext) { - super.addDebugInfo(debugInfoBase, debugTypeInfo, debugContext); - DebugPrimitiveTypeInfo debugPrimitiveTypeInfo = (DebugPrimitiveTypeInfo) debugTypeInfo; - flags = debugPrimitiveTypeInfo.flags(); - typeChar = debugPrimitiveTypeInfo.typeChar(); - bitCount = debugPrimitiveTypeInfo.bitCount(); - if (debugContext.isLogEnabled()) { - debugContext.log("typename %s %s (%d bits)%n", typeName, decodeFlags(), bitCount); - } + public PrimitiveTypeEntry(String typeName, int size, long classOffset, long typeSignature, int bitCount, boolean isNumericInteger, boolean isNumericFloat, boolean isUnsigned) { + super(typeName, size, classOffset, typeSignature, typeSignature); + this.bitCount = bitCount; + this.isNumericInteger = isNumericInteger; + this.isNumericFloat = isNumericFloat; + this.isUnsigned = isUnsigned; } - private String decodeFlags() { - StringBuilder builder = new StringBuilder(); - if ((flags & FLAG_NUMERIC) != 0) { - if ((flags & FLAG_INTEGRAL) != 0) { - if ((flags & FLAG_SIGNED) != 0) { - builder.append("SIGNED "); - } else { - builder.append("UNSIGNED "); - } - builder.append("INTEGRAL"); - } else { - builder.append("FLOATING"); - } - } else { - if (bitCount > 0) { - builder.append("LOGICAL"); - } else { - builder.append("VOID"); - } - } - return builder.toString(); + public int getBitCount() { + return bitCount; } - public char getTypeChar() { - return typeChar; + public boolean isNumericInteger() { + return isNumericInteger; } - public int getBitCount() { - return bitCount; + public boolean isNumericFloat() { + return isNumericFloat; } - public int getFlags() { - return flags; + public boolean isUnsigned() { + return isUnsigned; } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/RegisterValueEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/RegisterValueEntry.java new file mode 100644 index 000000000000..501a0a64cefa --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/RegisterValueEntry.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.debugentry; + +public record RegisterValueEntry(int regIndex) implements LocalValueEntry { + + @Override + public String toString() { + return "REG:" + regIndex; + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StackValueEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StackValueEntry.java new file mode 100644 index 000000000000..ac79be0943fe --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StackValueEntry.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.debugentry; + +public record StackValueEntry(int stackSlot) implements LocalValueEntry { + + @Override + public String toString() { + return "STACK:" + stackSlot; + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringEntry.java index 01eb6fb9de38..86e64e0069f8 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -33,7 +33,6 @@ public class StringEntry { private final String string; private int offset; - private boolean addToStrSection; StringEntry(String string) { this.string = string; @@ -55,20 +54,11 @@ public void setOffset(int offset) { this.offset = offset; } - public boolean isAddToStrSection() { - return addToStrSection; - } - - public void setAddToStrSection() { - this.addToStrSection = true; - } - @Override public boolean equals(Object object) { - if (object == null || !(object instanceof StringEntry)) { + if (!(object instanceof StringEntry other)) { return false; } else { - StringEntry other = (StringEntry) object; return this == other || string.equals(other.string); } } @@ -80,10 +70,10 @@ public int hashCode() { @Override public String toString() { - return string; + return "'" + string + "'@" + Integer.toHexString(offset); } public boolean isEmpty() { - return string.length() == 0; + return string.isEmpty(); } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringTable.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringTable.java index 1242c79a154e..ffc0811660ab 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringTable.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StringTable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,8 +26,8 @@ package com.oracle.objectfile.debugentry; -import java.util.HashMap; import java.util.Iterator; +import java.util.concurrent.ConcurrentHashMap; /** * Allows incoming strings to be reduced to unique (up to equals) instances and supports marking of @@ -36,22 +36,12 @@ */ public class StringTable implements Iterable { - private final HashMap table; + private final ConcurrentHashMap table; public StringTable() { - this.table = new HashMap<>(); - } - - /** - * Ensures a unique instance of a string exists in the table, inserting the supplied String if - * no equivalent String is already present. This should only be called before the string section - * has been written. - * - * @param string the string to be included in the table - * @return the unique instance of the String - */ - public String uniqueString(String string) { - return ensureString(string, false); + this.table = new ConcurrentHashMap<>(); + /* Ensure we have a null string at the start of the string table. */ + this.table.put("", new StringEntry("")); } /** @@ -64,19 +54,7 @@ public String uniqueString(String string) { * @return the unique instance of the String */ public String uniqueDebugString(String string) { - return ensureString(string, true); - } - - private String ensureString(String string, boolean addToStrSection) { - StringEntry stringEntry = table.get(string); - if (stringEntry == null) { - stringEntry = new StringEntry(string); - table.put(string, stringEntry); - } - if (addToStrSection && !stringEntry.isAddToStrSection()) { - stringEntry.setAddToStrSection(); - } - return stringEntry.getString(); + return table.computeIfAbsent(string, StringEntry::new).getString(); } /** @@ -90,9 +68,6 @@ private String ensureString(String string, boolean addToStrSection) { public int debugStringIndex(String string) { StringEntry stringEntry = table.get(string); assert stringEntry != null : "\"" + string + "\" not in string table"; - if (stringEntry == null) { - return -1; - } return stringEntry.getOffset(); } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java index ddb78e28247e..3aebfd6ca792 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/StructureTypeEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,27 +26,19 @@ package com.oracle.objectfile.debugentry; -import java.lang.reflect.Modifier; -import java.util.ArrayList; +import java.util.Comparator; import java.util.List; -import java.util.stream.Stream; - -import com.oracle.objectfile.debuginfo.DebugInfoProvider; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFieldInfo; -import com.oracle.objectfile.elf.dwarf.DwarfDebugInfo; - -import jdk.graal.compiler.debug.DebugContext; -import jdk.vm.ci.meta.ResolvedJavaType; +import java.util.concurrent.ConcurrentSkipListSet; /** * An intermediate type that provides behaviour for managing fields. This unifies code for handling * header structures and Java instance and array classes that both support data members. */ -public abstract class StructureTypeEntry extends TypeEntry { +public abstract sealed class StructureTypeEntry extends TypeEntry permits ArrayTypeEntry, ClassEntry, ForeignStructTypeEntry, HeaderTypeEntry { /** * Details of fields located in this instance. */ - protected final List fields; + private final ConcurrentSkipListSet fields; /** * The type signature of this types' layout. The layout of a type contains debug info of fields @@ -55,92 +47,23 @@ public abstract class StructureTypeEntry extends TypeEntry { */ protected long layoutTypeSignature; - public StructureTypeEntry(String typeName, int size) { - super(typeName, size); - this.fields = new ArrayList<>(); - this.layoutTypeSignature = 0; + public StructureTypeEntry(String typeName, int size, long classOffset, long typeSignature, + long compressedTypeSignature, long layoutTypeSignature) { + super(typeName, size, classOffset, typeSignature, compressedTypeSignature); + this.layoutTypeSignature = layoutTypeSignature; + + this.fields = new ConcurrentSkipListSet<>(Comparator.comparingInt(FieldEntry::getOffset)); } public long getLayoutTypeSignature() { return layoutTypeSignature; } - 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. */ - FieldEntry fieldEntry = createField(debugFieldInfo, debugInfoBase, debugContext); - fields.add(fieldEntry); - } - - protected FieldEntry createField(DebugFieldInfo debugFieldInfo, DebugInfoBase debugInfoBase, DebugContext debugContext) { - String fieldName = debugInfoBase.uniqueDebugString(debugFieldInfo.name()); - ResolvedJavaType valueType = debugFieldInfo.valueType(); - String valueTypeName = valueType.toJavaName(); - int fieldSize = debugFieldInfo.size(); - int fieldoffset = debugFieldInfo.offset(); - boolean fieldIsEmbedded = debugFieldInfo.isEmbedded(); - int fieldModifiers = debugFieldInfo.modifiers(); - if (debugContext.isLogEnabled()) { - debugContext.log("typename %s adding %s field %s type %s%s size %s at offset 0x%x%n", - typeName, memberModifiers(fieldModifiers), fieldName, valueTypeName, (fieldIsEmbedded ? "(embedded)" : ""), fieldSize, fieldoffset); - } - TypeEntry valueTypeEntry = debugInfoBase.lookupTypeEntry(valueType); - /* - * n.b. the field file may differ from the owning class file when the field is a - * substitution - */ - FileEntry fileEntry = debugInfoBase.ensureFileEntry(debugFieldInfo); - return new FieldEntry(fileEntry, fieldName, this, valueTypeEntry, fieldSize, fieldoffset, fieldIsEmbedded, fieldModifiers); - } - - String memberModifiers(int modifiers) { - StringBuilder builder = new StringBuilder(); - if (Modifier.isPublic(modifiers)) { - builder.append("public "); - } else if (Modifier.isProtected(modifiers)) { - builder.append("protected "); - } else if (Modifier.isPrivate(modifiers)) { - builder.append("private "); - } - if (Modifier.isFinal(modifiers)) { - builder.append("final "); - } - if (Modifier.isAbstract(modifiers)) { - builder.append("abstract "); - } else if (Modifier.isVolatile(modifiers)) { - builder.append("volatile "); - } else if (Modifier.isTransient(modifiers)) { - builder.append("transient "); - } else if (Modifier.isSynchronized(modifiers)) { - builder.append("synchronized "); - } - if (Modifier.isNative(modifiers)) { - builder.append("native "); - } - if (Modifier.isStatic(modifiers)) { - builder.append("static"); - } else { - builder.append("instance"); - } - - return builder.toString(); + public void addField(FieldEntry field) { + fields.add(field); } - @Override - public void addDebugInfo(@SuppressWarnings("unused") DebugInfoBase debugInfoBase, DebugInfoProvider.DebugTypeInfo debugTypeInfo, @SuppressWarnings("unused") DebugContext debugContext) { - super.addDebugInfo(debugInfoBase, debugTypeInfo, debugContext); - // header type does not have a separate layout type - if (this instanceof HeaderTypeEntry) { - this.layoutTypeSignature = typeSignature; - } else { - this.layoutTypeSignature = debugTypeInfo.typeSignature(DwarfDebugInfo.LAYOUT_PREFIX); - } + public List getFields() { + return List.copyOf(fields); } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/TypeEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/TypeEntry.java index 4bb5584a768b..1f7faea4f9c7 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/TypeEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/TypeEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,25 +26,11 @@ package com.oracle.objectfile.debugentry; -import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind.ARRAY; -import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind.ENUM; -import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind.FOREIGN; -import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind.HEADER; -import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind.INSTANCE; -import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind.INTERFACE; -import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind.PRIMITIVE; - -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugTypeInfo.DebugTypeKind; -import com.oracle.objectfile.elf.dwarf.DwarfDebugInfo; - -import jdk.graal.compiler.debug.DebugContext; - -public abstract class TypeEntry { +public abstract sealed class TypeEntry permits StructureTypeEntry, PrimitiveTypeEntry, PointerToTypeEntry { /** * The name of this type. */ - protected final String typeName; + private final String typeName; /** * The type signature of this type. This is a pointer to the underlying layout of the type. @@ -66,14 +52,15 @@ public abstract class TypeEntry { /** * The size of an occurrence of this type in bytes. */ - protected final int size; + private final int size; - protected TypeEntry(String typeName, int size) { + protected TypeEntry(String typeName, int size, long classOffset, long typeSignature, + long typeSignatureForCompressed) { this.typeName = typeName; this.size = size; - this.classOffset = -1; - this.typeSignature = 0; - this.typeSignatureForCompressed = 0; + this.classOffset = classOffset; + this.typeSignature = typeSignature; + this.typeSignatureForCompressed = typeSignatureForCompressed; } public long getTypeSignature() { @@ -88,6 +75,10 @@ public long getClassOffset() { return classOffset; } + public void setClassOffset(long classOffset) { + this.classOffset = classOffset; + } + public int getSize() { return size; } @@ -96,66 +87,18 @@ public String getTypeName() { return typeName; } - public abstract DebugTypeKind typeKind(); - - public boolean isPrimitive() { - return typeKind() == PRIMITIVE; - } - - public boolean isHeader() { - return typeKind() == HEADER; - } - - public boolean isArray() { - return typeKind() == ARRAY; - } - - public boolean isInstance() { - return typeKind() == INSTANCE; - } - - public boolean isInterface() { - return typeKind() == INTERFACE; - } - - 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() || isForeign(); - } - - public boolean isStructure() { - return isClass() || isHeader(); - } - - public void addDebugInfo(@SuppressWarnings("unused") DebugInfoBase debugInfoBase, DebugTypeInfo debugTypeInfo, @SuppressWarnings("unused") DebugContext debugContext) { - /* Record the location of the Class instance in the heap if there is one */ - this.classOffset = debugTypeInfo.classOffset(); - this.typeSignature = debugTypeInfo.typeSignature(""); - // primitives, header and foreign types are never stored compressed - if (!debugInfoBase.useHeapBase() || this instanceof PrimitiveTypeEntry || this instanceof HeaderTypeEntry || this instanceof ForeignTypeEntry) { - this.typeSignatureForCompressed = typeSignature; - } else { - this.typeSignatureForCompressed = debugTypeInfo.typeSignature(DwarfDebugInfo.COMPRESSED_PREFIX); - } + @Override + public String toString() { + String kind = switch (this) { + case PrimitiveTypeEntry p -> "Primitive"; + case HeaderTypeEntry h -> "Header"; + case ArrayTypeEntry a -> "Array"; + case InterfaceClassEntry i -> "Interface"; + case EnumClassEntry e -> "Enum"; + case ForeignStructTypeEntry fs -> "ForeignStruct"; + case PointerToTypeEntry fs -> "PointerTo"; + case ClassEntry c -> "Instance"; + }; + return String.format("%sType(%s size=%d @%s)", kind, getTypeName(), getSize(), Long.toHexString(classOffset)); } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/CallRange.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/CallRange.java index 39e639306c41..2f2d50c7e0d3 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/CallRange.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/CallRange.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2022, 2022, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,48 +26,55 @@ package com.oracle.objectfile.debugentry.range; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.stream.Stream; + +import com.oracle.objectfile.debugentry.LocalEntry; +import com.oracle.objectfile.debugentry.LocalValueEntry; import com.oracle.objectfile.debugentry.MethodEntry; -class CallRange extends SubRange { - /** - * The first direct callee whose range is wholly contained in this range or null if this is a - * leaf range. - */ - protected SubRange firstCallee; +public class CallRange extends Range { + /** - * The last direct callee whose range is wholly contained in this range. + * The direct callees whose ranges are wholly contained in this range. Empty if this is a leaf + * range. */ - protected SubRange lastCallee; + private Set callees = Set.of(); - protected CallRange(MethodEntry methodEntry, int lo, int hi, int line, PrimaryRange primary, Range caller) { - super(methodEntry, lo, hi, line, primary, caller); - this.firstCallee = null; - this.lastCallee = null; + protected CallRange(PrimaryRange primary, MethodEntry methodEntry, Map localInfoList, int lo, int hi, int line, CallRange caller, int depth) { + super(primary, methodEntry, localInfoList, lo, hi, line, caller, depth); } @Override - protected void addCallee(SubRange callee) { - assert this.lo <= callee.lo; - assert this.hi >= callee.hi; - assert callee.caller == this; - assert callee.siblingCallee == null; - if (this.firstCallee == null) { - assert this.lastCallee == null; - this.firstCallee = this.lastCallee = callee; - } else { - this.lastCallee.siblingCallee = callee; - this.lastCallee = callee; - } + public List getCallees() { + return List.copyOf(callees); } @Override - public SubRange getFirstCallee() { - return firstCallee; + public Stream rangeStream() { + return Stream.concat(super.rangeStream(), callees.stream().flatMap(Range::rangeStream)); + } + + protected void addCallee(Range callee) { + assert this.contains(callee); + assert callee.getCaller() == this; + + if (callees.isEmpty()) { + callees = new TreeSet<>(Comparator.comparing(Range::getLoOffset)); + } + callees.add(callee); } @Override public boolean isLeaf() { - assert firstCallee != null; - return false; + return callees.isEmpty(); + } + + public void removeCallee(Range callee) { + callees.remove(callee); } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/LeafRange.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/LeafRange.java index 19d9a706cc16..173a3f0cbac2 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/LeafRange.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/LeafRange.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2022, 2022, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,21 +26,15 @@ package com.oracle.objectfile.debugentry.range; -import com.oracle.objectfile.debugentry.MethodEntry; - -class LeafRange extends SubRange { - protected LeafRange(MethodEntry methodEntry, int lo, int hi, int line, PrimaryRange primary, Range caller) { - super(methodEntry, lo, hi, line, primary, caller); - } +import java.util.Map; - @Override - protected void addCallee(SubRange callee) { - assert false : "should never be adding callees to a leaf range!"; - } +import com.oracle.objectfile.debugentry.LocalEntry; +import com.oracle.objectfile.debugentry.LocalValueEntry; +import com.oracle.objectfile.debugentry.MethodEntry; - @Override - public SubRange getFirstCallee() { - return null; +public class LeafRange extends Range { + protected LeafRange(PrimaryRange primary, MethodEntry methodEntry, Map localInfoList, int lo, int hi, int line, CallRange caller, int depth) { + super(primary, methodEntry, localInfoList, lo, hi, line, caller, depth); } @Override diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/PrimaryRange.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/PrimaryRange.java index 4e4c7fc6ae91..b675ca6160e1 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/PrimaryRange.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/PrimaryRange.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2022, 2022, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,52 +26,30 @@ package com.oracle.objectfile.debugentry.range; -import com.oracle.objectfile.debugentry.MethodEntry; +import java.util.HashMap; -public class PrimaryRange extends Range { - /** - * The first subrange in the range covered by this primary or null if this primary as no - * subranges. - */ - protected SubRange firstCallee; - /** - * The last subrange in the range covered by this primary. - */ - protected SubRange lastCallee; +import com.oracle.objectfile.debugentry.MethodEntry; - protected PrimaryRange(MethodEntry methodEntry, int lo, int hi, int line) { - super(methodEntry, lo, hi, line, -1); - this.firstCallee = null; - this.lastCallee = null; - } +public class PrimaryRange extends CallRange { + private final long codeOffset; - @Override - public boolean isPrimary() { - return true; + protected PrimaryRange(MethodEntry methodEntry, int lo, int hi, int line, long codeOffset) { + super(null, methodEntry, new HashMap<>(), lo, hi, line, null, -1); + this.codeOffset = codeOffset; } @Override - protected void addCallee(SubRange callee) { - assert this.lo <= callee.lo; - assert this.hi >= callee.hi; - assert callee.caller == this; - assert callee.siblingCallee == null; - if (this.firstCallee == null) { - assert this.lastCallee == null; - this.firstCallee = this.lastCallee = callee; - } else { - this.lastCallee.siblingCallee = callee; - this.lastCallee = callee; - } + public long getCodeOffset() { + return codeOffset; } @Override - public SubRange getFirstCallee() { - return firstCallee; + public boolean isPrimary() { + return true; } @Override - public boolean isLeaf() { - return firstCallee == null; + public PrimaryRange getPrimary() { + return this; } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java index e0a0360a6177..66c598e17d76 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/Range.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,15 +26,24 @@ package com.oracle.objectfile.debugentry.range; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import com.oracle.objectfile.debugentry.ConstantValueEntry; import com.oracle.objectfile.debugentry.FileEntry; +import com.oracle.objectfile.debugentry.LocalEntry; +import com.oracle.objectfile.debugentry.LocalValueEntry; import com.oracle.objectfile.debugentry.MethodEntry; +import com.oracle.objectfile.debugentry.StackValueEntry; import com.oracle.objectfile.debugentry.TypeEntry; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalValueInfo; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.PrimitiveConstant; /** * Details of a specific address range in a compiled method either a primary range identifying a @@ -69,13 +78,29 @@ * parameter values for separate sub-extents of an inline called method whose full extent is * represented by the parent call range at level N. */ -public abstract class Range { - private static final String CLASS_DELIMITER = "."; - protected final MethodEntry methodEntry; - protected final int lo; - protected int hi; - protected final int line; - protected final int depth; +public abstract class Range implements Comparable { + private final MethodEntry methodEntry; + private final int loOffset; + private int hiOffset; + private final int line; + private final int depth; + + /** + * The range for the caller or the primary range when this range is for top level method code. + */ + private final CallRange caller; + + private final PrimaryRange primary; + + /** + * Values for the associated method's local and parameter variables that are available or, + * alternatively, marked as invalid in this range. + */ + private final Map localValueInfos; + /** + * Mapping of local entries to subranges they occur in. + */ + private final Map> varRangeMap = new HashMap<>(); /** * Create a primary range representing the root of the subrange tree for a top level compiled @@ -87,8 +112,8 @@ public abstract class Range { * @param line the line number associated with the range * @return a new primary range to serve as the root of the subrange tree. */ - public static PrimaryRange createPrimary(MethodEntry methodEntry, int lo, int hi, int line) { - return new PrimaryRange(methodEntry, lo, hi, line); + public static PrimaryRange createPrimary(MethodEntry methodEntry, int lo, int hi, int line, long codeOffset) { + return new PrimaryRange(methodEntry, lo, hi, line, codeOffset); } /** @@ -99,57 +124,119 @@ public static PrimaryRange createPrimary(MethodEntry methodEntry, int lo, int hi * @param lo the lowest address included in the range. * @param hi the first address above the highest address in the range. * @param line the line number associated with the range - * @param primary the primary range to which this subrange belongs * @param caller the range for which this is a subrange, either an inlined call range or the * primary range. * @param isLeaf true if this is a leaf range with no subranges * @return a new subrange to be linked into the range tree below the primary range. */ - public static SubRange createSubrange(MethodEntry methodEntry, int lo, int hi, int line, PrimaryRange primary, Range caller, boolean isLeaf) { - assert primary != null; - assert primary.isPrimary(); + public static Range createSubrange(PrimaryRange primary, MethodEntry methodEntry, Map localValueInfos, int lo, int hi, int line, CallRange caller, boolean isLeaf) { + assert caller != null; + if (caller != primary) { + methodEntry.setInlined(); + } + Range callee; if (isLeaf) { - return new LeafRange(methodEntry, lo, hi, line, primary, caller); + callee = new LeafRange(primary, methodEntry, localValueInfos, lo, hi, line, caller, caller.getDepth() + 1); } else { - return new CallRange(methodEntry, lo, hi, line, primary, caller); + callee = new CallRange(primary, methodEntry, localValueInfos, lo, hi, line, caller, caller.getDepth() + 1); } + + caller.addCallee(callee); + return callee; } - protected Range(MethodEntry methodEntry, int lo, int hi, int line, int depth) { + protected Range(PrimaryRange primary, MethodEntry methodEntry, Map localValueInfos, int loOffset, int hiOffset, int line, CallRange caller, int depth) { assert methodEntry != null; + this.primary = primary; this.methodEntry = methodEntry; - this.lo = lo; - this.hi = hi; + this.loOffset = loOffset; + this.hiOffset = hiOffset; this.line = line; + this.caller = caller; this.depth = depth; + this.localValueInfos = localValueInfos; } - protected abstract void addCallee(SubRange callee); + /** + * Splits an initial range at the given stack decrement point. The lower split will stay as is + * with its high offset reduced to the stack decrement point. The higher split starts at the + * stack decrement point and has updated local value entries for the parameters in the then + * extended stack. + * + * @param stackDecrement the offset to split the range at + * @param frameSize the frame size after the split + * @param preExtendFrameSize the frame size before the split + * @return the higher split, that has been split off the original {@code Range} + */ + public Range split(int stackDecrement, int frameSize, int preExtendFrameSize) { + // This should be for an initial range extending beyond the stack decrement. + assert loOffset == 0 && loOffset < stackDecrement && stackDecrement < hiOffset : "invalid split request"; + + Map splitLocalValueInfos = new HashMap<>(); + + for (var localInfo : localValueInfos.entrySet()) { + if (localInfo.getValue() instanceof StackValueEntry stackValue) { + /* + * Need to redefine the value for this param using a stack slot value that allows + * for the stack being extended by framesize. however we also need to remove any + * adjustment that was made to allow for the difference between the caller SP and + * the pre-extend callee SP because of a stacked return address. + */ + int adjustment = frameSize - preExtendFrameSize; + splitLocalValueInfos.put(localInfo.getKey(), new StackValueEntry(stackValue.stackSlot() + adjustment)); + } else { + splitLocalValueInfos.put(localInfo.getKey(), localInfo.getValue()); + } + } + + Range splitRange = Range.createSubrange(primary, methodEntry, splitLocalValueInfos, stackDecrement, hiOffset, line, caller, isLeaf()); + hiOffset = stackDecrement; + + return splitRange; + } public boolean contains(Range other) { - return (lo <= other.lo && hi >= other.hi); + return (loOffset <= other.loOffset && hiOffset >= other.hiOffset); } - public abstract boolean isPrimary(); + public boolean isPrimary() { + return false; + } - public String getClassName() { - return methodEntry.ownerType().getTypeName(); + public String getTypeName() { + return methodEntry.getOwnerType().getTypeName(); } public String getMethodName() { - return methodEntry.methodName(); + return methodEntry.getMethodName(); } public String getSymbolName() { return methodEntry.getSymbolName(); } - public int getHi() { - return hi; + public int getHiOffset() { + return hiOffset; + } + + public int getLoOffset() { + return loOffset; + } + + public long getCodeOffset() { + return primary.getCodeOffset(); + } + + public long getLo() { + return getCodeOffset() + loOffset; } - public int getLo() { - return lo; + public long getHi() { + return getCodeOffset() + hiOffset; + } + + public PrimaryRange getPrimary() { + return primary; } public int getLine() { @@ -157,31 +244,27 @@ public int getLine() { } public String getFullMethodName() { - return constructClassAndMethodName(); + return getExtendedMethodName(false); } public String getFullMethodNameWithParams() { - return constructClassAndMethodNameWithParams(); + return getExtendedMethodName(true); } public boolean isDeoptTarget() { return methodEntry.isDeopt(); } - private String getExtendedMethodName(boolean includeClass, boolean includeParams, boolean includeReturnType) { + private String getExtendedMethodName(boolean includeParams) { StringBuilder builder = new StringBuilder(); - if (includeReturnType && methodEntry.getValueType().getTypeName().length() > 0) { - builder.append(methodEntry.getValueType().getTypeName()); - builder.append(' '); - } - if (includeClass && getClassName() != null) { - builder.append(getClassName()); - builder.append(CLASS_DELIMITER); + if (getTypeName() != null) { + builder.append(getTypeName()); + builder.append("."); } builder.append(getMethodName()); if (includeParams) { builder.append("("); - TypeEntry[] paramTypes = methodEntry.getParamTypes(); + List paramTypes = methodEntry.getParamTypes(); if (paramTypes != null) { String prefix = ""; for (TypeEntry t : paramTypes) { @@ -192,21 +275,9 @@ private String getExtendedMethodName(boolean includeClass, boolean includeParams } builder.append(')'); } - if (includeReturnType) { - builder.append(" "); - builder.append(methodEntry.getValueType().getTypeName()); - } return builder.toString(); } - private String constructClassAndMethodName() { - return getExtendedMethodName(true, false, false); - } - - private String constructClassAndMethodNameWithParams() { - return getExtendedMethodName(true, true, false); - } - public FileEntry getFileEntry() { return methodEntry.getFileEntry(); } @@ -217,7 +288,15 @@ public int getModifiers() { @Override public String toString() { - return String.format("Range(lo=0x%05x hi=0x%05x %s %s:%d)", lo, hi, constructClassAndMethodNameWithParams(), methodEntry.getFullFileName(), line); + return String.format("Range(lo=0x%05x hi=0x%05x %s %s:%d)", getLo(), getHi(), getFullMethodNameWithParams(), methodEntry.getFullFileName(), line); + } + + @Override + public int compareTo(Range other) { + return Comparator.comparingLong(Range::getLo) + .thenComparingLong(Range::getHi) + .thenComparingInt(Range::getLine) + .compare(this, other); } public String getFileName() { @@ -228,74 +307,100 @@ public MethodEntry getMethodEntry() { return methodEntry; } - public abstract SubRange getFirstCallee(); - public abstract boolean isLeaf(); public boolean includesInlineRanges() { - SubRange child = getFirstCallee(); - while (child != null && child.isLeaf()) { - child = child.getSiblingCallee(); + for (Range callee : getCallees()) { + if (!callee.isLeaf()) { + return true; + } } - return child != null; + return false; + } + + public List getCallees() { + return List.of(); + } + + public Stream rangeStream() { + return Stream.of(this); } public int getDepth() { return depth; } - public HashMap> getVarRangeMap() { - MethodEntry calleeMethod; - if (isPrimary()) { - calleeMethod = getMethodEntry(); - } else { - assert !isLeaf() : "should only be looking up var ranges for inlined calls"; - calleeMethod = getFirstCallee().getMethodEntry(); - } - HashMap> varRangeMap = new HashMap<>(); - if (calleeMethod.getThisParam() != null) { - varRangeMap.put(calleeMethod.getThisParam(), new ArrayList<>()); - } - for (int i = 0; i < calleeMethod.getParamCount(); i++) { - varRangeMap.put(calleeMethod.getParam(i), new ArrayList<>()); - } - for (int i = 0; i < calleeMethod.getLocalCount(); i++) { - varRangeMap.put(calleeMethod.getLocal(i), new ArrayList<>()); + /** + * Returns the subranges grouped by local entries in the subranges. If the map is empty, it + * first tries to populate the map with its callees {@link #localValueInfos}. + * + * @return a mapping from local entries to subranges + */ + public Map> getVarRangeMap() { + if (varRangeMap.isEmpty()) { + for (Range callee : getCallees()) { + for (LocalEntry local : callee.localValueInfos.keySet()) { + varRangeMap.computeIfAbsent(local, l -> new ArrayList<>()).add(callee); + } + } } - return updateVarRangeMap(varRangeMap); - } - public HashMap> updateVarRangeMap(HashMap> varRangeMap) { - // leaf subranges of the current range may provide values for param or local vars - // of this range's method. find them and index the range so that we can identify - // both the local/param and the associated range. - SubRange subRange = this.getFirstCallee(); - while (subRange != null) { - addVarRanges(subRange, varRangeMap); - subRange = subRange.siblingCallee; - } return varRangeMap; } - public void addVarRanges(SubRange subRange, HashMap> varRangeMap) { - int localValueCount = subRange.getLocalValueCount(); - for (int i = 0; i < localValueCount; i++) { - DebugLocalValueInfo localValueInfo = subRange.getLocalValue(i); - DebugLocalInfo local = subRange.getLocal(i); - if (local != null) { - switch (localValueInfo.localKind()) { - case REGISTER: - case STACKSLOT: - case CONSTANT: - List varRanges = varRangeMap.get(local); - assert varRanges != null : "local not present in var to ranges map!"; - varRanges.add(subRange); - break; - case UNDEFINED: - break; + /** + * Returns whether subranges contain a value in {@link #localValueInfos} for a given local + * entry. A value is valid if it exists, and it can be represented as local values in the debug + * info. + * + * @param local the local entry to check for + * @return whether a local entry has a value in any of this range's subranges + */ + public boolean hasLocalValues(LocalEntry local) { + for (Range callee : getVarRangeMap().getOrDefault(local, List.of())) { + LocalValueEntry localValue = callee.lookupValue(local); + if (localValue != null) { + if (localValue instanceof ConstantValueEntry constantValueEntry) { + JavaConstant constant = constantValueEntry.constant(); + // can only handle primitive or null constants just now + return constant instanceof PrimitiveConstant || constant.getJavaKind() == JavaKind.Object; + } else { + // register or stack value + return true; } } } + return false; } + public Range getCaller() { + return caller; + } + + public LocalValueEntry lookupValue(LocalEntry local) { + return localValueInfos.getOrDefault(local, null); + } + + public boolean tryMerge(Range that) { + assert this.caller == that.caller; + assert this.isLeaf() == that.isLeaf(); + assert this.depth == that.depth : "should only compare sibling ranges"; + assert this.hiOffset <= that.loOffset : "later nodes should not overlap earlier ones"; + if (this.hiOffset != that.loOffset) { + return false; + } + if (this.methodEntry != that.methodEntry) { + return false; + } + if (this.line != that.line) { + return false; + } + if (!this.localValueInfos.equals(that.localValueInfos)) { + return false; + } + // merging just requires updating lo and hi range as everything else is equal + this.hiOffset = that.hiOffset; + caller.removeCallee(that); + return true; + } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/SubRange.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/SubRange.java deleted file mode 100644 index 6c8f9a1fc669..000000000000 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/range/SubRange.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2022, 2022, Red Hat Inc. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -package com.oracle.objectfile.debugentry.range; - -import com.oracle.objectfile.debugentry.MethodEntry; -import com.oracle.objectfile.debuginfo.DebugInfoProvider; - -public abstract class SubRange extends Range { - private static final DebugInfoProvider.DebugLocalInfo[] EMPTY_LOCAL_INFOS = new DebugInfoProvider.DebugLocalInfo[0]; - /** - * The root of the call tree the subrange belongs to. - */ - private final PrimaryRange primary; - /** - * The range for the caller or the primary range when this range is for top level method code. - */ - protected Range caller; - /** - * A link to a sibling callee, i.e., a range sharing the same caller with this range. - */ - protected SubRange siblingCallee; - /** - * Values for the associated method's local and parameter variables that are available or, - * alternatively, marked as invalid in this range. - */ - private DebugInfoProvider.DebugLocalValueInfo[] localValueInfos; - /** - * The set of local or parameter variables with which each corresponding local value in field - * localValueInfos is associated. Local values which are associated with the same local or - * parameter variable will share the same reference in the corresponding array entries. Local - * values with which no local variable can be associated will have a null reference in the - * corresponding array. The latter case can happen when a local value has an invalid slot or - * when a local value that maps to a parameter slot has a different name or type to the - * parameter. - */ - private DebugInfoProvider.DebugLocalInfo[] localInfos; - - @SuppressWarnings("this-escape") - protected SubRange(MethodEntry methodEntry, int lo, int hi, int line, PrimaryRange primary, Range caller) { - super(methodEntry, lo, hi, line, (caller == null ? 0 : caller.depth + 1)); - this.caller = caller; - if (caller != null) { - caller.addCallee(this); - } - assert primary != null; - this.primary = primary; - } - - public Range getPrimary() { - return primary; - } - - @Override - public boolean isPrimary() { - return false; - } - - public Range getCaller() { - return caller; - } - - @Override - public abstract SubRange getFirstCallee(); - - @Override - public abstract boolean isLeaf(); - - public int getLocalValueCount() { - return localValueInfos.length; - } - - public DebugInfoProvider.DebugLocalValueInfo getLocalValue(int i) { - assert i >= 0 && i < localValueInfos.length : "bad index"; - return localValueInfos[i]; - } - - public DebugInfoProvider.DebugLocalInfo getLocal(int i) { - assert i >= 0 && i < localInfos.length : "bad index"; - return localInfos[i]; - } - - public void setLocalValueInfo(DebugInfoProvider.DebugLocalValueInfo[] localValueInfos) { - int len = localValueInfos.length; - this.localValueInfos = localValueInfos; - this.localInfos = (len > 0 ? new DebugInfoProvider.DebugLocalInfo[len] : EMPTY_LOCAL_INFOS); - // set up mapping from local values to local variables - for (int i = 0; i < len; i++) { - localInfos[i] = methodEntry.recordLocal(localValueInfos[i]); - } - } - - public DebugInfoProvider.DebugLocalValueInfo lookupValue(DebugInfoProvider.DebugLocalInfo local) { - int localValueCount = getLocalValueCount(); - for (int i = 0; i < localValueCount; i++) { - if (getLocal(i) == local) { - return getLocalValue(i); - } - } - return null; - } - - public SubRange getSiblingCallee() { - return siblingCallee; - } -} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java index 7a4b0e6d8025..c99e674e2c4d 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java @@ -26,24 +26,23 @@ package com.oracle.objectfile.debuginfo; -import java.nio.file.Path; -import java.util.List; -import java.util.function.Consumer; -import java.util.stream.Stream; +import java.util.SortedSet; -import jdk.graal.compiler.debug.DebugContext; -import jdk.vm.ci.meta.JavaConstant; -import jdk.vm.ci.meta.JavaKind; -import jdk.vm.ci.meta.ResolvedJavaMethod; -import jdk.vm.ci.meta.ResolvedJavaType; +import com.oracle.objectfile.debugentry.CompiledMethodEntry; +import com.oracle.objectfile.debugentry.TypeEntry; /** * Interfaces used to allow a native image to communicate details of types, code and data to the * underlying object file so that the latter can insert appropriate debug info. */ public interface DebugInfoProvider { + + void installDebugInfo(); + boolean useHeapBase(); + boolean isRuntimeCompilation(); + /** * Number of bits oops are left shifted by when using compressed oops. */ @@ -69,386 +68,14 @@ public interface DebugInfoProvider { */ int objectAlignment(); - int compiledCodeMax(); - - /** - * An interface implemented by items that can be located in a file. - */ - interface DebugFileInfo { - /** - * @return the name of the file containing a file element excluding any path. - */ - String fileName(); - - /** - * @return a relative path to the file containing a file element derived from its package - * name or {@code null} if the element is in the empty package. - */ - Path filePath(); - } - - interface DebugTypeInfo extends DebugFileInfo { - ResolvedJavaType idType(); - - enum DebugTypeKind { - PRIMITIVE, - ENUM, - INSTANCE, - INTERFACE, - ARRAY, - HEADER, - FOREIGN; - - @Override - public String toString() { - switch (this) { - case PRIMITIVE: - return "primitive"; - case ENUM: - return "enum"; - case INSTANCE: - return "instance"; - case INTERFACE: - return "interface"; - case ARRAY: - return "array"; - case HEADER: - return "header"; - case FOREIGN: - return "foreign"; - default: - return "???"; - } - } - } - - void debugContext(Consumer action); - - /** - * @return the fully qualified name of the debug type. - */ - String typeName(); - - /** - * @return a 64bit type signature to uniquely identify the type - */ - long typeSignature(String prefix); - - DebugTypeKind typeKind(); - - /** - * returns the offset in the heap at which the java.lang.Class instance which models this - * class is located or -1 if no such instance exists for this class. - * - * @return the offset of the java.lang.Class instance which models this class or -1. - */ - long classOffset(); + SortedSet typeEntries(); - int size(); - } - - interface DebugInstanceTypeInfo extends DebugTypeInfo { - String loaderName(); - - Stream fieldInfoProvider(); - - Stream methodInfoProvider(); + SortedSet compiledMethodEntries(); - ResolvedJavaType superClass(); - - Stream interfaces(); - } - - interface DebugEnumTypeInfo extends DebugInstanceTypeInfo { - } + String cachePath(); - interface DebugInterfaceTypeInfo extends DebugInstanceTypeInfo { + enum FrameSizeChangeType { + EXTEND, + CONTRACT; } - - 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(); - - int lengthOffset(); - - ResolvedJavaType elementType(); - - Stream fieldInfoProvider(); - } - - interface DebugPrimitiveTypeInfo extends DebugTypeInfo { - /* - * NUMERIC excludes LOGICAL types boolean and void - */ - int FLAG_NUMERIC = 1 << 0; - /* - * INTEGRAL excludes FLOATING types float and double - */ - int FLAG_INTEGRAL = 1 << 1; - /* - * SIGNED excludes UNSIGNED type char - */ - int FLAG_SIGNED = 1 << 2; - - int bitCount(); - - char typeChar(); - - int flags(); - } - - interface DebugHeaderTypeInfo extends DebugTypeInfo { - - Stream fieldInfoProvider(); - - DebugFieldInfo hubField(); - } - - interface DebugMemberInfo extends DebugFileInfo { - - String name(); - - ResolvedJavaType valueType(); - - int modifiers(); - } - - interface DebugFieldInfo extends DebugMemberInfo { - int offset(); - - int size(); - - boolean isEmbedded(); - } - - interface DebugMethodInfo extends DebugMemberInfo { - /** - * @return the line number for the outer or inlined segment. - */ - int line(); - - /** - * @return an array of DebugLocalInfo objects holding details of this method's parameters - */ - DebugLocalInfo[] getParamInfo(); - - /** - * @return a DebugLocalInfo objects holding details of the target instance parameter this if - * the method is an instance method or null if it is a static method. - */ - DebugLocalInfo getThisParamInfo(); - - /** - * @return the symbolNameForMethod string - */ - String symbolNameForMethod(); - - /** - * @return true if this method has been compiled in as a deoptimization target - */ - boolean isDeoptTarget(); - - /** - * @return true if this method is a constructor. - */ - boolean isConstructor(); - - /** - * @return true if this is a virtual method. In Graal a virtual method can become - * non-virtual if all other implementations are non-reachable. - */ - boolean isVirtual(); - - /** - * @return the offset into the virtual function table for this method if virtual - */ - int vtableOffset(); - - /** - * @return true if this method is an override of another method. - */ - boolean isOverride(); - - /* - * Return the unique type that owns this method.

- * - * @return the unique type that owns this method - */ - ResolvedJavaType ownerType(); - - /* - * Return the unique identifier for this method. The result can be used to unify details of - * methods presented via interface DebugTypeInfo with related details of compiled methods - * presented via interface DebugRangeInfo and of call frame methods presented via interface - * DebugLocationInfo.

- * - * @return the unique identifier for this method - */ - ResolvedJavaMethod idMethod(); - } - - /** - * Access details of a compiled top level or inline method producing the code in a specific - * {@link com.oracle.objectfile.debugentry.range.Range}. - */ - interface DebugRangeInfo extends DebugMethodInfo { - - /** - * @return the lowest address containing code generated for an outer or inlined code segment - * reported at this line represented as an offset into the code segment. - */ - int addressLo(); - - /** - * @return the first address above the code generated for an outer or inlined code segment - * reported at this line represented as an offset into the code segment. - */ - int addressHi(); - } - - /** - * Access details of a specific compiled method. - */ - interface DebugCodeInfo extends DebugRangeInfo { - void debugContext(Consumer action); - - /** - * @return a stream of records detailing source local var and line locations within the - * compiled method. - */ - Stream locationInfoProvider(); - - /** - * @return the size of the method frame between prologue and epilogue. - */ - int getFrameSize(); - - /** - * @return a list of positions at which the stack is extended to a full frame or torn down - * to an empty frame - */ - List getFrameSizeChanges(); - } - - /** - * Access details of a specific heap object. - */ - interface DebugDataInfo { - void debugContext(Consumer action); - - String getProvenance(); - - String getTypeName(); - - String getPartition(); - - long getOffset(); - - long getSize(); - } - - /** - * Access details of code generated for a specific outer or inlined method at a given line - * number. - */ - interface DebugLocationInfo extends DebugRangeInfo { - /** - * @return the {@link DebugLocationInfo} of the nested inline caller-line - */ - DebugLocationInfo getCaller(); - - /** - * @return a stream of {@link DebugLocalValueInfo} objects identifying local or parameter - * variables present in the frame of the current range. - */ - DebugLocalValueInfo[] getLocalValueInfo(); - - boolean isLeaf(); - } - - /** - * A DebugLocalInfo details a local or parameter variable recording its name and type, the - * (abstract machine) local slot index it resides in and the number of slots it occupies. - */ - interface DebugLocalInfo { - ResolvedJavaType valueType(); - - String name(); - - String typeName(); - - int slot(); - - int slotCount(); - - JavaKind javaKind(); - - int line(); - } - - /** - * A DebugLocalValueInfo details the value a local or parameter variable present in a specific - * frame. The value may be undefined. If not then the instance records its type and either its - * (constant) value or the register or stack location in which the value resides. - */ - interface DebugLocalValueInfo extends DebugLocalInfo { - enum LocalKind { - UNDEFINED, - REGISTER, - STACKSLOT, - CONSTANT - } - - LocalKind localKind(); - - int regIndex(); - - int stackSlot(); - - long heapOffset(); - - JavaConstant constantValue(); - } - - interface DebugFrameSizeChange { - enum Type { - EXTEND, - CONTRACT - } - - int getOffset(); - - DebugFrameSizeChange.Type getType(); - } - - @SuppressWarnings("unused") - Stream typeInfoProvider(); - - Stream codeInfoProvider(); - - @SuppressWarnings("unused") - Stream dataInfoProvider(); - - Path getCachePath(); - - void recordActivity(); } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/ELFObjectFile.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/ELFObjectFile.java index e0af88f3fd07..60ab09fd39a5 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/ELFObjectFile.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/ELFObjectFile.java @@ -35,7 +35,6 @@ import java.util.Map; import java.util.Set; -import com.oracle.objectfile.elf.dwarf.DwarfLocSectionImpl; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; @@ -53,6 +52,8 @@ import com.oracle.objectfile.elf.dwarf.DwarfFrameSectionImpl; import com.oracle.objectfile.elf.dwarf.DwarfInfoSectionImpl; import com.oracle.objectfile.elf.dwarf.DwarfLineSectionImpl; +import com.oracle.objectfile.elf.dwarf.DwarfLineStrSectionImpl; +import com.oracle.objectfile.elf.dwarf.DwarfLocSectionImpl; import com.oracle.objectfile.elf.dwarf.DwarfRangesSectionImpl; import com.oracle.objectfile.elf.dwarf.DwarfStrSectionImpl; import com.oracle.objectfile.io.AssemblyBuffer; @@ -1174,8 +1175,10 @@ protected int getMinimumFileSize() { @Override public void installDebugInfo(DebugInfoProvider debugInfoProvider) { DwarfDebugInfo dwarfSections = new DwarfDebugInfo(getMachine(), getByteOrder()); + /* We need an implementation for each generated DWARF section. */ DwarfStrSectionImpl elfStrSectionImpl = dwarfSections.getStrSectionImpl(); + DwarfLineStrSectionImpl elfLineStrSectionImpl = dwarfSections.getLineStrSectionImpl(); DwarfAbbrevSectionImpl elfAbbrevSectionImpl = dwarfSections.getAbbrevSectionImpl(); DwarfFrameSectionImpl frameSectionImpl = dwarfSections.getFrameSectionImpl(); DwarfLocSectionImpl elfLocSectionImpl = dwarfSections.getLocSectionImpl(); @@ -1185,6 +1188,7 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) { DwarfLineSectionImpl elfLineSectionImpl = dwarfSections.getLineSectionImpl(); /* Now we can create the section elements with empty content. */ newDebugSection(elfStrSectionImpl.getSectionName(), elfStrSectionImpl); + newDebugSection(elfLineStrSectionImpl.getSectionName(), elfLineStrSectionImpl); newDebugSection(elfAbbrevSectionImpl.getSectionName(), elfAbbrevSectionImpl); newDebugSection(frameSectionImpl.getSectionName(), frameSectionImpl); newDebugSection(elfLocSectionImpl.getSectionName(), elfLocSectionImpl); @@ -1202,6 +1206,7 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) { createDefinedSymbol(elfInfoSectionImpl.getSectionName(), elfInfoSectionImpl.getElement(), 0, 0, false, false); createDefinedSymbol(elfLineSectionImpl.getSectionName(), elfLineSectionImpl.getElement(), 0, 0, false, false); createDefinedSymbol(elfStrSectionImpl.getSectionName(), elfStrSectionImpl.getElement(), 0, 0, false, false); + createDefinedSymbol(elfLineStrSectionImpl.getSectionName(), elfLineStrSectionImpl.getElement(), 0, 0, false, false); createDefinedSymbol(elfRangesSectionImpl.getSectionName(), elfRangesSectionImpl.getElement(), 0, 0, false, false); createDefinedSymbol(elfLocSectionImpl.getSectionName(), elfLocSectionImpl.getElement(), 0, 0, false, false); /* @@ -1214,6 +1219,7 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) { * relevant reloc sections here in advance. */ elfStrSectionImpl.getOrCreateRelocationElement(0); + elfLineStrSectionImpl.getOrCreateRelocationElement(0); elfAbbrevSectionImpl.getOrCreateRelocationElement(0); frameSectionImpl.getOrCreateRelocationElement(0); elfInfoSectionImpl.getOrCreateRelocationElement(0); diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfARangesSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfARangesSectionImpl.java index 91ba489360c5..ec3a149e0a20 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfARangesSectionImpl.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfARangesSectionImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,18 +26,12 @@ package com.oracle.objectfile.elf.dwarf; -import java.util.Map; - -import com.oracle.objectfile.debugentry.ClassEntry; +import com.oracle.objectfile.debugentry.CompiledMethodEntry; +import com.oracle.objectfile.debugentry.range.Range; import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName; import com.oracle.objectfile.elf.dwarf.constants.DwarfVersion; -import jdk.graal.compiler.debug.DebugContext; -import com.oracle.objectfile.LayoutDecision; -import com.oracle.objectfile.LayoutDecisionMap; -import com.oracle.objectfile.ObjectFile; -import com.oracle.objectfile.debugentry.CompiledMethodEntry; -import com.oracle.objectfile.debugentry.range.Range; +import jdk.graal.compiler.debug.DebugContext; /** * Section generator for debug_aranges section. @@ -90,11 +84,10 @@ public void createContent() { * Where N is the number of compiled methods. */ assert !contentByteArrayCreated(); - Cursor byteCount = new Cursor(); - instanceClassStream().filter(ClassEntry::hasCompiledEntries).forEachOrdered(classEntry -> { - byteCount.add(entrySize(classEntry.compiledEntryCount())); - }); - byte[] buffer = new byte[byteCount.get()]; + int byteCount = instanceClassWithCompilationStream() + .mapToInt(classEntry -> entrySize(classEntry.compiledMethods().size())) + .sum(); + byte[] buffer = new byte[byteCount]; super.setContent(buffer); } @@ -111,23 +104,6 @@ private static int entrySize(int methodCount) { return size; } - @Override - public byte[] getOrDecideContent(Map alreadyDecided, byte[] contentHint) { - ObjectFile.Element textElement = getElement().getOwner().elementForName(".text"); - LayoutDecisionMap decisionMap = alreadyDecided.get(textElement); - if (decisionMap != null) { - Object valueObj = decisionMap.getDecidedValue(LayoutDecision.Kind.VADDR); - if (valueObj != null && valueObj instanceof Number) { - /* - * This may not be the final vaddr for the text segment but it will be close enough - * to make debug easier i.e. to within a 4k page or two. - */ - debugTextBase = ((Number) valueObj).longValue(); - } - } - return super.getOrDecideContent(alreadyDecided, contentHint); - } - @Override public void writeContent(DebugContext context) { assert contentByteArrayCreated(); @@ -135,14 +111,14 @@ public void writeContent(DebugContext context) { int size = buffer.length; Cursor cursor = new Cursor(); - enableLog(context, cursor.get()); + enableLog(context); log(context, " [0x%08x] DEBUG_ARANGES", cursor.get()); - instanceClassStream().filter(ClassEntry::hasCompiledEntries).forEachOrdered(classEntry -> { + instanceClassWithCompilationStream().forEachOrdered(classEntry -> { int lengthPos = cursor.get(); log(context, " [0x%08x] class %s CU 0x%x", lengthPos, classEntry.getTypeName(), getCUIndex(classEntry)); cursor.set(writeHeader(getCUIndex(classEntry), buffer, cursor.get())); - classEntry.compiledEntries().forEachOrdered(compiledMethodEntry -> { + classEntry.compiledMethods().forEach(compiledMethodEntry -> { cursor.set(writeARange(context, compiledMethodEntry, buffer, cursor.get())); }); // write two terminating zeroes @@ -176,9 +152,9 @@ private int writeHeader(int cuIndex, byte[] buffer, int p) { int writeARange(DebugContext context, CompiledMethodEntry compiledMethod, byte[] buffer, int p) { int pos = p; - Range primary = compiledMethod.getPrimary(); - log(context, " [0x%08x] %016x %016x %s", pos, debugTextBase + primary.getLo(), primary.getHi() - primary.getLo(), primary.getFullMethodNameWithParams()); - pos = writeRelocatableCodeOffset(primary.getLo(), buffer, pos); + Range primary = compiledMethod.primary(); + log(context, " [0x%08x] %016x %016x %s", pos, primary.getLo(), primary.getHi() - primary.getLo(), primary.getFullMethodNameWithParams()); + pos = writeCodeOffset(primary.getLo(), buffer, pos); pos = writeLong(primary.getHi() - primary.getLo(), buffer, pos); return pos; } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java index 7d9c59ba0f63..19e35cb2243d 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -905,7 +905,7 @@ public void writeContent(DebugContext context) { int size = buffer.length; int pos = 0; - enableLog(context, pos); + enableLog(context); pos = writeAbbrevs(context, buffer, pos); @@ -914,47 +914,37 @@ public void writeContent(DebugContext context) { public int writeAbbrevs(DebugContext context, byte[] buffer, int p) { int pos = p; + /* + * Write all abbrevs for AOT and run-time debug info. Abbrevs use the least space of all + * debug info sections, so there is no real benefit in reducing the amount of abbrevs for + * run-time debug info. + */ + + // Top level DIEs pos = writeCompileUnitAbbrevs(context, buffer, pos); pos = writeTypeUnitAbbrev(context, buffer, pos); + // Level 1 DIEs pos = writePrimitiveTypeAbbrev(context, buffer, pos); pos = writeVoidTypeAbbrev(context, buffer, pos); pos = writeObjectHeaderAbbrev(context, buffer, pos); pos = writeClassConstantAbbrev(context, buffer, pos); - pos = writeNamespaceAbbrev(context, buffer, pos); - pos = writeClassLayoutAbbrevs(context, buffer, pos); - pos = writeClassReferenceAbbrevs(context, buffer, pos); - pos = writeMethodDeclarationAbbrevs(context, buffer, pos); - pos = writeFieldDeclarationAbbrevs(context, buffer, pos); - - pos = writeArrayLayoutAbbrev(context, buffer, pos); - - pos = writeInterfaceLayoutAbbrev(context, buffer, pos); - + pos = writePointerTypeAbbrevs(context, buffer, pos); pos = writeForeignTypedefAbbrev(context, buffer, pos); pos = writeForeignStructAbbrev(context, buffer, pos); - - pos = writeFieldAbbrevs(context, buffer, pos); - pos = writeArrayDataTypeAbbrevs(context, buffer, pos); - pos = writeArraySubrangeTypeAbbrev(context, buffer, pos); pos = writeMethodLocationAbbrev(context, buffer, pos); - pos = writeAbstractInlineMethodAbbrev(context, buffer, pos); pos = writeStaticFieldLocationAbbrev(context, buffer, pos); - pos = writeSuperReferenceAbbrev(context, buffer, pos); - pos = writeInterfaceImplementorAbbrev(context, buffer, pos); - - pos = writeInlinedSubroutineAbbrev(buffer, pos, false); - pos = writeInlinedSubroutineAbbrev(buffer, pos, true); - + pos = writeArrayLayoutAbbrev(context, buffer, pos); + pos = writeInterfaceLayoutAbbrev(context, buffer, pos); /* - * if we address rebasing is required then we need to use compressed layout types supplied - * with a suitable data_location attribute and compressed pointer types to ensure that gdb + * If address rebasing is required then we need to use compressed layout types supplied with + * a suitable data_location attribute and compressed pointer types to ensure that gdb * converts offsets embedded in static or instance fields to raw pointers. Transformed * addresses are typed using pointers to the underlying layout. * - * if address rebasing is not required then a data_location attribute on the layout type + * If address rebasing is not required then a data_location attribute on the layout type * will ensure that address tag bits are removed. * * The compressed layout is also used for representing the decode step for dynamic hubs. @@ -962,9 +952,20 @@ public int writeAbbrevs(DebugContext context, byte[] buffer, int p) { */ pos = writeCompressedLayoutAbbrev(context, buffer, pos); + /* Level 2 DIEs */ + pos = writeMethodDeclarationAbbrevs(context, buffer, pos); + pos = writeFieldDeclarationAbbrevs(context, buffer, pos); + pos = writeFieldAbbrevs(context, buffer, pos); + pos = writeArrayDataTypeAbbrevs(context, buffer, pos); + pos = writeArraySubrangeTypeAbbrev(context, buffer, pos); + pos = writeSuperReferenceAbbrev(context, buffer, pos); + pos = writeInterfaceImplementorAbbrev(context, buffer, pos); + + /* Level 2+ DIEs (used within functions and inlined functions) */ + pos = writeInlinedSubroutineAbbrev(buffer, pos); + pos = writeAbstractInlineMethodAbbrev(context, buffer, pos); pos = writeParameterDeclarationAbbrevs(context, buffer, pos); pos = writeLocalDeclarationAbbrevs(context, buffer, pos); - pos = writeParameterLocationAbbrevs(context, buffer, pos); pos = writeLocalLocationAbbrevs(context, buffer, pos); @@ -1117,6 +1118,7 @@ private int writeClassLayoutAbbrevs(@SuppressWarnings("unused") DebugContext con pos = writeClassLayoutAbbrev(context, AbbrevCode.CLASS_LAYOUT_TU, buffer, pos); pos = writeClassLayoutAbbrev(context, AbbrevCode.CLASS_LAYOUT_CU, buffer, pos); pos = writeClassLayoutAbbrev(context, AbbrevCode.CLASS_LAYOUT_ARRAY, buffer, pos); + pos = writeOpaqueClassLayoutAbbrev(context, buffer, pos); return pos; } @@ -1155,14 +1157,33 @@ private int writeClassLayoutAbbrev(@SuppressWarnings("unused") DebugContext cont return pos; } - private int writeClassReferenceAbbrevs(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { + private int writeOpaqueClassLayoutAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { + int pos = p; + + pos = writeAbbrevCode(AbbrevCode.CLASS_LAYOUT_OPAQUE, buffer, pos); + pos = writeTag(DwarfTag.DW_TAG_structure_type, buffer, pos); + pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_no, buffer, pos); + pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos); + pos = writeAttrType(DwarfAttribute.DW_AT_declaration, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos); + /* + * Now terminate. + */ + pos = writeAttrType(DwarfAttribute.DW_AT_null, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_null, buffer, pos); + + return pos; + } + + private int writePointerTypeAbbrevs(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { int pos = p; - pos = writeClassReferenceAbbrev(context, AbbrevCode.TYPE_POINTER_SIG, buffer, pos); - pos = writeClassReferenceAbbrev(context, AbbrevCode.TYPE_POINTER, buffer, pos); + pos = writePointerTypeAbbrev(context, AbbrevCode.TYPE_POINTER_SIG, buffer, pos); + pos = writePointerTypeAbbrev(context, AbbrevCode.TYPE_POINTER, buffer, pos); return pos; } - private int writeClassReferenceAbbrev(@SuppressWarnings("unused") DebugContext context, AbbrevCode abbrevCode, byte[] buffer, int p) { + private int writePointerTypeAbbrev(@SuppressWarnings("unused") DebugContext context, AbbrevCode abbrevCode, byte[] buffer, int p) { int pos = p; /* A pointer to the class struct type. */ @@ -1185,7 +1206,9 @@ private int writeClassReferenceAbbrev(@SuppressWarnings("unused") DebugContext c private int writeMethodDeclarationAbbrevs(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { int pos = p; pos = writeMethodDeclarationAbbrev(context, AbbrevCode.METHOD_DECLARATION, buffer, pos); + pos = writeMethodDeclarationAbbrev(context, AbbrevCode.METHOD_DECLARATION_INLINE, buffer, pos); pos = writeMethodDeclarationAbbrev(context, AbbrevCode.METHOD_DECLARATION_STATIC, buffer, pos); + pos = writeMethodDeclarationAbbrev(context, AbbrevCode.METHOD_DECLARATION_INLINE_STATIC, buffer, pos); pos = writeMethodDeclarationAbbrev(context, AbbrevCode.METHOD_DECLARATION_SKELETON, buffer, pos); return pos; } @@ -1195,11 +1218,15 @@ private int writeMethodDeclarationAbbrev(@SuppressWarnings("unused") DebugContex pos = writeAbbrevCode(abbrevCode, buffer, pos); pos = writeTag(DwarfTag.DW_TAG_subprogram, buffer, pos); pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_yes, buffer, pos); + if (abbrevCode == AbbrevCode.METHOD_DECLARATION_INLINE || abbrevCode == AbbrevCode.METHOD_DECLARATION_INLINE_STATIC) { + pos = writeAttrType(DwarfAttribute.DW_AT_inline, buffer, pos); + pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos); + } pos = writeAttrType(DwarfAttribute.DW_AT_external, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_name, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_strp, buffer, pos); - if (abbrevCode == AbbrevCode.METHOD_DECLARATION || abbrevCode == AbbrevCode.METHOD_DECLARATION_STATIC) { + if (abbrevCode != AbbrevCode.METHOD_DECLARATION_SKELETON) { pos = writeAttrType(DwarfAttribute.DW_AT_decl_file, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_data2, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_decl_line, buffer, pos); @@ -1215,14 +1242,14 @@ private int writeMethodDeclarationAbbrev(@SuppressWarnings("unused") DebugContex pos = writeAttrForm(DwarfForm.DW_FORM_data1, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_declaration, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_flag, buffer, pos); - if (abbrevCode == AbbrevCode.METHOD_DECLARATION || abbrevCode == AbbrevCode.METHOD_DECLARATION_STATIC) { - /* This is not in DWARF2 */ - // pos = writeAttrType(DW_AT_virtuality, buffer, pos); - // pos = writeAttrForm(DW_FORM_data1, buffer, pos); + /* This is not in DWARF2 */ + // pos = writeAttrType(DW_AT_virtuality, buffer, pos); + // pos = writeAttrForm(DW_FORM_data1, buffer, pos); + if (abbrevCode != AbbrevCode.METHOD_DECLARATION_SKELETON) { pos = writeAttrType(DwarfAttribute.DW_AT_containing_type, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_ref_sig8, buffer, pos); } - if (abbrevCode == AbbrevCode.METHOD_DECLARATION) { + if (abbrevCode == AbbrevCode.METHOD_DECLARATION || abbrevCode == AbbrevCode.METHOD_DECLARATION_INLINE) { pos = writeAttrType(DwarfAttribute.DW_AT_object_pointer, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_ref4, buffer, pos); } @@ -1745,11 +1772,11 @@ private int writeNullAbbrev(@SuppressWarnings("unused") DebugContext context, by return pos; } - private int writeInlinedSubroutineAbbrev(byte[] buffer, int p, boolean withChildren) { + private int writeInlinedSubroutineAbbrev(byte[] buffer, int p) { int pos = p; - pos = writeAbbrevCode(withChildren ? AbbrevCode.INLINED_SUBROUTINE_WITH_CHILDREN : AbbrevCode.INLINED_SUBROUTINE, buffer, pos); + pos = writeAbbrevCode(AbbrevCode.INLINED_SUBROUTINE, buffer, pos); pos = writeTag(DwarfTag.DW_TAG_inlined_subroutine, buffer, pos); - pos = writeHasChildren(withChildren ? DwarfHasChildren.DW_CHILDREN_yes : DwarfHasChildren.DW_CHILDREN_no, buffer, pos); + pos = writeHasChildren(DwarfHasChildren.DW_CHILDREN_yes, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_abstract_origin, buffer, pos); pos = writeAttrForm(DwarfForm.DW_FORM_ref4, buffer, pos); pos = writeAttrType(DwarfAttribute.DW_AT_low_pc, buffer, pos); diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java index a81245b3c52f..5f51bcbca2dd 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -27,16 +27,15 @@ package com.oracle.objectfile.elf.dwarf; import java.nio.ByteOrder; - -import org.graalvm.collections.EconomicMap; +import java.util.HashMap; import com.oracle.objectfile.debugentry.ClassEntry; import com.oracle.objectfile.debugentry.DebugInfoBase; +import com.oracle.objectfile.debugentry.LocalEntry; import com.oracle.objectfile.debugentry.MethodEntry; import com.oracle.objectfile.debugentry.StructureTypeEntry; import com.oracle.objectfile.debugentry.TypeEntry; import com.oracle.objectfile.debugentry.range.Range; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalInfo; import com.oracle.objectfile.elf.ELFMachine; import com.oracle.objectfile.elf.dwarf.constants.DwarfLanguage; @@ -52,7 +51,7 @@ public class DwarfDebugInfo extends DebugInfoBase { /* * Define all the abbrev section codes we need for our DIEs. */ - enum AbbrevCode { + public enum AbbrevCode { /* null marker which must come first as its ordinal has to equal zero */ NULL, /* Level 0 DIEs. */ @@ -70,6 +69,7 @@ enum AbbrevCode { CLASS_LAYOUT_TU, CLASS_LAYOUT_CU, CLASS_LAYOUT_ARRAY, + CLASS_LAYOUT_OPAQUE, TYPE_POINTER_SIG, TYPE_POINTER, FOREIGN_TYPEDEF, @@ -81,7 +81,9 @@ enum AbbrevCode { COMPRESSED_LAYOUT, /* Level 2 DIEs. */ METHOD_DECLARATION, + METHOD_DECLARATION_INLINE, METHOD_DECLARATION_STATIC, + METHOD_DECLARATION_INLINE_STATIC, METHOD_DECLARATION_SKELETON, FIELD_DECLARATION_1, FIELD_DECLARATION_2, @@ -96,7 +98,6 @@ enum AbbrevCode { INTERFACE_IMPLEMENTOR, /* Level 2+K DIEs (where inline depth K >= 0) */ INLINED_SUBROUTINE, - INLINED_SUBROUTINE_WITH_CHILDREN, ABSTRACT_INLINE_METHOD, /* Level 3 DIEs. */ METHOD_PARAMETER_DECLARATION_1, @@ -125,23 +126,9 @@ enum AbbrevCode { public static final byte rheapbase_x86 = (byte) 14; public static final byte rthread_x86 = (byte) 15; - /* - * A prefix used to label indirect types used to ensure gdb performs oop reference --> raw - * address translation - */ - public static final String COMPRESSED_PREFIX = "_z_."; - /* - * A prefix used for type signature generation to generate unique type signatures for type - * layout type units - */ - public static final String LAYOUT_PREFIX = "_layout_."; - /* - * The name of the type for header field hub which needs special case processing to remove tag - * bits - */ - public static final String HUB_TYPE_NAME = "Encoded$Dynamic$Hub"; /* Full byte/word values. */ private final DwarfStrSectionImpl dwarfStrSection; + private final DwarfLineStrSectionImpl dwarfLineStrSection; private final DwarfAbbrevSectionImpl dwarfAbbrevSection; private final DwarfInfoSectionImpl dwarfInfoSection; private final DwarfLocSectionImpl dwarfLocSection; @@ -164,23 +151,24 @@ enum AbbrevCode { * n.b. this collection includes entries for the structure types used to define the object and * array headers which do not have an associated TypeEntry. */ - private final EconomicMap classPropertiesIndex = EconomicMap.create(); + private final HashMap classPropertiesIndex = new HashMap<>(); /** * A collection of method properties associated with each generated method record. */ - private final EconomicMap methodPropertiesIndex = EconomicMap.create(); + private final HashMap methodPropertiesIndex = new HashMap<>(); /** * A collection of local variable properties associated with an inlined subrange. */ - private final EconomicMap rangeLocalPropertiesIndex = EconomicMap.create(); + private final HashMap rangeLocalPropertiesIndex = new HashMap<>(); @SuppressWarnings("this-escape") public DwarfDebugInfo(ELFMachine elfMachine, ByteOrder byteOrder) { super(byteOrder); this.elfMachine = elfMachine; dwarfStrSection = new DwarfStrSectionImpl(this); + dwarfLineStrSection = new DwarfLineStrSectionImpl(this); dwarfAbbrevSection = new DwarfAbbrevSectionImpl(this); dwarfInfoSection = new DwarfInfoSectionImpl(this); dwarfLocSection = new DwarfLocSectionImpl(this); @@ -203,6 +191,10 @@ public DwarfStrSectionImpl getStrSectionImpl() { return dwarfStrSection; } + public DwarfLineStrSectionImpl getLineStrSectionImpl() { + return dwarfLineStrSection; + } + public DwarfAbbrevSectionImpl getAbbrevSectionImpl() { return dwarfAbbrevSection; } @@ -271,7 +263,7 @@ static class DwarfClassProperties { /** * Map from field names to info section index for the field declaration. */ - private EconomicMap fieldDeclarationIndex; + private HashMap fieldDeclarationIndex; public StructureTypeEntry getTypeEntry() { return typeEntry; @@ -301,12 +293,12 @@ static class DwarfMethodProperties { * Per class map that identifies the info declarations for a top level method declaration or * an abstract inline method declaration. */ - private EconomicMap localPropertiesMap; + private HashMap localPropertiesMap; /** * Per class map that identifies the info declaration for an abstract inline method. */ - private EconomicMap abstractInlineMethodIndex; + private HashMap abstractInlineMethodIndex; DwarfMethodProperties() { methodDeclarationIndex = -1; @@ -326,19 +318,14 @@ public void setMethodDeclarationIndex(int pos) { public DwarfLocalProperties getLocalProperties(ClassEntry classEntry) { if (localPropertiesMap == null) { - localPropertiesMap = EconomicMap.create(); - } - DwarfLocalProperties localProperties = localPropertiesMap.get(classEntry); - if (localProperties == null) { - localProperties = new DwarfLocalProperties(); - localPropertiesMap.put(classEntry, localProperties); + localPropertiesMap = new HashMap<>(); } - return localProperties; + return localPropertiesMap.computeIfAbsent(classEntry, k -> new DwarfLocalProperties()); } public void setAbstractInlineMethodIndex(ClassEntry classEntry, int pos) { if (abstractInlineMethodIndex == null) { - abstractInlineMethodIndex = EconomicMap.create(); + abstractInlineMethodIndex = new HashMap<>(); } // replace but check it did not change Integer val = abstractInlineMethodIndex.put(classEntry, pos); @@ -383,7 +370,7 @@ private DwarfMethodProperties lookupMethodProperties(MethodEntry methodEntry) { public void setCUIndex(ClassEntry classEntry, int idx) { assert idx >= 0; DwarfClassProperties classProperties = lookupClassProperties(classEntry); - assert classProperties.getTypeEntry() == classEntry; + assert classProperties.getTypeEntry().equals(classEntry); assert classProperties.cuIndex == -1 || classProperties.cuIndex == idx; classProperties.cuIndex = idx; } @@ -391,7 +378,7 @@ public void setCUIndex(ClassEntry classEntry, int idx) { public int getCUIndex(ClassEntry classEntry) { DwarfClassProperties classProperties; classProperties = lookupClassProperties(classEntry); - assert classProperties.getTypeEntry() == classEntry; + assert classProperties.getTypeEntry().equals(classEntry); assert classProperties.cuIndex >= 0; return classProperties.cuIndex; } @@ -399,7 +386,7 @@ public int getCUIndex(ClassEntry classEntry) { public void setCodeRangesIndex(ClassEntry classEntry, int idx) { assert idx >= 0; DwarfClassProperties classProperties = lookupClassProperties(classEntry); - assert classProperties.getTypeEntry() == classEntry; + assert classProperties.getTypeEntry().equals(classEntry); assert classProperties.codeRangesIndex == -1 || classProperties.codeRangesIndex == idx; classProperties.codeRangesIndex = idx; } @@ -407,7 +394,7 @@ public void setCodeRangesIndex(ClassEntry classEntry, int idx) { public int getCodeRangesIndex(ClassEntry classEntry) { DwarfClassProperties classProperties; classProperties = lookupClassProperties(classEntry); - assert classProperties.getTypeEntry() == classEntry; + assert classProperties.getTypeEntry().equals(classEntry); assert classProperties.codeRangesIndex >= 0; return classProperties.codeRangesIndex; } @@ -415,7 +402,7 @@ public int getCodeRangesIndex(ClassEntry classEntry) { public void setLocationListIndex(ClassEntry classEntry, int idx) { assert idx >= 0; DwarfClassProperties classProperties = lookupClassProperties(classEntry); - assert classProperties.getTypeEntry() == classEntry; + assert classProperties.getTypeEntry().equals(classEntry); assert classProperties.locationListIndex == 0 || classProperties.locationListIndex == idx; classProperties.locationListIndex = idx; } @@ -423,14 +410,14 @@ public void setLocationListIndex(ClassEntry classEntry, int idx) { public int getLocationListIndex(ClassEntry classEntry) { DwarfClassProperties classProperties; classProperties = lookupClassProperties(classEntry); - assert classProperties.getTypeEntry() == classEntry; + assert classProperties.getTypeEntry().equals(classEntry); return classProperties.locationListIndex; } public void setLineIndex(ClassEntry classEntry, int idx) { assert idx >= 0; DwarfClassProperties classProperties = lookupClassProperties(classEntry); - assert classProperties.getTypeEntry() == classEntry; + assert classProperties.getTypeEntry().equals(classEntry); assert classProperties.lineIndex == -1 || classProperties.lineIndex == idx; classProperties.lineIndex = idx; } @@ -438,7 +425,7 @@ public void setLineIndex(ClassEntry classEntry, int idx) { public int getLineIndex(ClassEntry classEntry) { DwarfClassProperties classProperties; classProperties = lookupClassProperties(classEntry); - assert classProperties.getTypeEntry() == classEntry; + assert classProperties.getTypeEntry().equals(classEntry); assert classProperties.lineIndex >= 0; return classProperties.lineIndex; } @@ -446,7 +433,7 @@ public int getLineIndex(ClassEntry classEntry) { public void setLinePrologueSize(ClassEntry classEntry, int size) { assert size >= 0; DwarfClassProperties classProperties = lookupClassProperties(classEntry); - assert classProperties.getTypeEntry() == classEntry; + assert classProperties.getTypeEntry().equals(classEntry); assert classProperties.linePrologueSize == -1 || classProperties.linePrologueSize == size; classProperties.linePrologueSize = size; } @@ -454,7 +441,7 @@ public void setLinePrologueSize(ClassEntry classEntry, int size) { public int getLinePrologueSize(ClassEntry classEntry) { DwarfClassProperties classProperties; classProperties = lookupClassProperties(classEntry); - assert classProperties.getTypeEntry() == classEntry; + assert classProperties.getTypeEntry().equals(classEntry); assert classProperties.linePrologueSize >= 0; return classProperties.linePrologueSize; } @@ -462,10 +449,10 @@ public int getLinePrologueSize(ClassEntry classEntry) { public void setFieldDeclarationIndex(StructureTypeEntry entry, String fieldName, int pos) { DwarfClassProperties classProperties; classProperties = lookupClassProperties(entry); - assert classProperties.getTypeEntry() == entry; - EconomicMap fieldDeclarationIndex = classProperties.fieldDeclarationIndex; + assert classProperties.getTypeEntry().equals(entry); + HashMap fieldDeclarationIndex = classProperties.fieldDeclarationIndex; if (fieldDeclarationIndex == null) { - classProperties.fieldDeclarationIndex = fieldDeclarationIndex = EconomicMap.create(); + classProperties.fieldDeclarationIndex = fieldDeclarationIndex = new HashMap<>(); } if (fieldDeclarationIndex.get(fieldName) != null) { assert fieldDeclarationIndex.get(fieldName) == pos : entry.getTypeName() + fieldName; @@ -477,8 +464,8 @@ public void setFieldDeclarationIndex(StructureTypeEntry entry, String fieldName, public int getFieldDeclarationIndex(StructureTypeEntry entry, String fieldName) { DwarfClassProperties classProperties; classProperties = lookupClassProperties(entry); - assert classProperties.getTypeEntry() == entry; - EconomicMap fieldDeclarationIndex = classProperties.fieldDeclarationIndex; + assert classProperties.getTypeEntry().equals(entry); + HashMap fieldDeclarationIndex = classProperties.fieldDeclarationIndex; assert fieldDeclarationIndex != null : fieldName; assert fieldDeclarationIndex.get(fieldName) != null : entry.getTypeName() + fieldName; return fieldDeclarationIndex.get(fieldName); @@ -500,17 +487,17 @@ public int getMethodDeclarationIndex(MethodEntry methodEntry) { */ static final class DwarfLocalProperties { - private EconomicMap locals; + private final HashMap locals; private DwarfLocalProperties() { - locals = EconomicMap.create(); + locals = new HashMap<>(); } - int getIndex(DebugLocalInfo localInfo) { + int getIndex(LocalEntry localInfo) { return locals.get(localInfo); } - void setIndex(DebugLocalInfo localInfo, int index) { + void setIndex(LocalEntry localInfo, int index) { if (locals.get(localInfo) != null) { assert locals.get(localInfo) == index; } else { @@ -533,16 +520,16 @@ private DwarfLocalProperties addRangeLocalProperties(Range range) { return localProperties; } - public DwarfLocalProperties lookupLocalProperties(ClassEntry classEntry, MethodEntry methodEntry) { + private DwarfLocalProperties lookupLocalProperties(ClassEntry classEntry, MethodEntry methodEntry) { return lookupMethodProperties(methodEntry).getLocalProperties(classEntry); } - public void setMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, DebugLocalInfo localInfo, int index) { + public void setMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, LocalEntry localInfo, int index) { DwarfLocalProperties localProperties = lookupLocalProperties(classEntry, methodEntry); localProperties.setIndex(localInfo, index); } - public int getMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, DebugLocalInfo localInfo) { + public int getMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, LocalEntry localInfo) { DwarfLocalProperties localProperties = lookupLocalProperties(classEntry, methodEntry); assert localProperties != null : "get of non-existent local index"; int index = localProperties.getIndex(localInfo); @@ -550,7 +537,7 @@ public int getMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, D return index; } - public void setRangeLocalIndex(Range range, DebugLocalInfo localInfo, int index) { + public void setRangeLocalIndex(Range range, LocalEntry localInfo, int index) { DwarfLocalProperties rangeProperties = rangeLocalPropertiesIndex.get(range); if (rangeProperties == null) { rangeProperties = addRangeLocalProperties(range); @@ -558,10 +545,10 @@ public void setRangeLocalIndex(Range range, DebugLocalInfo localInfo, int index) rangeProperties.setIndex(localInfo, index); } - public int getRangeLocalIndex(Range range, DebugLocalInfo localinfo) { + public int getRangeLocalIndex(Range range, LocalEntry localInfo) { DwarfLocalProperties rangeProperties = rangeLocalPropertiesIndex.get(range); assert rangeProperties != null : "get of non-existent local index"; - int index = rangeProperties.getIndex(localinfo); + int index = rangeProperties.getIndex(localInfo); assert index >= 0 : "get of local index before it was set"; return index; } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImpl.java index ea0c269521f1..345a2906f2f9 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImpl.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,14 +26,15 @@ package com.oracle.objectfile.elf.dwarf; +import java.util.List; + import com.oracle.objectfile.debugentry.CompiledMethodEntry; +import com.oracle.objectfile.debugentry.FrameSizeChangeEntry; import com.oracle.objectfile.debugentry.range.Range; -import com.oracle.objectfile.debuginfo.DebugInfoProvider; import com.oracle.objectfile.elf.dwarf.constants.DwarfFrameValue; import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName; -import jdk.graal.compiler.debug.DebugContext; -import java.util.List; +import jdk.graal.compiler.debug.DebugContext; /** * Section generic generator for debug_frame section. @@ -45,8 +46,8 @@ public abstract class DwarfFrameSectionImpl extends DwarfSectionImpl { private static final int CFA_CIE_id_default = -1; public DwarfFrameSectionImpl(DwarfDebugInfo dwarfSections) { - // debug_frame section depends on debug_line section - super(dwarfSections, DwarfSectionName.DW_FRAME_SECTION, DwarfSectionName.DW_LINE_SECTION); + // debug_frame section depends on debug_line_str section + super(dwarfSections, DwarfSectionName.DW_FRAME_SECTION, DwarfSectionName.DW_LINE_STR_SECTION); } @Override @@ -74,7 +75,7 @@ public void writeContent(DebugContext context) { int size = buffer.length; int pos = 0; - enableLog(context, pos); + enableLog(context); /* * There are entries for the prologue region where the stack is being built, the method body @@ -143,11 +144,11 @@ private int writeCIEVersion(byte[] buffer, int pos) { private int writeMethodFrame(CompiledMethodEntry compiledEntry, byte[] buffer, int p) { int pos = p; int lengthPos = pos; - Range range = compiledEntry.getPrimary(); + Range range = compiledEntry.primary(); long lo = range.getLo(); long hi = range.getHi(); - pos = writeFDEHeader((int) lo, (int) hi, buffer, pos); - pos = writeFDEs(compiledEntry.getFrameSize(), compiledEntry.getFrameSizeInfos(), buffer, pos); + pos = writeFDEHeader(lo, hi, buffer, pos); + pos = writeFDEs(compiledEntry.frameSize(), compiledEntry.frameSizeInfos(), buffer, pos); pos = writePaddingNops(buffer, pos); patchLength(lengthPos, buffer, pos); return pos; @@ -161,9 +162,9 @@ private int writeMethodFrames(byte[] buffer, int p) { return cursor.get(); } - protected abstract int writeFDEs(int frameSize, List frameSizeInfos, byte[] buffer, int pos); + protected abstract int writeFDEs(int frameSize, List frameSizeInfos, byte[] buffer, int pos); - private int writeFDEHeader(int lo, int hi, byte[] buffer, int p) { + private int writeFDEHeader(long lo, long hi, byte[] buffer, int p) { /* * We only need a vanilla FDE header with default fields the layout is: * @@ -189,7 +190,7 @@ private int writeFDEHeader(int lo, int hi, byte[] buffer, int p) { /* CIE_offset */ pos = writeInt(0, buffer, pos); /* Initial address. */ - pos = writeRelocatableCodeOffset(lo, buffer, pos); + pos = writeCodeOffset(lo, buffer, pos); /* Address range. */ return writeLong(hi - lo, buffer, pos); } @@ -263,8 +264,7 @@ protected int writeOffset(int register, int offset, byte[] buffer, int p) { protected int writeRestore(int register, byte[] buffer, int p) { byte op = restoreOp(register); - int pos = p; - return writeByte(op, buffer, pos); + return writeByte(op, buffer, p); } @SuppressWarnings("unused") diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplAArch64.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplAArch64.java index b568d396c919..51fc2b6b475a 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplAArch64.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplAArch64.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,10 +26,10 @@ package com.oracle.objectfile.elf.dwarf; -import com.oracle.objectfile.debuginfo.DebugInfoProvider; - import java.util.List; +import com.oracle.objectfile.debugentry.FrameSizeChangeEntry; + /** * AArch64-specific section generator for debug_frame section that knows details of AArch64 * registers and frame layout. @@ -73,14 +73,14 @@ public int writeInitialInstructions(byte[] buffer, int p) { } @Override - protected int writeFDEs(int frameSize, List frameSizeInfos, byte[] buffer, int p) { + protected int writeFDEs(int frameSize, List frameSizeInfos, byte[] buffer, int p) { int pos = p; int currentOffset = 0; - for (DebugInfoProvider.DebugFrameSizeChange debugFrameSizeInfo : frameSizeInfos) { - int advance = debugFrameSizeInfo.getOffset() - currentOffset; + for (FrameSizeChangeEntry frameSizeInfo : frameSizeInfos) { + int advance = frameSizeInfo.offset() - currentOffset; currentOffset += advance; pos = writeAdvanceLoc(advance, buffer, pos); - if (debugFrameSizeInfo.getType() == DebugInfoProvider.DebugFrameSizeChange.Type.EXTEND) { + if (frameSizeInfo.isExtend()) { /* * SP has been extended so rebase CFA using full frame. * diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplX86_64.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplX86_64.java index 2a2fb2fd78ba..16025f9f0899 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplX86_64.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfFrameSectionImplX86_64.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,10 +26,10 @@ package com.oracle.objectfile.elf.dwarf; -import com.oracle.objectfile.debuginfo.DebugInfoProvider; - import java.util.List; +import com.oracle.objectfile.debugentry.FrameSizeChangeEntry; + /** * x86_64-specific section generator for debug_frame section that knows details of x86_64 registers * and frame layout. @@ -84,14 +84,14 @@ public int writeInitialInstructions(byte[] buffer, int p) { } @Override - protected int writeFDEs(int frameSize, List frameSizeInfos, byte[] buffer, int p) { + protected int writeFDEs(int frameSize, List frameSizeInfos, byte[] buffer, int p) { int pos = p; int currentOffset = 0; - for (DebugInfoProvider.DebugFrameSizeChange debugFrameSizeInfo : frameSizeInfos) { - int advance = debugFrameSizeInfo.getOffset() - currentOffset; + for (FrameSizeChangeEntry frameSizeInfo : frameSizeInfos) { + int advance = frameSizeInfo.offset() - currentOffset; currentOffset += advance; pos = writeAdvanceLoc(advance, buffer, pos); - if (debugFrameSizeInfo.getType() == DebugInfoProvider.DebugFrameSizeChange.Type.EXTEND) { + if (frameSizeInfo.isExtend()) { /* * SP has been extended so rebase CFA using full frame. * diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfInfoSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfInfoSectionImpl.java index 954ebb572637..9d6058157308 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfInfoSectionImpl.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfInfoSectionImpl.java @@ -27,31 +27,25 @@ package com.oracle.objectfile.elf.dwarf; import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; +import java.util.HashSet; import java.util.List; -import java.util.stream.Stream; - -import org.graalvm.collections.EconomicSet; import com.oracle.objectfile.debugentry.ArrayTypeEntry; import com.oracle.objectfile.debugentry.ClassEntry; import com.oracle.objectfile.debugentry.CompiledMethodEntry; +import com.oracle.objectfile.debugentry.EnumClassEntry; import com.oracle.objectfile.debugentry.FieldEntry; import com.oracle.objectfile.debugentry.FileEntry; -import com.oracle.objectfile.debugentry.ForeignTypeEntry; +import com.oracle.objectfile.debugentry.ForeignStructTypeEntry; import com.oracle.objectfile.debugentry.HeaderTypeEntry; import com.oracle.objectfile.debugentry.InterfaceClassEntry; +import com.oracle.objectfile.debugentry.LocalEntry; import com.oracle.objectfile.debugentry.MethodEntry; +import com.oracle.objectfile.debugentry.PointerToTypeEntry; import com.oracle.objectfile.debugentry.PrimitiveTypeEntry; import com.oracle.objectfile.debugentry.StructureTypeEntry; import com.oracle.objectfile.debugentry.TypeEntry; import com.oracle.objectfile.debugentry.range.Range; -import com.oracle.objectfile.debugentry.range.SubRange; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalValueInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugPrimitiveTypeInfo; import com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.AbbrevCode; import com.oracle.objectfile.elf.dwarf.constants.DwarfAccess; import com.oracle.objectfile.elf.dwarf.constants.DwarfEncoding; @@ -64,9 +58,6 @@ import com.oracle.objectfile.elf.dwarf.constants.DwarfVersion; import jdk.graal.compiler.debug.DebugContext; -import jdk.vm.ci.meta.JavaConstant; -import jdk.vm.ci.meta.JavaKind; -import jdk.vm.ci.meta.PrimitiveConstant; /** * Section generator for debug_info section. @@ -105,7 +96,7 @@ public void writeContent(DebugContext context) { int size = buffer.length; int pos = 0; - enableLog(context, pos); + enableLog(context); log(context, " [0x%08x] DEBUG_INFO", pos); log(context, " [0x%08x] size = 0x%08x", pos, size); @@ -113,49 +104,80 @@ public void writeContent(DebugContext context) { assert pos == size; } - DwarfEncoding computeEncoding(int flags, int bitCount) { + DwarfEncoding computeEncoding(PrimitiveTypeEntry type) { + int bitCount = type.getBitCount(); assert bitCount > 0; - if ((flags & DebugPrimitiveTypeInfo.FLAG_NUMERIC) != 0) { - if (((flags & DebugPrimitiveTypeInfo.FLAG_INTEGRAL) != 0)) { - if ((flags & DebugPrimitiveTypeInfo.FLAG_SIGNED) != 0) { - switch (bitCount) { - case 8: - return DwarfEncoding.DW_ATE_signed_char; - default: - assert bitCount == 16 || bitCount == 32 || bitCount == 64; - return DwarfEncoding.DW_ATE_signed; - } + if (type.isNumericInteger()) { + if (type.isUnsigned()) { + if (bitCount == 1) { + return DwarfEncoding.DW_ATE_boolean; + } else if (bitCount == 8) { + return DwarfEncoding.DW_ATE_unsigned_char; } else { - assert bitCount == 16; + assert bitCount == 16 || bitCount == 32 || bitCount == 64; return DwarfEncoding.DW_ATE_unsigned; } + } else if (bitCount == 8) { + return DwarfEncoding.DW_ATE_signed_char; } else { - assert bitCount == 32 || bitCount == 64; - return DwarfEncoding.DW_ATE_float; + assert bitCount == 16 || bitCount == 32 || bitCount == 64; + return DwarfEncoding.DW_ATE_signed; } } else { - assert bitCount == 1; - return DwarfEncoding.DW_ATE_boolean; + assert type.isNumericFloat(); + assert bitCount == 32 || bitCount == 64; + return DwarfEncoding.DW_ATE_float; } } public int generateContent(DebugContext context, byte[] buffer) { int pos = 0; - /* Write TUs for primitive types and header struct. */ - pos = writeBuiltInTypes(context, buffer, pos); /* - * Write TUs and CUs for all instance classes, which includes interfaces and enums. That - * also incorporates interfaces that model foreign types. + * Write TUs for primitive types and pointer to types. Required for AOT and run-time debug + * info. + */ + pos = writePrimitives(context, buffer, pos); + pos = writePointerToTypes(context, buffer, pos); + + /* + * Write CUs for all instance classes, which includes interfaces and enums. Additionally, + * for AOT debug info this also writes TUs. */ pos = writeInstanceClasses(context, buffer, pos); - /* Write TUs and CUs for array types. */ - pos = writeArrays(context, buffer, pos); + if (dwarfSections.isRuntimeCompilation()) { + /* + * All structured types are represented as opaque types. I.e. they refer to types + * produced for the AOT debug info. The referred type must be in the AOT debug info and + * GDB is able to resolve it by name. + */ + pos = writeOpaqueTypes(context, buffer, pos); + } else { + /* + * This is the AOT debug info, we write all gathered information into the debug info + * object file. Most of this information is only produced at image build time. + */ - /* Write CU for class constant objects. */ - pos = writeClassConstantObjects(context, buffer, pos); + /* + * Write the header struct representing the object header o a Java object in the native + * image. + */ + pos = writeHeaderType(context, buffer, pos); + /* + * Write TUs for foreign structure types. No CUs, functions of foreign types are handled + * as special instance class. + */ + pos = writeForeignStructTypes(context, buffer, pos); + /* Write TUs and CUs for array types. */ + pos = writeArrays(context, buffer, pos); + /* + * Write CU for class constant objects. This also contains class constant objects for + * foreign types. + */ + pos = writeClassConstantObjects(context, buffer, pos); + } return pos; } @@ -165,7 +187,7 @@ private int writeSkeletonClassLayout(DebugContext context, ClassEntry classEntry AbbrevCode abbrevCode = AbbrevCode.CLASS_LAYOUT_CU; log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); - String name = classEntry.getTypeName(); + String name = uniqueDebugString(classEntry.getTypeName()); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); pos = writeStrSectionOffset(name, buffer, pos); log(context, " [0x%08x] declaration true", pos); @@ -185,11 +207,12 @@ private int writeSkeletonClassLayout(DebugContext context, ClassEntry classEntry return writeAttrNull(buffer, pos); } - private int writeBuiltInTypes(DebugContext context, byte[] buffer, int p) { - int pos = p; + private int writePrimitives(DebugContext context, byte[] buffer, int p) { + log(context, " [0x%08x] primitive types", p); - log(context, " [0x%08x] primitive types", pos); - Cursor cursor = new Cursor(pos); + Cursor cursor = new Cursor(p); + // write primitives + // i.e. all Java primitives and foreign primitive types. primitiveTypeStream().forEach(primitiveTypeEntry -> { if (primitiveTypeEntry.getBitCount() > 0) { cursor.set(writePrimitiveType(context, primitiveTypeEntry, buffer, cursor.get())); @@ -197,10 +220,32 @@ private int writeBuiltInTypes(DebugContext context, byte[] buffer, int p) { cursor.set(writeVoidType(context, primitiveTypeEntry, buffer, cursor.get())); } }); - pos = cursor.get(); - log(context, " [0x%08x] header type", pos); - return writeHeaderType(context, headerType(), buffer, pos); + return cursor.get(); + } + + private int writePointerToTypes(DebugContext context, byte[] buffer, int p) { + log(context, " [0x%08x] pointer to types", p); + + Cursor cursor = new Cursor(p); + // write foreign pointer types + pointerTypeStream().forEach(pointerTypeEntry -> { + cursor.set(writePointerToType(context, pointerTypeEntry, buffer, cursor.get())); + }); + + return cursor.get(); + } + + private int writeForeignStructTypes(DebugContext context, byte[] buffer, int p) { + log(context, " [0x%08x] foreign struct types", p); + + Cursor cursor = new Cursor(p); + // write foreign pointer types + foreignStructTypeStream().forEach(foreignStructTypeEntry -> { + cursor.set(writeTypeUnits(context, foreignStructTypeEntry, buffer, cursor.get())); + }); + + return cursor.get(); } private int writeClassConstantObjects(DebugContext context, byte[] buffer, int p) { @@ -208,7 +253,7 @@ private int writeClassConstantObjects(DebugContext context, byte[] buffer, int p // Write the single Java builtin unit header int lengthPos = pos; - log(context, " [0x%08x] <0> Java Builtin Compile Unit", pos); + log(context, " [0x%08x] <0> Class constants Compile Unit", pos); pos = writeCUHeader(buffer, pos); assert pos == lengthPos + CU_DIE_HEADER_SIZE; AbbrevCode abbrevCode = AbbrevCode.CLASS_CONSTANT_UNIT; @@ -221,7 +266,7 @@ private int writeClassConstantObjects(DebugContext context, byte[] buffer, int p String name = uniqueDebugString("JAVA"); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); pos = writeStrSectionOffset(name, buffer, pos); - String compilationDirectory = dwarfSections.getCachePath(); + String compilationDirectory = uniqueDebugString(dwarfSections.getCachePath()); log(context, " [0x%08x] comp_dir 0x%x (%s)", pos, debugStringIndex(compilationDirectory), compilationDirectory); pos = writeStrSectionOffset(compilationDirectory, buffer, pos); @@ -260,10 +305,10 @@ private int writePrimitiveType(DebugContext context, PrimitiveTypeEntry primitiv byte bitCount = (byte) primitiveTypeEntry.getBitCount(); log(context, " [0x%08x] bitCount %d", pos, bitCount); pos = writeAttrData1(bitCount, buffer, pos); - DwarfEncoding encoding = computeEncoding(primitiveTypeEntry.getFlags(), bitCount); + DwarfEncoding encoding = computeEncoding(primitiveTypeEntry); log(context, " [0x%08x] encoding 0x%x", pos, encoding.value()); pos = writeAttrEncoding(encoding, buffer, pos); - String name = primitiveTypeEntry.getTypeName(); + String name = uniqueDebugString(primitiveTypeEntry.getTypeName()); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); pos = writeStrSectionOffset(name, buffer, pos); @@ -287,7 +332,7 @@ private int writeVoidType(DebugContext context, PrimitiveTypeEntry primitiveType AbbrevCode abbrevCode = AbbrevCode.VOID_TYPE; log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); - String name = primitiveTypeEntry.getTypeName(); + String name = uniqueDebugString(primitiveTypeEntry.getTypeName()); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); pos = writeStrSectionOffset(name, buffer, pos); @@ -299,8 +344,68 @@ private int writeVoidType(DebugContext context, PrimitiveTypeEntry primitiveType return pos; } - private int writeHeaderType(DebugContext context, HeaderTypeEntry headerTypeEntry, byte[] buffer, int p) { + private int writePointerToType(DebugContext context, PointerToTypeEntry pointerTypeEntry, byte[] buffer, int p) { int pos = p; + long typeSignature = pointerTypeEntry.getTypeSignature(); + + // 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*. + + // Write a type unit header + int lengthPos = pos; + pos = writeTUHeader(typeSignature, buffer, pos); + int typeOffsetPos = pos - 4; + assert pos == lengthPos + TU_DIE_HEADER_SIZE; + AbbrevCode abbrevCode = AbbrevCode.TYPE_UNIT; + log(context, " [0x%08x] <0> Abbrev Number %d", pos, abbrevCode.ordinal()); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + log(context, " [0x%08x] language %s", pos, "DW_LANG_Java"); + pos = writeAttrLanguage(DwarfDebugInfo.LANG_ENCODING, buffer, pos); + log(context, " [0x%08x] use_UTF8", pos); + pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos); + + /* Define a pointer type referring to the base type */ + int refTypeIdx = pos; + log(context, " [0x%08x] foreign type wrapper", pos); + abbrevCode = AbbrevCode.TYPE_POINTER_SIG; + log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + int pointerSize = dwarfSections.pointerSize(); + log(context, " [0x%08x] byte_size 0x%x", pos, pointerSize); + pos = writeAttrData1((byte) pointerSize, buffer, pos); + long layoutTypeSignature = pointerTypeEntry.getPointerTo().getTypeSignature(); + log(context, " [0x%08x] type 0x%x", pos, layoutTypeSignature); + pos = writeTypeSignature(layoutTypeSignature, buffer, pos); + + /* Fix up the type offset. */ + writeInt(pos - lengthPos, buffer, typeOffsetPos); + + /* Define a typedef for the layout type using the Java name. */ + log(context, " [0x%08x] foreign typedef", pos); + abbrevCode = AbbrevCode.FOREIGN_TYPEDEF; + log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + String name = uniqueDebugString(pointerTypeEntry.getTypeName()); + log(context, " [0x%08x] name %s", pos, name); + pos = writeStrSectionOffset(name, buffer, pos); + log(context, " [0x%08x] type 0x%x", pos, refTypeIdx); + pos = writeAttrRef4(refTypeIdx, buffer, pos); + + /* Write a terminating null attribute for the top level TU DIE. */ + pos = writeAttrNull(buffer, pos); + + /* Fix up the TU length. */ + patchLength(lengthPos, buffer, pos); + return pos; + } + + private int writeHeaderType(DebugContext context, byte[] buffer, int p) { + int pos = p; + + log(context, " [0x%08x] header type", pos); + HeaderTypeEntry headerTypeEntry = headerType(); + long typeSignature = headerTypeEntry.getTypeSignature(); FieldEntry hubField = headerTypeEntry.getHubField(); ClassEntry hubType = (ClassEntry) hubField.getValueType(); @@ -364,7 +469,7 @@ private int writeHeaderType(DebugContext context, HeaderTypeEntry headerTypeEntr writeInt(pos - lengthPos, buffer, typeOffsetPos); /* Write the type representing the object header. */ - name = headerTypeEntry.getTypeName(); + name = uniqueDebugString(headerTypeEntry.getTypeName()); int headerSize = headerTypeEntry.getSize(); log(context, " [0x%08x] header type %s", pos, name); abbrevCode = AbbrevCode.OBJECT_HEADER; @@ -375,7 +480,7 @@ private int writeHeaderType(DebugContext context, HeaderTypeEntry headerTypeEntr log(context, " [0x%08x] byte_size 0x%x", pos, headerSize); pos = writeAttrData1((byte) headerSize, buffer, pos); pos = writeHubField(context, hubField, hubTypeIdx, buffer, pos); - pos = writeStructFields(context, headerTypeEntry.fields(), buffer, pos); + pos = writeStructFields(context, headerTypeEntry.getFields(), buffer, pos); /* Write a terminating null attribute. */ pos = writeAttrNull(buffer, pos); @@ -395,7 +500,7 @@ private int writeHubField(DebugContext context, FieldEntry hubFieldEntry, int hu log(context, " [0x%08x] hub field", pos); log(context, " [0x%08x] <2> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); - String fieldName = hubFieldEntry.fieldName(); + String fieldName = uniqueDebugString(hubFieldEntry.fieldName()); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(fieldName), fieldName); pos = writeStrSectionOffset(fieldName, buffer, pos); log(context, " [0x%08x] type 0x%x (%s)", pos, hubTypeIdx, DwarfDebugInfo.HUB_TYPE_NAME); @@ -409,7 +514,7 @@ private int writeHubField(DebugContext context, FieldEntry hubFieldEntry, int hu return writeAttrAccessibility(modifiers, buffer, pos); } - private int writeStructFields(DebugContext context, Stream fields, byte[] buffer, int p) { + private int writeStructFields(DebugContext context, List fields, byte[] buffer, int p) { Cursor cursor = new Cursor(p); fields.forEach(fieldEntry -> { cursor.set(writeStructField(context, fieldEntry, buffer, cursor.get())); @@ -419,27 +524,26 @@ private int writeStructFields(DebugContext context, Stream fields, b private int writeStructField(DebugContext context, FieldEntry fieldEntry, byte[] buffer, int p) { int pos = p; - String fieldName = fieldEntry.fieldName(); + String fieldName = uniqueDebugString(fieldEntry.fieldName()); TypeEntry valueType = fieldEntry.getValueType(); long typeSignature = 0; int typeIdx = 0; AbbrevCode abbrevCode = AbbrevCode.STRUCT_FIELD_SIG; if (fieldEntry.isEmbedded()) { // the field type must be a foreign type - ForeignTypeEntry foreignValueType = (ForeignTypeEntry) valueType; /* use the 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()) { + int valueSize = valueType.getSize(); + if (fieldSize != valueSize) { 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 typeIdx = pos; abbrevCode = AbbrevCode.STRUCT_FIELD; - pos = writeEmbeddedArrayDataType(context, foreignValueType, valueSize, fieldSize / valueSize, buffer, pos); + pos = writeEmbeddedArrayDataType(context, valueType, valueSize, fieldSize / valueSize, buffer, pos); } else { - if (foreignValueType.isPointer()) { - TypeEntry pointerTo = foreignValueType.getPointerTo(); + if (valueType instanceof PointerToTypeEntry pointerTypeEntry) { + TypeEntry pointerTo = pointerTypeEntry.getPointerTo(); assert pointerTo != null : "ADDRESS field pointer type must have a known target type"; // type the array using the referent of the pointer type // @@ -453,8 +557,10 @@ private int writeStructField(DebugContext context, FieldEntry fieldEntry, byte[] // referent type then the layout index of the referring type may still be unset // at this point. typeSignature = pointerTo.getTypeSignature(); + } else if (valueType instanceof ForeignStructTypeEntry foreignStructTypeEntry) { + typeSignature = foreignStructTypeEntry.getLayoutTypeSignature(); } else { - typeSignature = foreignValueType.getLayoutTypeSignature(); + typeSignature = valueType.getTypeSignature(); } } } else { @@ -486,9 +592,25 @@ private int writeInstanceClasses(DebugContext context, byte[] buffer, int pos) { log(context, " [0x%08x] instance classes", pos); Cursor cursor = new Cursor(pos); instanceClassStream().forEach(classEntry -> { - cursor.set(writeTypeUnits(context, classEntry, buffer, cursor.get())); - setCUIndex(classEntry, cursor.get()); - cursor.set(writeInstanceClassInfo(context, classEntry, buffer, cursor.get())); + /* + * For run-time debug info, we create a type unit with the opaque type, so no need to + * create a full type unit here. The foreign method list is no actual instance class, + * but just needs a compilation unit to reference the compilation. + */ + if (!dwarfSections.isRuntimeCompilation() && classEntry != dwarfSections.getForeignMethodListClassEntry()) { + cursor.set(writeTypeUnits(context, classEntry, buffer, cursor.get())); + } + /* + * We only need to write a CU for a class entry if compilations or static fields are + * available for this class. This includes inlined compilations as they refer to the + * declaration in the owner type CU. Other information is already written to the + * corresponding type units. + */ + if (classEntry.getMethods().stream().anyMatch(m -> m.isInRange() || m.isInlined()) || + classEntry.getFields().stream().anyMatch(DwarfInfoSectionImpl::isManifestedStaticField)) { + setCUIndex(classEntry, cursor.get()); + cursor.set(writeInstanceClassInfo(context, classEntry, buffer, cursor.get())); + } }); return cursor.get(); } @@ -496,15 +618,14 @@ private int writeInstanceClasses(DebugContext context, byte[] buffer, int pos) { private int writeTypeUnits(DebugContext context, StructureTypeEntry typeEntry, byte[] buffer, int p) { int pos = p; - if (typeEntry.isForeign()) { - ForeignTypeEntry foreignTypeEntry = (ForeignTypeEntry) typeEntry; - pos = writeForeignLayoutTypeUnit(context, foreignTypeEntry, buffer, pos); - pos = writeForeignTypeUnit(context, foreignTypeEntry, buffer, pos); + if (typeEntry instanceof ForeignStructTypeEntry foreignStructTypeEntry) { + pos = writeForeignStructLayoutTypeUnit(context, foreignStructTypeEntry, buffer, pos); + pos = writeForeignStructTypeUnit(context, foreignStructTypeEntry, buffer, pos); } else { - if (typeEntry.isArray()) { - pos = writeArrayLayoutTypeUnit(context, (ArrayTypeEntry) typeEntry, buffer, pos); - } else if (typeEntry.isInterface()) { - pos = writeInterfaceLayoutTypeUnit(context, (InterfaceClassEntry) typeEntry, buffer, pos); + if (typeEntry instanceof ArrayTypeEntry arrayTypeEntry) { + pos = writeArrayLayoutTypeUnit(context, arrayTypeEntry, buffer, pos); + } else if (typeEntry instanceof InterfaceClassEntry interfaceClassEntry) { + pos = writeInterfaceLayoutTypeUnit(context, interfaceClassEntry, buffer, pos); } else { assert typeEntry instanceof ClassEntry; pos = writeClassLayoutTypeUnit(context, (ClassEntry) typeEntry, buffer, pos); @@ -546,17 +667,17 @@ private int writePointerTypeUnit(DebugContext context, StructureTypeEntry typeEn int pos = p; String loaderId = ""; - if (typeEntry.isArray()) { - loaderId = ((ArrayTypeEntry) typeEntry).getLoaderId(); - } else if (typeEntry.isClass()) { - loaderId = ((ClassEntry) typeEntry).getLoaderId(); + if (typeEntry instanceof ArrayTypeEntry arrayTypeEntry) { + loaderId = arrayTypeEntry.getLoaderId(); + } else if (typeEntry instanceof ClassEntry classEntry) { + loaderId = classEntry.getLoaderId(); } int lengthPos = pos; long typeSignature = typeEntry.getTypeSignature(); pos = writeTUPreamble(context, typeSignature, loaderId, buffer, p); /* Define a pointer type referring to the underlying layout. */ - log(context, " [0x%08x] %s pointer type", pos, typeEntry.isInterface() ? "interface" : "class"); + log(context, " [0x%08x] %s pointer type", pos, typeEntry instanceof InterfaceClassEntry ? "interface" : "class"); AbbrevCode abbrevCode = AbbrevCode.TYPE_POINTER_SIG; log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); @@ -599,10 +720,10 @@ private int writePointerTypeUnitForCompressed(DebugContext context, StructureTyp /* if the class has a loader then embed the children in a namespace */ String loaderId = ""; - if (typeEntry.isArray()) { - loaderId = ((ArrayTypeEntry) typeEntry).getLoaderId(); - } else if (typeEntry.isClass()) { - loaderId = ((ClassEntry) typeEntry).getLoaderId(); + if (typeEntry instanceof ArrayTypeEntry arrayTypeEntry) { + loaderId = arrayTypeEntry.getLoaderId(); + } else if (typeEntry instanceof ClassEntry classEntry) { + loaderId = classEntry.getLoaderId(); } if (!loaderId.isEmpty()) { pos = writeNameSpace(context, loaderId, buffer, pos); @@ -619,7 +740,7 @@ private int writePointerTypeUnitForCompressed(DebugContext context, StructureTyp writeInt(pos - lengthPos, buffer, typeOffsetPos); /* Define a pointer type referring to the underlying layout. */ - log(context, " [0x%08x] %s compressed pointer type", pos, typeEntry.isInterface() ? "interface" : "class"); + log(context, " [0x%08x] %s compressed pointer type", pos, typeEntry instanceof InterfaceClassEntry ? "interface" : "class"); abbrevCode = AbbrevCode.TYPE_POINTER; log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); @@ -648,7 +769,7 @@ private int writeLayoutTypeForCompressed(DebugContext context, StructureTypeEntr * Write a wrapper type with a data_location attribute that can act as a target for * compressed oops. */ - log(context, " [0x%08x] compressed %s layout", pos, typeEntry.isInterface() ? "interface" : "class"); + log(context, " [0x%08x] compressed %s layout", pos, typeEntry instanceof InterfaceClassEntry ? "interface" : "class"); AbbrevCode abbrevCode = AbbrevCode.COMPRESSED_LAYOUT; log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); @@ -681,7 +802,7 @@ private int writeInterfaceLayoutTypeUnit(DebugContext context, InterfaceClassEnt AbbrevCode abbrevCode = AbbrevCode.INTERFACE_LAYOUT; log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); - String name = interfaceClassEntry.getTypeName(); + String name = uniqueDebugString(interfaceClassEntry.getTypeName()); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); pos = writeStrSectionOffset(name, buffer, pos); @@ -712,11 +833,12 @@ private int writeClassLayoutTypeUnit(DebugContext context, ClassEntry classEntry int lengthPos = pos; pos = writeTUPreamble(context, classEntry.getLayoutTypeSignature(), loaderId, buffer, pos); + int refTypeIdx = pos; log(context, " [0x%08x] type layout", pos); AbbrevCode abbrevCode = AbbrevCode.CLASS_LAYOUT_TU; log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); - String name = classEntry.getTypeName(); + String name = uniqueDebugString(classEntry.getTypeName()); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); pos = writeStrSectionOffset(name, buffer, pos); int size = classEntry.getSize(); @@ -741,6 +863,19 @@ private int writeClassLayoutTypeUnit(DebugContext context, ClassEntry classEntry pos = writeAttrNull(buffer, pos); } + if (classEntry instanceof EnumClassEntry enumClassEntry && !enumClassEntry.getTypedefName().isEmpty()) { + /* Define a typedef c enum type. */ + log(context, " [0x%08x] c enum typedef", pos); + abbrevCode = AbbrevCode.FOREIGN_TYPEDEF; + log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + name = uniqueDebugString(enumClassEntry.getTypedefName()); + log(context, " [0x%08x] name %s", pos, name); + pos = writeStrSectionOffset(name, buffer, pos); + log(context, " [0x%08x] type 0x%x", pos, refTypeIdx); + pos = writeAttrRef4(refTypeIdx, buffer, pos); + } + /* Write a terminating null attribute for the top level TU DIE. */ pos = writeAttrNull(buffer, pos); @@ -749,9 +884,9 @@ private int writeClassLayoutTypeUnit(DebugContext context, ClassEntry classEntry return pos; } - private int writeForeignTypeUnit(DebugContext context, ForeignTypeEntry foreignTypeEntry, byte[] buffer, int p) { + private int writeForeignStructTypeUnit(DebugContext context, ForeignStructTypeEntry foreignStructTypeEntry, byte[] buffer, int p) { int pos = p; - long typeSignature = foreignTypeEntry.getTypeSignature(); + long typeSignature = foreignStructTypeEntry.getTypeSignature(); // 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. @@ -770,22 +905,16 @@ private int writeForeignTypeUnit(DebugContext context, ForeignTypeEntry foreignT log(context, " [0x%08x] use_UTF8", pos); pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos); - /* if the class has a loader then embed the children in a namespace */ - String loaderId = foreignTypeEntry.getLoaderId(); - if (!loaderId.isEmpty()) { - pos = writeNameSpace(context, loaderId, buffer, pos); - } - /* Define a pointer type referring to the base type */ int refTypeIdx = pos; - log(context, " [0x%08x] foreign pointer type", pos); + log(context, " [0x%08x] foreign type wrapper", pos); abbrevCode = AbbrevCode.TYPE_POINTER_SIG; log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); int pointerSize = dwarfSections.pointerSize(); log(context, " [0x%08x] byte_size 0x%x", pos, pointerSize); pos = writeAttrData1((byte) pointerSize, buffer, pos); - long layoutTypeSignature = foreignTypeEntry.getLayoutTypeSignature(); + long layoutTypeSignature = foreignStructTypeEntry.getLayoutTypeSignature(); log(context, " [0x%08x] type 0x%x", pos, layoutTypeSignature); pos = writeTypeSignature(layoutTypeSignature, buffer, pos); @@ -797,17 +926,12 @@ private int writeForeignTypeUnit(DebugContext context, ForeignTypeEntry foreignT abbrevCode = AbbrevCode.FOREIGN_TYPEDEF; log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); - String name = uniqueDebugString(foreignTypeEntry.getTypeName()); + String name = uniqueDebugString(foreignStructTypeEntry.getTypeName()); log(context, " [0x%08x] name %s", pos, name); pos = writeStrSectionOffset(name, buffer, pos); log(context, " [0x%08x] type 0x%x", pos, refTypeIdx); pos = writeAttrRef4(refTypeIdx, buffer, pos); - if (!loaderId.isEmpty()) { - /* Write a terminating null attribute for the namespace. */ - pos = writeAttrNull(buffer, pos); - } - /* Write a terminating null attribute for the top level TU DIE. */ pos = writeAttrNull(buffer, pos); @@ -816,65 +940,107 @@ private int writeForeignTypeUnit(DebugContext context, ForeignTypeEntry foreignT return pos; } - private int writeForeignLayoutTypeUnit(DebugContext context, ForeignTypeEntry foreignTypeEntry, byte[] buffer, int p) { + private int writeOpaqueTypes(DebugContext context, byte[] buffer, int p) { + log(context, " [0x%08x] opaque types", p); + + Cursor cursor = new Cursor(p); + /* + * Write all structured types as opaque types (except Foreign method list). With this + * representation, types are resolved by name in gdb (at run-time we know the types must + * already exist in the AOT debug info). + */ + typeStream().filter(t -> t instanceof StructureTypeEntry && t != dwarfSections.getForeignMethodListClassEntry()) + .forEach(structureTypeEntry -> cursor.set(writeOpaqueType(context, structureTypeEntry, buffer, cursor.get()))); + + return cursor.get(); + } + + private int writeOpaqueType(DebugContext context, TypeEntry typeEntry, byte[] buffer, int p) { int pos = p; + long typeSignature = typeEntry.getTypeSignature(); - String loaderId = foreignTypeEntry.getLoaderId(); + // Write a type unit header int lengthPos = pos; + pos = writeTUHeader(typeSignature, buffer, pos); + int typeOffsetPos = pos - 4; + assert pos == lengthPos + TU_DIE_HEADER_SIZE; + AbbrevCode abbrevCode = AbbrevCode.TYPE_UNIT; + log(context, " [0x%08x] <0> Abbrev Number %d", pos, abbrevCode.ordinal()); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + log(context, " [0x%08x] language %s", pos, "DW_LANG_Java"); + pos = writeAttrLanguage(DwarfDebugInfo.LANG_ENCODING, buffer, pos); + log(context, " [0x%08x] use_UTF8", pos); + pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos); - /* Only write a TU preamble if we will write a new layout type. */ - if (foreignTypeEntry.isWord() || foreignTypeEntry.isIntegral() || foreignTypeEntry.isFloat() || foreignTypeEntry.isStruct()) { - pos = writeTUPreamble(context, foreignTypeEntry.getLayoutTypeSignature(), loaderId, buffer, pos); - } - - int size = foreignTypeEntry.getSize(); - if (foreignTypeEntry.isWord()) { - // define the type as a typedef for a signed or unsigned word i.e. we don't have a - // layout type - pos = writeForeignWordLayout(context, foreignTypeEntry, size, foreignTypeEntry.isSigned(), buffer, pos); - } else if (foreignTypeEntry.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); + int refTypeIdx = pos; + log(context, " [0x%08x] class layout", pos); + abbrevCode = AbbrevCode.CLASS_LAYOUT_OPAQUE; + log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + String name; + if (typeEntry instanceof ForeignStructTypeEntry foreignStructTypeEntry) { + name = uniqueDebugString(foreignStructTypeEntry.getTypedefName()); } else { - // this must be a pointer. if the target type is known use it to declare the pointer - // type, otherwise default to 'void *' - TypeEntry targetType = voidType(); - if (foreignTypeEntry.isPointer()) { - TypeEntry pointerTo = foreignTypeEntry.getPointerTo(); - if (pointerTo != null) { - targetType = pointerTo; - } - } - log(context, " [0x%08x] foreign pointer type %s referent 0x%x (%s)", pos, foreignTypeEntry.getTypeName(), targetType.getTypeSignature(), targetType.getTypeName()); - /* - * Setting the layout type to the type we point to reuses an available type unit, so we - * do not have to write are separate type unit. - * - * As we do not write anything, we can just return the initial position. - */ - foreignTypeEntry.setLayoutTypeSignature(targetType.getTypeSignature()); - return p; + name = uniqueDebugString(typeEntry.getTypeName()); } + log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); + pos = writeStrSectionOffset(name, buffer, pos); + log(context, " [0x%08x] declaration true", pos); + pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos); - /* - * 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 = writeSkeletonMethodDeclarations(context, foreignTypeEntry, buffer, pos); + /* Fix up the type offset. */ + writeInt(pos - lengthPos, buffer, typeOffsetPos); - if (!loaderId.isEmpty()) { - /* Write a terminating null attribute for the namespace. */ - pos = writeAttrNull(buffer, pos); + /* Define a pointer type referring to the underlying layout. */ + log(context, " [0x%08x] %s dummy pointer type", pos, typeEntry instanceof InterfaceClassEntry ? "interface" : "class"); + abbrevCode = AbbrevCode.TYPE_POINTER; + log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + int pointerSize = dwarfSections.pointerSize(); + log(context, " [0x%08x] byte_size 0x%x", pos, pointerSize); + pos = writeAttrData1((byte) pointerSize, buffer, pos); + log(context, " [0x%08x] type 0x%x", pos, refTypeIdx); + pos = writeAttrRef4(refTypeIdx, buffer, pos); + + /* Write a terminating null attribute for the top level TU DIE. */ + pos = writeAttrNull(buffer, pos); + + /* Fix up the TU length. */ + patchLength(lengthPos, buffer, pos); + return pos; + } + + private int writeForeignStructLayoutTypeUnit(DebugContext context, ForeignStructTypeEntry foreignStructTypeEntry, byte[] buffer, int p) { + int pos = p; + int lengthPos = pos; + + pos = writeTUPreamble(context, foreignStructTypeEntry.getLayoutTypeSignature(), "", buffer, pos); + + int size = foreignStructTypeEntry.getSize(); + // define this type using a structure layout + log(context, " [0x%08x] foreign struct type for %s", pos, foreignStructTypeEntry.getTypeName()); + AbbrevCode abbrevCode = AbbrevCode.FOREIGN_STRUCT; + log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + String typedefName = uniqueDebugString(foreignStructTypeEntry.getTypedefName()); + 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 + ForeignStructTypeEntry parent = foreignStructTypeEntry.getParent(); + if (parent != null) { + long typeSignature = parent.getLayoutTypeSignature(); + pos = writeSuperReference(context, typeSignature, parent.getTypedefName(), buffer, pos); } + pos = writeStructFields(context, foreignStructTypeEntry.getFields(), buffer, pos); + + /* + * Write a terminating null attribute for the structure type. + */ + pos = writeAttrNull(buffer, pos); + /* Write a terminating null attribute for the top level TU DIE. */ pos = writeAttrNull(buffer, pos); @@ -891,7 +1057,7 @@ private int writeInstanceClassInfo(DebugContext context, ClassEntry classEntry, pos = writeCUHeader(buffer, pos); assert pos == lengthPos + CU_DIE_HEADER_SIZE; AbbrevCode abbrevCode; - if (classEntry.hasCompiledEntries()) { + if (classEntry.hasCompiledMethods()) { if (getLocationListIndex(classEntry) == 0) { abbrevCode = AbbrevCode.CLASS_UNIT_2; } else { @@ -910,9 +1076,10 @@ private int writeInstanceClassInfo(DebugContext context, ClassEntry classEntry, if (name == null) { name = classEntry.getTypeName().replace('.', '/') + ".java"; } + name = uniqueDebugString(name); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); - pos = writeStrSectionOffset(uniqueDebugString(name), buffer, pos); - String compilationDirectory = dwarfSections.getCachePath(); + pos = writeStrSectionOffset(name, buffer, pos); + String compilationDirectory = uniqueDebugString(dwarfSections.getCachePath()); log(context, " [0x%08x] comp_dir 0x%x (%s)", pos, debugStringIndex(compilationDirectory), compilationDirectory); pos = writeStrSectionOffset(compilationDirectory, buffer, pos); if (abbrevCode == AbbrevCode.CLASS_UNIT_2 || abbrevCode == AbbrevCode.CLASS_UNIT_3) { @@ -920,7 +1087,7 @@ private int writeInstanceClassInfo(DebugContext context, ClassEntry classEntry, log(context, " [0x%08x] ranges 0x%x", pos, codeRangesIndex); pos = writeRangeListsSectionOffset(codeRangesIndex, buffer, pos); // write low_pc as well as ranges so that location lists can default the base address - int lo = classEntry.lowpc(); + long lo = classEntry.lowpc(); log(context, " [0x%08x] low_pc 0x%x", pos, codeRangesIndex); pos = writeAttrAddress(lo, buffer, pos); int lineIndex = getLineIndex(classEntry); @@ -940,20 +1107,23 @@ private int writeInstanceClassInfo(DebugContext context, ClassEntry classEntry, /* Now write the child DIEs starting with the layout and pointer type. */ - // this works for interfaces, foreign types and classes, entry kind specifics are in the - // type units - pos = writeSkeletonClassLayout(context, classEntry, buffer, pos); + if (dwarfSections.getForeignMethodListClassEntry() != classEntry) { + // This works for any structured type entry. Entry kind specifics are in the + // type units. + pos = writeSkeletonClassLayout(context, classEntry, buffer, pos); + } else { + // The foreign class list does not have a corresponding type unit, so we have to add + // full declarations here. + pos = writeMethodDeclarations(context, classEntry, buffer, pos); + } /* Write all compiled code locations */ - pos = writeMethodLocations(context, classEntry, buffer, pos); /* Write abstract inline methods. */ - pos = writeAbstractInlineMethods(context, classEntry, buffer, pos); /* Write all static field definitions */ - pos = writeStaticFieldLocations(context, classEntry, buffer, pos); /* if we opened a namespace then terminate its children */ @@ -1038,7 +1208,7 @@ private int writeSuperReference(DebugContext context, long typeSignature, String } private int writeFields(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { - return classEntry.fields().filter(DwarfInfoSectionImpl::isManifestedField).reduce(p, + return classEntry.getFields().stream().filter(DwarfInfoSectionImpl::isManifestedField).reduce(p, (pos, fieldEntry) -> writeField(context, classEntry, fieldEntry, buffer, pos), (oldPos, newPos) -> newPos); } @@ -1070,7 +1240,7 @@ private int writeField(DebugContext context, StructureTypeEntry entry, FieldEntr log(context, " [0x%08x] <2> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); - String name = fieldEntry.fieldName(); + String name = uniqueDebugString(fieldEntry.fieldName()); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); pos = writeStrSectionOffset(name, buffer, pos); /* We may not have a file and line for a field. */ @@ -1120,13 +1290,13 @@ private int writeSkeletonMethodDeclarations(DebugContext context, ClassEntry cla private int writeSkeletonMethodDeclaration(DebugContext context, ClassEntry classEntry, MethodEntry method, byte[] buffer, int p) { int pos = p; - log(context, " [0x%08x] method declaration %s::%s", pos, classEntry.getTypeName(), method.methodName()); + log(context, " [0x%08x] method declaration %s::%s", pos, classEntry.getTypeName(), method.getMethodName()); AbbrevCode abbrevCode = AbbrevCode.METHOD_DECLARATION_SKELETON; log(context, " [0x%08x] <2> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); log(context, " [0x%08x] external true", pos); pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos); - String name = uniqueDebugString(method.methodName()); + String name = uniqueDebugString(method.getMethodName()); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); pos = writeStrSectionOffset(name, buffer, pos); String linkageName = uniqueDebugString(method.getSymbolName()); @@ -1154,22 +1324,21 @@ private int writeSkeletonMethodDeclaration(DebugContext context, ClassEntry clas private int writeSkeletonMethodParameterDeclarations(DebugContext context, MethodEntry method, byte[] buffer, int p) { int pos = p; if (!Modifier.isStatic(method.getModifiers())) { - DebugLocalInfo paramInfo = method.getThisParam(); + LocalEntry paramInfo = method.getThisParam(); pos = writeSkeletonMethodParameterDeclaration(context, paramInfo, true, buffer, pos); } - for (int i = 0; i < method.getParamCount(); i++) { - DebugLocalInfo paramInfo = method.getParam(i); + for (LocalEntry paramInfo : method.getParams()) { pos = writeSkeletonMethodParameterDeclaration(context, paramInfo, false, buffer, pos); } return pos; } - private int writeSkeletonMethodParameterDeclaration(DebugContext context, DebugLocalInfo paramInfo, boolean artificial, byte[] buffer, + private int writeSkeletonMethodParameterDeclaration(DebugContext context, LocalEntry paramInfo, boolean artificial, byte[] buffer, int p) { int pos = p; log(context, " [0x%08x] method parameter declaration", pos); AbbrevCode abbrevCode; - TypeEntry paramType = lookupType(paramInfo.valueType()); + TypeEntry paramType = paramInfo.type(); if (artificial) { abbrevCode = AbbrevCode.METHOD_PARAMETER_DECLARATION_4; } else { @@ -1192,29 +1361,37 @@ private int writeMethodDeclarations(DebugContext context, ClassEntry classEntry, for (MethodEntry method : classEntry.getMethods()) { if (method.isInRange() || method.isInlined()) { /* - * Declare all methods whether or not they have been compiled or inlined. + * Declare all methods whether they have been compiled or inlined. */ - pos = writeMethodDeclaration(context, classEntry, method, buffer, pos); + pos = writeMethodDeclaration(context, classEntry, method, false, buffer, pos); } } return pos; } - private int writeMethodDeclaration(DebugContext context, ClassEntry classEntry, MethodEntry method, byte[] buffer, int p) { + private int writeMethodDeclaration(DebugContext context, ClassEntry classEntry, MethodEntry method, boolean isInlined, byte[] buffer, int p) { int pos = p; - String methodKey = method.getSymbolName(); - String linkageName = uniqueDebugString(methodKey); + String linkageName = uniqueDebugString(method.getSymbolName()); setMethodDeclarationIndex(method, pos); int modifiers = method.getModifiers(); boolean isStatic = Modifier.isStatic(modifiers); - log(context, " [0x%08x] method declaration %s::%s", pos, classEntry.getTypeName(), method.methodName()); - AbbrevCode abbrevCode = (isStatic ? AbbrevCode.METHOD_DECLARATION_STATIC : AbbrevCode.METHOD_DECLARATION); + log(context, " [0x%08x] method declaration %s::%s", pos, classEntry.getTypeName(), method.getMethodName()); + AbbrevCode abbrevCode; + if (isInlined) { + abbrevCode = (isStatic ? AbbrevCode.METHOD_DECLARATION_INLINE_STATIC : AbbrevCode.METHOD_DECLARATION_INLINE); + } else { + abbrevCode = (isStatic ? AbbrevCode.METHOD_DECLARATION_STATIC : AbbrevCode.METHOD_DECLARATION); + } log(context, " [0x%08x] <2> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); + if (isInlined) { + log(context, " [0x%08x] inline 0x%x", pos, DwarfInline.DW_INL_inlined.value()); + pos = writeAttrInline(DwarfInline.DW_INL_inlined, buffer, pos); + } log(context, " [0x%08x] external true", pos); pos = writeFlag(DwarfFlag.DW_FLAG_true, buffer, pos); - String name = uniqueDebugString(method.methodName()); + String name = uniqueDebugString(method.getMethodName()); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); pos = writeStrSectionOffset(name, buffer, pos); FileEntry fileEntry = method.getFileEntry(); @@ -1243,7 +1420,7 @@ private int writeMethodDeclaration(DebugContext context, ClassEntry classEntry, long typeSignature = classEntry.getLayoutTypeSignature(); log(context, " [0x%08x] containing_type 0x%x (%s)", pos, typeSignature, classEntry.getTypeName()); pos = writeTypeSignature(typeSignature, buffer, pos); - if (abbrevCode == AbbrevCode.METHOD_DECLARATION) { + if (abbrevCode == AbbrevCode.METHOD_DECLARATION | abbrevCode == AbbrevCode.METHOD_DECLARATION_INLINE) { /* Record the current position so we can back patch the object pointer. */ int objectPointerIndex = pos; /* @@ -1268,30 +1445,26 @@ private int writeMethodDeclaration(DebugContext context, ClassEntry classEntry, private int writeMethodParameterDeclarations(DebugContext context, ClassEntry classEntry, MethodEntry method, int fileIdx, int level, byte[] buffer, int p) { int pos = p; - int refAddr; if (!Modifier.isStatic(method.getModifiers())) { - refAddr = pos; - DebugLocalInfo paramInfo = method.getThisParam(); - setMethodLocalIndex(classEntry, method, paramInfo, refAddr); + LocalEntry paramInfo = method.getThisParam(); + setMethodLocalIndex(classEntry, method, paramInfo, pos); pos = writeMethodParameterDeclaration(context, paramInfo, fileIdx, true, level, buffer, pos); } - for (int i = 0; i < method.getParamCount(); i++) { - refAddr = pos; - DebugLocalInfo paramInfo = method.getParam(i); - setMethodLocalIndex(classEntry, method, paramInfo, refAddr); + for (LocalEntry paramInfo : method.getParams()) { + setMethodLocalIndex(classEntry, method, paramInfo, pos); pos = writeMethodParameterDeclaration(context, paramInfo, fileIdx, false, level, buffer, pos); } return pos; } - private int writeMethodParameterDeclaration(DebugContext context, DebugLocalInfo paramInfo, int fileIdx, boolean artificial, int level, byte[] buffer, + private int writeMethodParameterDeclaration(DebugContext context, LocalEntry paramInfo, int fileIdx, boolean artificial, int level, byte[] buffer, int p) { int pos = p; log(context, " [0x%08x] method parameter declaration", pos); AbbrevCode abbrevCode; - String paramName = paramInfo.name(); - TypeEntry paramType = lookupType(paramInfo.valueType()); - int line = paramInfo.line(); + String paramName = uniqueDebugString(paramInfo.name()); + TypeEntry paramType = paramInfo.type(); + int line = paramInfo.getLine(); if (artificial) { abbrevCode = AbbrevCode.METHOD_PARAMETER_DECLARATION_1; } else if (line >= 0) { @@ -1302,7 +1475,7 @@ private int writeMethodParameterDeclaration(DebugContext context, DebugLocalInfo log(context, " [0x%08x] <%d> Abbrev Number %d", pos, level, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); log(context, " [0x%08x] name %s", pos, paramName); - pos = writeStrSectionOffset(uniqueDebugString(paramName), buffer, pos); + pos = writeStrSectionOffset(paramName, buffer, pos); if (abbrevCode == AbbrevCode.METHOD_PARAMETER_DECLARATION_2) { log(context, " [0x%08x] file 0x%x", pos, fileIdx); pos = writeAttrData2((short) fileIdx, buffer, pos); @@ -1323,24 +1496,21 @@ private int writeMethodParameterDeclaration(DebugContext context, DebugLocalInfo private int writeMethodLocalDeclarations(DebugContext context, ClassEntry classEntry, MethodEntry method, int fileIdx, int level, byte[] buffer, int p) { int pos = p; - int refAddr; - for (int i = 0; i < method.getLocalCount(); i++) { - refAddr = pos; - DebugLocalInfo localInfo = method.getLocal(i); - setMethodLocalIndex(classEntry, method, localInfo, refAddr); - pos = writeMethodLocalDeclaration(context, localInfo, fileIdx, level, buffer, pos); + for (LocalEntry local : method.getLocals()) { + setMethodLocalIndex(classEntry, method, local, pos); + pos = writeMethodLocalDeclaration(context, local, fileIdx, level, buffer, pos); } return pos; } - private int writeMethodLocalDeclaration(DebugContext context, DebugLocalInfo paramInfo, int fileIdx, int level, byte[] buffer, + private int writeMethodLocalDeclaration(DebugContext context, LocalEntry paramInfo, int fileIdx, int level, byte[] buffer, int p) { int pos = p; log(context, " [0x%08x] method local declaration", pos); AbbrevCode abbrevCode; - String paramName = paramInfo.name(); - TypeEntry paramType = lookupType(paramInfo.valueType()); - int line = paramInfo.line(); + String paramName = uniqueDebugString(paramInfo.name()); + TypeEntry paramType = paramInfo.type(); + int line = paramInfo.getLine(); if (line >= 0) { abbrevCode = AbbrevCode.METHOD_LOCAL_DECLARATION_1; } else { @@ -1349,7 +1519,7 @@ private int writeMethodLocalDeclaration(DebugContext context, DebugLocalInfo par log(context, " [0x%08x] <%d> Abbrev Number %d", pos, level, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); log(context, " [0x%08x] name %s", pos, paramName); - pos = writeStrSectionOffset(uniqueDebugString(paramName), buffer, pos); + pos = writeStrSectionOffset(paramName, buffer, pos); if (abbrevCode == AbbrevCode.METHOD_LOCAL_DECLARATION_1) { log(context, " [0x%08x] file 0x%x", pos, fileIdx); pos = writeAttrData2((short) fileIdx, buffer, pos); @@ -1365,7 +1535,7 @@ private int writeMethodLocalDeclaration(DebugContext context, DebugLocalInfo par } private int writeInterfaceImplementors(DebugContext context, InterfaceClassEntry interfaceClassEntry, byte[] buffer, int p) { - return interfaceClassEntry.implementors().reduce(p, + return interfaceClassEntry.getImplementors().stream().reduce(p, (pos, classEntry) -> writeInterfaceImplementor(context, classEntry, buffer, pos), (oldPos, newPos) -> newPos); } @@ -1388,128 +1558,13 @@ private int writeInterfaceImplementor(DebugContext context, ClassEntry classEntr 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()); - AbbrevCode abbrevCode = AbbrevCode.FOREIGN_STRUCT; - log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); - pos = writeAbbrevCode(abbrevCode, buffer, pos); - String typedefName = foreignTypeEntry.getTypedefName(); - 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) { - long typeSignature = parent.getLayoutTypeSignature(); - pos = writeSuperReference(context, typeSignature, 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. */ - AbbrevCode abbrevCode = AbbrevCode.PRIMITIVE_TYPE; - log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); - 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 - DwarfEncoding encoding = (isSigned ? DwarfEncoding.DW_ATE_signed : DwarfEncoding.DW_ATE_unsigned); - log(context, " [0x%08x] encoding 0x%x", pos, encoding.value()); - pos = writeAttrEncoding(encoding, buffer, pos); - String name = uniqueDebugString(integralTypeName(byteSize, isSigned)); - 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. */ - AbbrevCode abbrevCode = AbbrevCode.PRIMITIVE_TYPE; - log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); - 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 - DwarfEncoding encoding = (isSigned ? DwarfEncoding.DW_ATE_signed : DwarfEncoding.DW_ATE_unsigned); - log(context, " [0x%08x] encoding 0x%x", pos, encoding.value()); - pos = writeAttrEncoding(encoding, buffer, pos); - String name = uniqueDebugString(integralTypeName(byteSize, isSigned)); - 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. */ - AbbrevCode abbrevCode = AbbrevCode.PRIMITIVE_TYPE; - log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); - 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 - DwarfEncoding encoding = DwarfEncoding.DW_ATE_float; - log(context, " [0x%08x] encoding 0x%x", pos, encoding.value()); - pos = writeAttrEncoding(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 writeStaticFieldLocations(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { /* * Only write locations for static fields that have an offset greater than 0. A negative * offset indicates that the field has been folded into code as an unmaterialized constant. */ Cursor cursor = new Cursor(p); - classEntry.fields().filter(DwarfInfoSectionImpl::isManifestedStaticField) + classEntry.getFields().stream().filter(DwarfInfoSectionImpl::isManifestedStaticField) .forEach(fieldEntry -> { cursor.set(writeClassStaticFieldLocation(context, classEntry, fieldEntry, buffer, cursor.get())); }); @@ -1522,7 +1577,7 @@ private int writeStaticFieldDeclarations(DebugContext context, ClassEntry classE * offset indicates that the field has been folded into code as an unmaterialized constant. */ Cursor cursor = new Cursor(p); - classEntry.fields().filter(DwarfInfoSectionImpl::isManifestedStaticField) + classEntry.getFields().stream().filter(DwarfInfoSectionImpl::isManifestedStaticField) .forEach(fieldEntry -> { cursor.set(writeClassStaticFieldDeclaration(context, classEntry, fieldEntry, buffer, cursor.get())); }); @@ -1550,7 +1605,7 @@ private int writeClassStaticFieldDeclaration(DebugContext context, ClassEntry cl log(context, " [0x%08x] <2> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); - String name = fieldEntry.fieldName(); + String name = uniqueDebugString(fieldEntry.fieldName()); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); pos = writeStrSectionOffset(name, buffer, pos); /* We may not have a file and line for a field. */ @@ -1589,7 +1644,7 @@ private int writeClassStaticFieldLocation(DebugContext context, ClassEntry class pos = writeAttrRef4(fieldDefinitionOffset, buffer, pos); /* Field offset needs to be relocated relative to static primitive or static object base. */ int offset = fieldEntry.getOffset(); - log(context, " [0x%08x] location heapbase + 0x%x (%s)", pos, offset, (fieldEntry.getValueType().isPrimitive() ? "primitive" : "object")); + log(context, " [0x%08x] location heapbase + 0x%x (%s)", pos, offset, (fieldEntry.getValueType() instanceof PrimitiveTypeEntry ? "primitive" : "object")); pos = writeHeapLocationExprLoc(offset, buffer, pos); return pos; } @@ -1618,7 +1673,7 @@ private int writeArrayLayoutTypeUnit(DebugContext context, ArrayTypeEntry arrayT AbbrevCode abbrevCode = AbbrevCode.ARRAY_LAYOUT; log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); - String name = arrayTypeEntry.getTypeName(); + String name = uniqueDebugString(arrayTypeEntry.getTypeName()); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); pos = writeStrSectionOffset(name, buffer, pos); log(context, " [0x%08x] byte_size 0x%x", pos, size); @@ -1673,7 +1728,7 @@ private int writeArray(DebugContext context, ArrayTypeEntry arrayTypeEntry, byte String name = uniqueDebugString("JAVA"); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); pos = writeStrSectionOffset(name, buffer, pos); - String compilationDirectory = dwarfSections.getCachePath(); + String compilationDirectory = uniqueDebugString(dwarfSections.getCachePath()); log(context, " [0x%08x] comp_dir 0x%x (%s)", pos, debugStringIndex(compilationDirectory), compilationDirectory); pos = writeStrSectionOffset(compilationDirectory, buffer, pos); @@ -1708,7 +1763,7 @@ private int writeSkeletonArrayLayout(DebugContext context, ArrayTypeEntry arrayT AbbrevCode abbrevCode = AbbrevCode.CLASS_LAYOUT_ARRAY; log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); - String name = arrayTypeEntry.getTypeName(); + String name = uniqueDebugString(arrayTypeEntry.getTypeName()); log(context, " [0x%08x] name 0x%x (%s)", pos, debugStringIndex(name), name); pos = writeStrSectionOffset(name, buffer, pos); log(context, " [0x%08x] declaration true", pos); @@ -1724,7 +1779,7 @@ private int writeSkeletonArrayLayout(DebugContext context, ArrayTypeEntry arrayT private int writeFields(DebugContext context, ArrayTypeEntry arrayTypeEntry, byte[] buffer, int p) { Cursor cursor = new Cursor(p); - arrayTypeEntry.fields().filter(DwarfInfoSectionImpl::isManifestedField) + arrayTypeEntry.getFields().stream().filter(DwarfInfoSectionImpl::isManifestedField) .forEach(fieldEntry -> { cursor.set(writeField(context, arrayTypeEntry, fieldEntry, buffer, cursor.get())); }); @@ -1746,7 +1801,7 @@ 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) { + private int writeEmbeddedArrayDataType(DebugContext context, TypeEntry valueType, int valueSize, int arraySize, byte[] buffer, int p) { int pos = p; log(context, " [0x%08x] embedded array element data type", pos); AbbrevCode abbrevCode = AbbrevCode.ARRAY_DATA_TYPE_2; @@ -1756,10 +1811,10 @@ private int writeEmbeddedArrayDataType(DebugContext context, ForeignTypeEntry fo int size = arraySize * valueSize; log(context, " [0x%08x] byte_size 0x%x", pos, size); pos = writeAttrData4(size, buffer, pos); - String elementTypeName = foreignValueType.getTypeName(); + String elementTypeName = valueType.getTypeName(); long elementTypeSignature; - if (foreignValueType.isPointer()) { - TypeEntry pointerTo = foreignValueType.getPointerTo(); + if (valueType instanceof PointerToTypeEntry pointerTypeEntry) { + TypeEntry pointerTo = pointerTypeEntry.getPointerTo(); assert pointerTo != null : "ADDRESS field pointer type must have a known target type"; // type the array using the referent of the pointer type // @@ -1771,9 +1826,12 @@ private int writeEmbeddedArrayDataType(DebugContext context, ForeignTypeEntry fo // 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. elementTypeSignature = pointerTo.getTypeSignature(); - } else { + } else if (valueType instanceof ForeignStructTypeEntry foreignStructTypeEntry) { // type the array using the layout type - elementTypeSignature = foreignValueType.getLayoutTypeSignature(); + elementTypeSignature = foreignStructTypeEntry.getLayoutTypeSignature(); + } else { + // otherwise just use the value type + elementTypeSignature = valueType.getTypeSignature(); } log(context, " [0x%08x] type idx 0x%x (%s)", pos, elementTypeSignature, elementTypeName); pos = writeTypeSignature(elementTypeSignature, buffer, pos); @@ -1811,7 +1869,7 @@ private int writeArrayElementField(DebugContext context, int offset, int arrayDa private int writeMethodLocations(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { Cursor cursor = new Cursor(p); - classEntry.compiledEntries().forEach(compiledMethodEntry -> { + classEntry.compiledMethods().forEach(compiledMethodEntry -> { cursor.set(writeMethodLocation(context, classEntry, compiledMethodEntry, buffer, cursor.get())); }); return cursor.get(); @@ -1819,7 +1877,7 @@ private int writeMethodLocations(DebugContext context, ClassEntry classEntry, by private int writeMethodLocation(DebugContext context, ClassEntry classEntry, CompiledMethodEntry compiledEntry, byte[] buffer, int p) { int pos = p; - Range primary = compiledEntry.getPrimary(); + Range primary = compiledEntry.primary(); log(context, " [0x%08x] method location", pos); AbbrevCode abbrevCode = AbbrevCode.METHOD_LOCATION; log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); @@ -1837,9 +1895,8 @@ private int writeMethodLocation(DebugContext context, ClassEntry classEntry, Com int methodSpecOffset = getMethodDeclarationIndex(primary.getMethodEntry()); log(context, " [0x%08x] specification 0x%x (%s)", pos, methodSpecOffset, methodKey); pos = writeInfoSectionOffset(methodSpecOffset, buffer, pos); - HashMap> varRangeMap = primary.getVarRangeMap(); - pos = writeMethodParameterLocations(context, classEntry, varRangeMap, primary, 2, buffer, pos); - pos = writeMethodLocalLocations(context, classEntry, varRangeMap, primary, 2, buffer, pos); + pos = writeMethodParameterLocations(context, classEntry, primary, 2, buffer, pos); + pos = writeMethodLocalLocations(context, classEntry, primary, 2, buffer, pos); if (primary.includesInlineRanges()) { /* * the method has inlined ranges so write concrete inlined method entries as its @@ -1853,80 +1910,54 @@ private int writeMethodLocation(DebugContext context, ClassEntry classEntry, Com return writeAttrNull(buffer, pos); } - private int writeMethodParameterLocations(DebugContext context, ClassEntry classEntry, HashMap> varRangeMap, Range range, int depth, byte[] buffer, int p) { + private int writeMethodParameterLocations(DebugContext context, ClassEntry classEntry, Range range, int depth, byte[] buffer, int p) { int pos = p; MethodEntry methodEntry; if (range.isPrimary()) { methodEntry = range.getMethodEntry(); } else { assert !range.isLeaf() : "should only be looking up var ranges for inlined calls"; - methodEntry = range.getFirstCallee().getMethodEntry(); + methodEntry = range.getCallees().getFirst().getMethodEntry(); } if (!Modifier.isStatic(methodEntry.getModifiers())) { - DebugLocalInfo thisParamInfo = methodEntry.getThisParam(); + LocalEntry thisParamInfo = methodEntry.getThisParam(); int refAddr = getMethodLocalIndex(classEntry, methodEntry, thisParamInfo); - List ranges = varRangeMap.get(thisParamInfo); - pos = writeMethodLocalLocation(context, range, thisParamInfo, refAddr, ranges, depth, true, buffer, pos); + pos = writeMethodLocalLocation(context, range, thisParamInfo, refAddr, depth, true, buffer, pos); } - for (int i = 0; i < methodEntry.getParamCount(); i++) { - DebugLocalInfo paramInfo = methodEntry.getParam(i); + for (LocalEntry paramInfo : methodEntry.getParams()) { int refAddr = getMethodLocalIndex(classEntry, methodEntry, paramInfo); - List ranges = varRangeMap.get(paramInfo); - pos = writeMethodLocalLocation(context, range, paramInfo, refAddr, ranges, depth, true, buffer, pos); + pos = writeMethodLocalLocation(context, range, paramInfo, refAddr, depth, true, buffer, pos); } return pos; } - private int writeMethodLocalLocations(DebugContext context, ClassEntry classEntry, HashMap> varRangeMap, Range range, int depth, byte[] buffer, int p) { + private int writeMethodLocalLocations(DebugContext context, ClassEntry classEntry, Range range, int depth, byte[] buffer, int p) { int pos = p; MethodEntry methodEntry; if (range.isPrimary()) { methodEntry = range.getMethodEntry(); } else { assert !range.isLeaf() : "should only be looking up var ranges for inlined calls"; - methodEntry = range.getFirstCallee().getMethodEntry(); + methodEntry = range.getCallees().getFirst().getMethodEntry(); } - int count = methodEntry.getLocalCount(); - for (int i = 0; i < count; i++) { - DebugLocalInfo localInfo = methodEntry.getLocal(i); - int refAddr = getMethodLocalIndex(classEntry, methodEntry, localInfo); - List ranges = varRangeMap.get(localInfo); - pos = writeMethodLocalLocation(context, range, localInfo, refAddr, ranges, depth, false, buffer, pos); + + for (LocalEntry local : methodEntry.getLocals()) { + int refAddr = getMethodLocalIndex(classEntry, methodEntry, local); + pos = writeMethodLocalLocation(context, range, local, refAddr, depth, false, buffer, pos); } return pos; } - private int writeMethodLocalLocation(DebugContext context, Range range, DebugLocalInfo localInfo, int refAddr, List ranges, int depth, boolean isParam, byte[] buffer, + private int writeMethodLocalLocation(DebugContext context, Range range, LocalEntry localInfo, int refAddr, int depth, boolean isParam, byte[] buffer, int p) { int pos = p; - log(context, " [0x%08x] method %s location %s:%s", pos, (isParam ? "parameter" : "local"), localInfo.name(), localInfo.typeName()); - List localValues = new ArrayList<>(); - for (SubRange subrange : ranges) { - DebugLocalValueInfo value = subrange.lookupValue(localInfo); - if (value != null) { - log(context, " [0x%08x] local %s:%s [0x%x, 0x%x] = %s", pos, value.name(), value.typeName(), subrange.getLo(), subrange.getHi(), formatValue(value)); - switch (value.localKind()) { - case REGISTER: - case STACKSLOT: - localValues.add(value); - break; - case CONSTANT: - JavaConstant constant = value.constantValue(); - // can only handle primitive or null constants just now - if (constant instanceof PrimitiveConstant || constant.getJavaKind() == JavaKind.Object) { - localValues.add(value); - } - break; - default: - break; - } - } - } + log(context, " [0x%08x] method %s location %s:%s", pos, (isParam ? "parameter" : "local"), localInfo.name(), localInfo.type().getTypeName()); + AbbrevCode abbrevCode; - if (localValues.isEmpty()) { - abbrevCode = (isParam ? AbbrevCode.METHOD_PARAMETER_LOCATION_1 : AbbrevCode.METHOD_LOCAL_LOCATION_1); - } else { + if (range.hasLocalValues(localInfo)) { abbrevCode = (isParam ? AbbrevCode.METHOD_PARAMETER_LOCATION_2 : AbbrevCode.METHOD_LOCAL_LOCATION_2); + } else { + abbrevCode = (isParam ? AbbrevCode.METHOD_PARAMETER_LOCATION_1 : AbbrevCode.METHOD_LOCAL_LOCATION_1); } log(context, " [0x%08x] <%d> Abbrev Number %d", pos, depth, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); @@ -1945,20 +1976,14 @@ private int writeMethodLocalLocation(DebugContext context, Range range, DebugLoc * Go through the subranges and generate concrete debug entries for inlined methods. */ private int generateConcreteInlinedMethods(DebugContext context, ClassEntry classEntry, CompiledMethodEntry compiledEntry, byte[] buffer, int p) { - Range primary = compiledEntry.getPrimary(); + Range primary = compiledEntry.primary(); if (primary.isLeaf()) { return p; } int pos = p; log(context, " [0x%08x] concrete entries [0x%x,0x%x] %s", pos, primary.getLo(), primary.getHi(), primary.getFullMethodName()); int depth = 0; - Iterator iterator = compiledEntry.topDownRangeIterator(); - while (iterator.hasNext()) { - SubRange subrange = iterator.next(); - if (subrange.isLeaf()) { - // we only generate concrete methods for non-leaf entries - continue; - } + for (Range subrange : compiledEntry.callRangeStream().toList()) { // if we just stepped out of a child range write nulls for each step up while (depth > subrange.getDepth()) { pos = writeAttrNull(buffer, pos); @@ -1966,11 +1991,10 @@ private int generateConcreteInlinedMethods(DebugContext context, ClassEntry clas } depth = subrange.getDepth(); pos = writeInlineSubroutine(context, classEntry, subrange, depth + 2, buffer, pos); - HashMap> varRangeMap = subrange.getVarRangeMap(); // increment depth to account for parameter and method locations depth++; - pos = writeMethodParameterLocations(context, classEntry, varRangeMap, subrange, depth + 2, buffer, pos); - pos = writeMethodLocalLocations(context, classEntry, varRangeMap, subrange, depth + 2, buffer, pos); + pos = writeMethodParameterLocations(context, classEntry, subrange, depth + 2, buffer, pos); + pos = writeMethodLocalLocations(context, classEntry, subrange, depth + 2, buffer, pos); } // if we just stepped out of a child range write nulls for each step up while (depth > 0) { @@ -1980,17 +2004,22 @@ private int generateConcreteInlinedMethods(DebugContext context, ClassEntry clas return pos; } - private int writeInlineSubroutine(DebugContext context, ClassEntry classEntry, SubRange caller, int depth, byte[] buffer, int p) { + private int writeInlineSubroutine(DebugContext context, ClassEntry classEntry, Range caller, int depth, byte[] buffer, int p) { assert !caller.isLeaf(); // the supplied range covers an inline call and references the caller method entry. its // child ranges all reference the same inlined called method. leaf children cover code for // that inlined method. non-leaf children cover code for recursively inlined methods. // identify the inlined method by looking at the first callee - Range callee = caller.getFirstCallee(); + Range callee = caller.getCallees().getFirst(); MethodEntry methodEntry = callee.getMethodEntry(); String methodKey = methodEntry.getSymbolName(); /* the abstract index was written in the method's class entry */ - int abstractOriginIndex = (classEntry == methodEntry.ownerType() ? getMethodDeclarationIndex(methodEntry) : getAbstractInlineMethodIndex(classEntry, methodEntry)); + int abstractOriginIndex; + if (classEntry == methodEntry.getOwnerType() && !dwarfSections.isRuntimeCompilation()) { + abstractOriginIndex = getMethodDeclarationIndex(methodEntry); + } else { + abstractOriginIndex = getAbstractInlineMethodIndex(classEntry, methodEntry); + } int pos = p; log(context, " [0x%08x] concrete inline subroutine [0x%x, 0x%x] %s", pos, caller.getLo(), caller.getHi(), methodKey); @@ -2012,7 +2041,7 @@ private int writeInlineSubroutine(DebugContext context, ClassEntry classEntry, S fileIndex = classEntry.getFileIdx(); } } - final AbbrevCode abbrevCode = AbbrevCode.INLINED_SUBROUTINE_WITH_CHILDREN; + final AbbrevCode abbrevCode = AbbrevCode.INLINED_SUBROUTINE; log(context, " [0x%08x] <%d> Abbrev Number %d", pos, depth, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); log(context, " [0x%08x] abstract_origin 0x%x", pos, abstractOriginIndex); @@ -2029,41 +2058,39 @@ private int writeInlineSubroutine(DebugContext context, ClassEntry classEntry, S } private int writeAbstractInlineMethods(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { - EconomicSet inlinedMethods = collectInlinedMethods(context, classEntry, p); + HashSet inlinedMethods = collectInlinedMethods(context, classEntry, p); int pos = p; for (MethodEntry methodEntry : inlinedMethods) { // n.b. class entry used to index the method belongs to the inlining method // not the inlined method setAbstractInlineMethodIndex(classEntry, methodEntry, pos); - pos = writeAbstractInlineMethod(context, classEntry, methodEntry, buffer, pos); + if (dwarfSections.isRuntimeCompilation() && classEntry != methodEntry.getOwnerType()) { + pos = writeMethodDeclaration(context, classEntry, methodEntry, true, buffer, pos); + } else { + pos = writeAbstractInlineMethod(context, classEntry, methodEntry, buffer, pos); + } } return pos; } - private EconomicSet collectInlinedMethods(DebugContext context, ClassEntry classEntry, int p) { - final EconomicSet methods = EconomicSet.create(); - classEntry.compiledEntries().forEach(compiledEntry -> addInlinedMethods(context, compiledEntry, compiledEntry.getPrimary(), methods, p)); + private HashSet collectInlinedMethods(DebugContext context, ClassEntry classEntry, int p) { + final HashSet methods = new HashSet<>(); + classEntry.compiledMethods().forEach(compiledMethod -> addInlinedMethods(context, compiledMethod, compiledMethod.primary(), methods, p)); return methods; } - private void addInlinedMethods(DebugContext context, CompiledMethodEntry compiledEntry, Range primary, EconomicSet hashSet, int p) { + private void addInlinedMethods(DebugContext context, CompiledMethodEntry compiledEntry, Range primary, HashSet hashSet, int p) { if (primary.isLeaf()) { return; } verboseLog(context, " [0x%08x] collect abstract inlined methods %s", p, primary.getFullMethodName()); - Iterator iterator = compiledEntry.topDownRangeIterator(); - while (iterator.hasNext()) { - SubRange subrange = iterator.next(); - if (subrange.isLeaf()) { - // we only generate abstract inline methods for non-leaf entries - continue; - } + for (Range subrange : compiledEntry.callRangeStream().toList()) { // the subrange covers an inline call and references the caller method entry. its // child ranges all reference the same inlined called method. leaf children cover code // for // that inlined method. non-leaf children cover code for recursively inlined methods. // identify the inlined method by looking at the first callee - Range callee = subrange.getFirstCallee(); + Range callee = subrange.getCallees().getFirst(); MethodEntry methodEntry = callee.getMethodEntry(); if (hashSet.add(methodEntry)) { verboseLog(context, " [0x%08x] add abstract inlined method %s", p, methodEntry.getSymbolName()); @@ -2073,7 +2100,7 @@ private void addInlinedMethods(DebugContext context, CompiledMethodEntry compile private int writeAbstractInlineMethod(DebugContext context, ClassEntry classEntry, MethodEntry method, byte[] buffer, int p) { int pos = p; - log(context, " [0x%08x] abstract inline method %s::%s", pos, classEntry.getTypeName(), method.methodName()); + log(context, " [0x%08x] abstract inline method %s::%s", pos, classEntry.getTypeName(), method.getMethodName()); AbbrevCode abbrevCode = AbbrevCode.ABSTRACT_INLINE_METHOD; log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode.ordinal()); pos = writeAbbrevCode(abbrevCode, buffer, pos); @@ -2091,7 +2118,7 @@ private int writeAbstractInlineMethod(DebugContext context, ClassEntry classEntr * If the inline method exists in a different CU then write locals and params otherwise we * can just reuse the locals and params in the declaration */ - if (classEntry != method.ownerType()) { + if (classEntry != method.getOwnerType()) { FileEntry fileEntry = method.getFileEntry(); if (fileEntry == null) { fileEntry = classEntry.getFileEntry(); diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLineSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLineSectionImpl.java index ae1071c2e700..039c65becf7d 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLineSectionImpl.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLineSectionImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,22 +26,17 @@ package com.oracle.objectfile.elf.dwarf; -import java.util.Iterator; -import java.util.Map; - import com.oracle.objectfile.debugentry.ClassEntry; +import com.oracle.objectfile.debugentry.CompiledMethodEntry; +import com.oracle.objectfile.debugentry.FileEntry; +import com.oracle.objectfile.debugentry.range.Range; +import com.oracle.objectfile.elf.dwarf.constants.DwarfForm; +import com.oracle.objectfile.elf.dwarf.constants.DwarfLineNumberHeaderEntry; import com.oracle.objectfile.elf.dwarf.constants.DwarfLineOpcode; import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName; import com.oracle.objectfile.elf.dwarf.constants.DwarfVersion; -import jdk.graal.compiler.debug.DebugContext; -import com.oracle.objectfile.LayoutDecision; -import com.oracle.objectfile.LayoutDecisionMap; -import com.oracle.objectfile.ObjectFile; -import com.oracle.objectfile.debugentry.CompiledMethodEntry; -import com.oracle.objectfile.debugentry.FileEntry; -import com.oracle.objectfile.debugentry.range.Range; -import com.oracle.objectfile.debugentry.range.SubRange; +import jdk.graal.compiler.debug.DebugContext; /** * Section generator for debug_line section. @@ -55,7 +50,7 @@ public class DwarfLineSectionImpl extends DwarfSectionImpl { /** * Line header section always contains fixed number of bytes. */ - private static final int LN_HEADER_SIZE = 28; + private static final int LN_HEADER_SIZE = 30; /** * Current generator follows C++ with line base -5. */ @@ -84,11 +79,11 @@ public void createContent() { */ Cursor byteCount = new Cursor(); - instanceClassStream().filter(ClassEntry::hasCompiledEntries).forEachOrdered(classEntry -> { + instanceClassWithCompilationStream().forEachOrdered(classEntry -> { setLineIndex(classEntry, byteCount.get()); int headerSize = headerSize(); - int dirTableSize = computeDirTableSize(classEntry); - int fileTableSize = computeFileTableSize(classEntry); + int dirTableSize = writeDirTable(null, classEntry, null, 0); + int fileTableSize = writeFileTable(null, classEntry, null, 0); int prologueSize = headerSize + dirTableSize + fileTableSize; setLinePrologueSize(classEntry, prologueSize); // mark the start of the line table for this entry @@ -110,6 +105,10 @@ private static int headerSize() { * *

  • uint16 version * + *
  • uint8 address_size + * + *
  • uint8 segment_selector_size + * *
  • uint32 header_length * *
  • uint8 min_insn_length @@ -132,53 +131,6 @@ private static int headerSize() { return LN_HEADER_SIZE; } - private int computeDirTableSize(ClassEntry classEntry) { - /* - * Table contains a sequence of 'nul'-terminated UTF8 dir name bytes followed by an extra - * 'nul'. - */ - Cursor cursor = new Cursor(); - classEntry.dirStream().forEachOrdered(dirEntry -> { - int length = countUTF8Bytes(dirEntry.getPathString()); - // We should never have a null or zero length entry in local dirs - assert length > 0; - cursor.add(length + 1); - }); - /* - * Allow for terminator nul. - */ - cursor.add(1); - return cursor.get(); - } - - private int computeFileTableSize(ClassEntry classEntry) { - /* - * Table contains a sequence of file entries followed by an extra 'nul' - * - * each file entry consists of a 'nul'-terminated UTF8 file name, a dir entry idx and two 0 - * time stamps - */ - Cursor cursor = new Cursor(); - classEntry.fileStream().forEachOrdered(fileEntry -> { - // We want the file base name excluding path. - String baseName = fileEntry.getFileName(); - int length = countUTF8Bytes(baseName); - // We should never have a null or zero length entry in local files. - assert length > 0; - cursor.add(length + 1); - // The dir index gets written as a ULEB - int dirIdx = classEntry.getDirIdx(fileEntry); - cursor.add(writeULEB(dirIdx, scratch, 0)); - // The two zero timestamps require 1 byte each - cursor.add(2); - }); - /* - * Allow for terminator nul. - */ - cursor.add(1); - return cursor.get(); - } - private int computeLineNumberTableSize(ClassEntry classEntry) { /* * Sigh -- we have to do this by generating the content even though we cannot write it into @@ -187,23 +139,6 @@ private int computeLineNumberTableSize(ClassEntry classEntry) { return writeLineNumberTable(null, classEntry, null, 0); } - @Override - public byte[] getOrDecideContent(Map alreadyDecided, byte[] contentHint) { - ObjectFile.Element textElement = getElement().getOwner().elementForName(".text"); - LayoutDecisionMap decisionMap = alreadyDecided.get(textElement); - if (decisionMap != null) { - Object valueObj = decisionMap.getDecidedValue(LayoutDecision.Kind.VADDR); - if (valueObj != null && valueObj instanceof Number) { - /* - * This may not be the final vaddr for the text segment but it will be close enough - * to make debug easier i.e. to within a 4k page or two. - */ - debugTextBase = ((Number) valueObj).longValue(); - } - } - return super.getOrDecideContent(alreadyDecided, contentHint); - } - @Override public void writeContent(DebugContext context) { assert contentByteArrayCreated(); @@ -211,9 +146,9 @@ public void writeContent(DebugContext context) { byte[] buffer = getContent(); Cursor cursor = new Cursor(); - enableLog(context, cursor.get()); + enableLog(context); log(context, " [0x%08x] DEBUG_LINE", cursor.get()); - instanceClassStream().filter(ClassEntry::hasCompiledEntries).forEachOrdered(classEntry -> { + instanceClassWithCompilationStream().forEachOrdered(classEntry -> { int pos = cursor.get(); setLineIndex(classEntry, pos); int lengthPos = pos; @@ -242,14 +177,25 @@ private int writeHeader(ClassEntry classEntry, byte[] buffer, int p) { */ pos = writeInt(0, buffer, pos); /* - * 2 ubyte version is always 2. + * 2 ubyte version is always 5. + */ + pos = writeDwarfVersion(DwarfVersion.DW_VERSION_5, buffer, pos); + /* + * 1 ubyte address size field. */ - pos = writeDwarfVersion(DwarfVersion.DW_VERSION_4, buffer, pos); + pos = writeByte((byte) 8, buffer, pos); + /* + * 1 ubyte segment selector size field. + */ + pos = writeByte((byte) 0, buffer, pos); + /* - * 4 ubyte prologue length includes rest of header and dir + file table section. + * TODO: fix this 4 ubyte prologue length includes rest of header and dir + file table + * section. */ - int prologueSize = getLinePrologueSize(classEntry) - (4 + 2 + 4); + int prologueSize = getLinePrologueSize(classEntry) - (4 + 2 + 1 + 1 + 4); pos = writeInt(prologueSize, buffer, pos); + /* * 1 ubyte min instruction length is always 1. */ @@ -305,52 +251,93 @@ private int writeHeader(ClassEntry classEntry, byte[] buffer, int p) { } private int writeDirTable(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { + int pos = p; verboseLog(context, " [0x%08x] Dir Name", p); + + /* + * 1 ubyte directory entry format count field. + */ + pos = writeByte((byte) 1, buffer, pos); + /* + * 1 ULEB128 pair for the directory entry format. + */ + pos = writeULEB(DwarfLineNumberHeaderEntry.DW_LNCT_path.value(), buffer, pos); + // DW_FORM_strp is not supported by GDB but DW_FORM_line_strp is + pos = writeULEB(DwarfForm.DW_FORM_line_strp.value(), buffer, pos); + + /* + * 1 ULEB128 for directory count. + */ + pos = writeULEB(classEntry.getDirs().size() + 1, buffer, pos); + + /* + * Write explicit 0 entry for current directory. (compilation directory) + */ + String compilationDirectory = uniqueDebugLineString(dwarfSections.getCachePath()); + pos = writeLineStrSectionOffset(compilationDirectory, buffer, pos); + /* * Write out the list of dirs */ - Cursor cursor = new Cursor(p); + Cursor cursor = new Cursor(pos); Cursor idx = new Cursor(1); - classEntry.dirStream().forEach(dirEntry -> { + classEntry.getDirs().forEach(dirEntry -> { int dirIdx = idx.get(); assert (classEntry.getDirIdx(dirEntry) == dirIdx); - String dirPath = dirEntry.getPathString(); + String dirPath = uniqueDebugLineString(dirEntry.getPathString()); verboseLog(context, " [0x%08x] %-4d %s", cursor.get(), dirIdx, dirPath); - cursor.set(writeUTF8StringBytes(dirPath, buffer, cursor.get())); + cursor.set(writeLineStrSectionOffset(dirPath, buffer, cursor.get())); idx.add(1); }); - /* - * Separate dirs from files with a nul. - */ - cursor.set(writeByte((byte) 0, buffer, cursor.get())); + return cursor.get(); } private int writeFileTable(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { + int pos = p; verboseLog(context, " [0x%08x] Entry Dir Name", p); + + /* + * 1 ubyte file name entry format count field. + */ + pos = writeByte((byte) 2, buffer, pos); + /* + * 2 ULEB128 pairs for the directory entry format. + */ + pos = writeULEB(DwarfLineNumberHeaderEntry.DW_LNCT_path.value(), buffer, pos); + // DW_FORM_strp is not supported by GDB but DW_FORM_line_strp is + pos = writeULEB(DwarfForm.DW_FORM_line_strp.value(), buffer, pos); + pos = writeULEB(DwarfLineNumberHeaderEntry.DW_LNCT_directory_index.value(), buffer, pos); + pos = writeULEB(DwarfForm.DW_FORM_udata.value(), buffer, pos); + + /* + * 1 ULEB128 for directory count. + */ + pos = writeULEB(classEntry.getFiles().size() + 1, buffer, pos); + + /* + * Write explicit 0 dummy entry. + */ + String fileName = uniqueDebugLineString(classEntry.getFileName()); + pos = writeLineStrSectionOffset(fileName, buffer, pos); + pos = writeULEB(classEntry.getDirIdx(), buffer, pos); + /* * Write out the list of files */ - Cursor cursor = new Cursor(p); + Cursor cursor = new Cursor(pos); Cursor idx = new Cursor(1); - classEntry.fileStream().forEach(fileEntry -> { - int pos = cursor.get(); + classEntry.getFiles().forEach(fileEntry -> { int fileIdx = idx.get(); assert classEntry.getFileIdx(fileEntry) == fileIdx; int dirIdx = classEntry.getDirIdx(fileEntry); - String baseName = fileEntry.getFileName(); - verboseLog(context, " [0x%08x] %-5d %-5d %s", pos, fileIdx, dirIdx, baseName); - pos = writeUTF8StringBytes(baseName, buffer, pos); - pos = writeULEB(dirIdx, buffer, pos); - pos = writeULEB(0, buffer, pos); - pos = writeULEB(0, buffer, pos); - cursor.set(pos); + String baseName = uniqueDebugLineString(fileEntry.fileName()); + verboseLog(context, " [0x%08x] %-5d %-5d %s", cursor.get(), fileIdx, dirIdx, baseName); + cursor.set(writeLineStrSectionOffset(baseName, buffer, cursor.get())); + cursor.set(writeULEB(dirIdx, buffer, cursor.get())); idx.add(1); }); - /* - * Terminate files with a nul. - */ - cursor.set(writeByte((byte) 0, buffer, cursor.get())); + return cursor.get(); } @@ -359,15 +346,15 @@ private int writeFileTable(DebugContext context, ClassEntry classEntry, byte[] b private int writeCompiledMethodLineInfo(DebugContext context, ClassEntry classEntry, CompiledMethodEntry compiledEntry, byte[] buffer, int p) { int pos = p; - Range primaryRange = compiledEntry.getPrimary(); + Range primaryRange = compiledEntry.primary(); // the compiled method might be a substitution and not in the file of the class entry FileEntry fileEntry = primaryRange.getFileEntry(); if (fileEntry == null) { - log(context, " [0x%08x] primary range [0x%08x, 0x%08x] skipped (no file) %s", pos, debugTextBase + primaryRange.getLo(), debugTextBase + primaryRange.getHi(), + log(context, " [0x%08x] primary range [0x%08x, 0x%08x] skipped (no file) %s", pos, primaryRange.getLo(), primaryRange.getHi(), primaryRange.getFullMethodNameWithParams()); return pos; } - String file = fileEntry.getFileName(); + String file = fileEntry.fileName(); int fileIdx = classEntry.getFileIdx(fileEntry); /* * Each primary represents a method i.e. a contiguous sequence of subranges. For normal @@ -396,7 +383,7 @@ private int writeCompiledMethodLineInfo(DebugContext context, ClassEntry classEn /* * Set state for primary. */ - log(context, " [0x%08x] primary range [0x%08x, 0x%08x] %s %s:%d", pos, debugTextBase + primaryRange.getLo(), debugTextBase + primaryRange.getHi(), + log(context, " [0x%08x] primary range [0x%08x, 0x%08x] %s %s:%d", pos, primaryRange.getLo(), primaryRange.getHi(), primaryRange.getFullMethodNameWithParams(), file, primaryRange.getLine()); @@ -420,27 +407,24 @@ private int writeCompiledMethodLineInfo(DebugContext context, ClassEntry classEn /* * Now write a row for each subrange lo and hi. */ - Iterator iterator = compiledEntry.leafRangeIterator(); - if (prologueRange != null) { - // skip already processed range - SubRange first = iterator.next(); - assert first == prologueRange; - } - while (iterator.hasNext()) { - SubRange subrange = iterator.next(); + + assert prologueRange == null || compiledEntry.leafRangeStream().findFirst().filter(first -> first == prologueRange).isPresent(); + + // skip already processed range + for (Range subrange : compiledEntry.leafRangeStream().skip(prologueRange != null ? 1 : 0).toList()) { assert subrange.getLo() >= primaryRange.getLo(); assert subrange.getHi() <= primaryRange.getHi(); FileEntry subFileEntry = subrange.getFileEntry(); if (subFileEntry == null) { continue; } - String subfile = subFileEntry.getFileName(); + String subfile = subFileEntry.fileName(); int subFileIdx = classEntry.getFileIdx(subFileEntry); assert subFileIdx > 0; long subLine = subrange.getLine(); long subAddressLo = subrange.getLo(); long subAddressHi = subrange.getHi(); - log(context, " [0x%08x] sub range [0x%08x, 0x%08x] %s %s:%d", pos, debugTextBase + subAddressLo, debugTextBase + subAddressHi, subrange.getFullMethodNameWithParams(), subfile, + log(context, " [0x%08x] sub range [0x%08x, 0x%08x] %s %s:%d", pos, subAddressLo, subAddressHi, subrange.getFullMethodNameWithParams(), subfile, subLine); if (subLine < 0) { /* @@ -538,6 +522,7 @@ private int writeCompiledMethodLineInfo(DebugContext context, ClassEntry classEn line += lineDelta; address += addressDelta; } + /* * Append a final end sequence just below the next primary range. */ @@ -555,10 +540,10 @@ private int writeCompiledMethodLineInfo(DebugContext context, ClassEntry classEn private int writeLineNumberTable(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { Cursor cursor = new Cursor(p); - classEntry.compiledEntries().forEachOrdered(compiledMethod -> { + classEntry.compiledMethods().forEach(compiledMethod -> { int pos = cursor.get(); - String methodName = compiledMethod.getPrimary().getFullMethodNameWithParams(); - String fileName = compiledMethod.getClassEntry().getFullFileName(); + String methodName = compiledMethod.primary().getFullMethodNameWithParams(); + String fileName = compiledMethod.ownerType().getFullFileName(); log(context, " [0x%08x] %s %s", pos, methodName, fileName); pos = writeCompiledMethodLineInfo(context, classEntry, compiledMethod, buffer, pos); cursor.set(pos); @@ -566,23 +551,18 @@ private int writeLineNumberTable(DebugContext context, ClassEntry classEntry, by return cursor.get(); } - private static SubRange prologueLeafRange(CompiledMethodEntry compiledEntry) { - Iterator iterator = compiledEntry.leafRangeIterator(); - if (iterator.hasNext()) { - SubRange range = iterator.next(); - if (range.getLo() == compiledEntry.getPrimary().getLo()) { - return range; - } - } - return null; + private static Range prologueLeafRange(CompiledMethodEntry compiledEntry) { + return compiledEntry.leafRangeStream() + .findFirst() + .filter(r -> r.getLo() == compiledEntry.primary().getLo()) + .orElse(null); } private int writeCopyOp(DebugContext context, byte[] buffer, int p) { DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNS_copy; - int pos = p; debugCopyCount++; - verboseLog(context, " [0x%08x] Copy %d", pos, debugCopyCount); - return writeLineOpcode(opcode, buffer, pos); + verboseLog(context, " [0x%08x] Copy %d", p, debugCopyCount); + return writeLineOpcode(opcode, buffer, p); } private int writeAdvancePCOp(DebugContext context, long uleb, byte[] buffer, int p) { @@ -622,24 +602,21 @@ private int writeSetColumnOp(DebugContext context, long uleb, byte[] buffer, int @SuppressWarnings("unused") private int writeNegateStmtOp(DebugContext context, byte[] buffer, int p) { DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNS_negate_stmt; - int pos = p; - return writeLineOpcode(opcode, buffer, pos); + return writeLineOpcode(opcode, buffer, p); } private int writeSetBasicBlockOp(DebugContext context, byte[] buffer, int p) { DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNS_set_basic_block; - int pos = p; - verboseLog(context, " [0x%08x] Set basic block", pos); - return writeLineOpcode(opcode, buffer, pos); + verboseLog(context, " [0x%08x] Set basic block", p); + return writeLineOpcode(opcode, buffer, p); } private int writeConstAddPCOp(DebugContext context, byte[] buffer, int p) { DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNS_const_add_pc; - int pos = p; int advance = opcodeAddress((byte) 255); debugAddress += advance; - verboseLog(context, " [0x%08x] Advance PC by constant %d to 0x%08x", pos, advance, debugAddress); - return writeLineOpcode(opcode, buffer, pos); + verboseLog(context, " [0x%08x] Advance PC by constant %d to 0x%08x", p, advance, debugAddress); + return writeLineOpcode(opcode, buffer, p); } private int writeFixedAdvancePCOp(DebugContext context, short arg, byte[] buffer, int p) { @@ -655,7 +632,7 @@ private int writeEndSequenceOp(DebugContext context, byte[] buffer, int p) { DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNE_end_sequence; int pos = p; verboseLog(context, " [0x%08x] Extended opcode 1: End sequence", pos); - debugAddress = debugTextBase; + debugAddress = 0; debugLine = 1; debugCopyCount = 0; pos = writePrefixOpcode(buffer, pos); @@ -669,15 +646,14 @@ private int writeEndSequenceOp(DebugContext context, byte[] buffer, int p) { private int writeSetAddressOp(DebugContext context, long arg, byte[] buffer, int p) { DwarfLineOpcode opcode = DwarfLineOpcode.DW_LNE_set_address; int pos = p; - debugAddress = debugTextBase + (int) arg; - verboseLog(context, " [0x%08x] Extended opcode 2: Set Address to 0x%08x", pos, debugAddress); + verboseLog(context, " [0x%08x] Extended opcode 2: Set Address to 0x%08x", pos, arg); pos = writePrefixOpcode(buffer, pos); /* * Insert extended insn byte count as ULEB. */ pos = writeULEB(9, buffer, pos); pos = writeLineOpcode(opcode, buffer, pos); - return writeRelocatableCodeOffset(arg, buffer, pos); + return writeCodeOffset(arg, buffer, pos); } @SuppressWarnings("unused") @@ -733,15 +709,14 @@ private static int opcodeLine(byte opcode) { } private int writeSpecialOpcode(DebugContext context, byte opcode, byte[] buffer, int p) { - int pos = p; if (debug && opcode == 0) { verboseLog(context, " [0x%08x] ERROR Special Opcode %d: Address 0x%08x Line %d", debugAddress, debugLine); } debugAddress += opcodeAddress(opcode); debugLine += opcodeLine(opcode); verboseLog(context, " [0x%08x] Special Opcode %d: advance Address by %d to 0x%08x and Line by %d to %d", - pos, opcodeId(opcode), opcodeAddress(opcode), debugAddress, opcodeLine(opcode), debugLine); - return writeByte(opcode, buffer, pos); + p, opcodeId(opcode), opcodeAddress(opcode), debugAddress, opcodeLine(opcode), debugLine); + return writeByte(opcode, buffer, p); } private static final int MAX_ADDRESS_ONLY_DELTA = (0xff - LN_OPCODE_BASE) / LN_LINE_RANGE; diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLineStrSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLineStrSectionImpl.java new file mode 100644 index 000000000000..0984c5e3a5fa --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLineStrSectionImpl.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.elf.dwarf; + +import com.oracle.objectfile.debugentry.StringEntry; +import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName; + +import jdk.graal.compiler.debug.DebugContext; + +/** + * Generator for debug_line_str section. + */ +public class DwarfLineStrSectionImpl extends DwarfSectionImpl { + public DwarfLineStrSectionImpl(DwarfDebugInfo dwarfSections) { + // debug_line_str section depends on line section + super(dwarfSections, DwarfSectionName.DW_LINE_STR_SECTION, DwarfSectionName.DW_LINE_SECTION); + } + + @Override + public void createContent() { + assert !contentByteArrayCreated(); + + int pos = 0; + for (StringEntry stringEntry : dwarfSections.getLineStringTable()) { + stringEntry.setOffset(pos); + String string = stringEntry.getString(); + pos = writeUTF8StringBytes(string, null, pos); + } + byte[] buffer = new byte[pos]; + super.setContent(buffer); + } + + @Override + public void writeContent(DebugContext context) { + assert contentByteArrayCreated(); + + byte[] buffer = getContent(); + int size = buffer.length; + int pos = 0; + + enableLog(context); + + verboseLog(context, " [0x%08x] DEBUG_STR", pos); + for (StringEntry stringEntry : dwarfSections.getLineStringTable()) { + assert stringEntry.getOffset() == pos; + String string = stringEntry.getString(); + pos = writeUTF8StringBytes(string, buffer, pos); + verboseLog(context, " [0x%08x] string = %s", pos, string); + } + assert pos == size; + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLocSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLocSectionImpl.java index bdc82370dbc8..cfa45ab326d3 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLocSectionImpl.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLocSectionImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2022, 2022, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -28,7 +28,6 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -38,10 +37,12 @@ import com.oracle.objectfile.LayoutDecisionMap; import com.oracle.objectfile.ObjectFile; import com.oracle.objectfile.debugentry.ClassEntry; +import com.oracle.objectfile.debugentry.ConstantValueEntry; +import com.oracle.objectfile.debugentry.LocalEntry; +import com.oracle.objectfile.debugentry.LocalValueEntry; +import com.oracle.objectfile.debugentry.RegisterValueEntry; +import com.oracle.objectfile.debugentry.StackValueEntry; import com.oracle.objectfile.debugentry.range.Range; -import com.oracle.objectfile.debugentry.range.SubRange; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalValueInfo; import com.oracle.objectfile.elf.ELFMachine; import com.oracle.objectfile.elf.ELFObjectFile; import com.oracle.objectfile.elf.dwarf.constants.DwarfExpressionOpcode; @@ -71,15 +72,9 @@ public class DwarfLocSectionImpl extends DwarfSectionImpl { */ private int dwarfStackRegister; - private static final LayoutDecision.Kind[] targetLayoutKinds = { - LayoutDecision.Kind.CONTENT, - LayoutDecision.Kind.SIZE, - /* Add this so we can use the text section base address for debug. */ - LayoutDecision.Kind.VADDR}; - public DwarfLocSectionImpl(DwarfDebugInfo dwarfSections) { // debug_loc section depends on text section - super(dwarfSections, DwarfSectionName.DW_LOCLISTS_SECTION, DwarfSectionName.TEXT_SECTION, targetLayoutKinds); + super(dwarfSections, DwarfSectionName.DW_LOCLISTS_SECTION, DwarfSectionName.TEXT_SECTION); initDwarfRegMap(); } @@ -116,7 +111,7 @@ public void writeContent(DebugContext context) { int size = buffer.length; int pos = 0; - enableLog(context, pos); + enableLog(context); log(context, " [0x%08x] DEBUG_LOC", pos); log(context, " [0x%08x] size = 0x%08x", pos, size); @@ -131,7 +126,7 @@ private int generateContent(DebugContext context, byte[] buffer) { // reason for doing it in class entry order is to because it mirrors the // order in which entries appear in the info section. That stops objdump // posting spurious messages about overlaps and holes in the var ranges. - instanceClassStream().filter(ClassEntry::hasCompiledEntries).forEachOrdered(classEntry -> { + instanceClassStream().filter(ClassEntry::hasCompiledMethods).forEachOrdered(classEntry -> { List locationListEntries = getLocationListEntries(classEntry); if (locationListEntries.isEmpty()) { // no need to emit empty location list @@ -177,14 +172,14 @@ private int writeLocationListsHeader(int offsetEntries, byte[] buffer, int p) { return writeInt(offsetEntries, buffer, pos); } - private record LocationListEntry(Range range, int base, DebugLocalInfo local, List rangeList) { + private record LocationListEntry(Range range, long base, LocalEntry local, List rangeList) { } private static List getLocationListEntries(ClassEntry classEntry) { List locationListEntries = new ArrayList<>(); - classEntry.compiledEntries().forEachOrdered(compiledEntry -> { - Range primary = compiledEntry.getPrimary(); + classEntry.compiledMethods().forEach(compiledEntry -> { + Range primary = compiledEntry.primary(); /* * Note that offsets are written relative to the primary range base. This requires * writing a base address entry before each of the location list ranges. It is possible @@ -194,37 +189,25 @@ private static List getLocationListEntries(ClassEntry classEn * code addresses e.g. to set a breakpoint, leading to a very slow response for the * user. */ - int base = primary.getLo(); + long base = primary.getLo(); // location list entries for primary range locationListEntries.addAll(getRangeLocationListEntries(primary, base)); // location list entries for inlined calls if (!primary.isLeaf()) { - Iterator iterator = compiledEntry.topDownRangeIterator(); - while (iterator.hasNext()) { - SubRange subrange = iterator.next(); - if (subrange.isLeaf()) { - continue; - } - locationListEntries.addAll(getRangeLocationListEntries(subrange, base)); - } + compiledEntry.callRangeStream().forEach(subrange -> locationListEntries.addAll(getRangeLocationListEntries(subrange, base))); } }); return locationListEntries; } - private static List getRangeLocationListEntries(Range range, int base) { - List locationListEntries = new ArrayList<>(); - - for (Map.Entry> entry : range.getVarRangeMap().entrySet()) { - if (!entry.getValue().isEmpty()) { - locationListEntries.add(new LocationListEntry(range, base, entry.getKey(), entry.getValue())); - } - } - - return locationListEntries; + private static List getRangeLocationListEntries(Range range, long base) { + return range.getVarRangeMap().entrySet().stream() + .filter(entry -> !entry.getValue().isEmpty()) + .map(entry -> new LocationListEntry(range, base, entry.getKey(), entry.getValue())) + .toList(); } - private int writeVarLocations(DebugContext context, DebugLocalInfo local, int base, List rangeList, byte[] buffer, int p) { + private int writeVarLocations(DebugContext context, LocalEntry local, long base, List rangeList, byte[] buffer, int p) { assert !rangeList.isEmpty(); int pos = p; // collect ranges and values, merging adjacent ranges that have equal value @@ -236,27 +219,27 @@ private int writeVarLocations(DebugContext context, DebugLocalInfo local, int ba pos = writeAttrAddress(base, buffer, pos); // write ranges as offsets from base for (LocalValueExtent extent : extents) { - DebugLocalValueInfo value = extent.value; + LocalValueEntry value = extent.value; assert (value != null); - log(context, " [0x%08x] local %s:%s [0x%x, 0x%x] = %s", pos, value.name(), value.typeName(), extent.getLo(), extent.getHi(), formatValue(value)); + log(context, " [0x%08x] local %s:%s [0x%x, 0x%x] = %s", pos, local.name(), local.type().getTypeName(), extent.getLo(), extent.getHi(), value); pos = writeLocationListEntry(DwarfLocationListEntry.DW_LLE_offset_pair, buffer, pos); pos = writeULEB(extent.getLo() - base, buffer, pos); pos = writeULEB(extent.getHi() - base, buffer, pos); - switch (value.localKind()) { - case REGISTER: - pos = writeRegisterLocation(context, value.regIndex(), buffer, pos); + switch (value) { + case RegisterValueEntry registerValueEntry: + pos = writeRegisterLocation(context, registerValueEntry.regIndex(), buffer, pos); break; - case STACKSLOT: - pos = writeStackLocation(context, value.stackSlot(), buffer, pos); + case StackValueEntry stackValueEntry: + pos = writeStackLocation(context, stackValueEntry.stackSlot(), buffer, pos); break; - case CONSTANT: - JavaConstant constant = value.constantValue(); + case ConstantValueEntry constantValueEntry: + JavaConstant constant = constantValueEntry.constant(); if (constant instanceof PrimitiveConstant) { - pos = writePrimitiveConstantLocation(context, value.constantValue(), buffer, pos); + pos = writePrimitiveConstantLocation(context, constant, buffer, pos); } else if (constant.isNull()) { - pos = writeNullConstantLocation(context, value.constantValue(), buffer, pos); + pos = writeNullConstantLocation(context, constant, buffer, pos); } else { - pos = writeObjectConstantLocation(context, value.constantValue(), value.heapOffset(), buffer, pos); + pos = writeObjectConstantLocation(context, constant, constantValueEntry.heapOffset(), buffer, pos); } break; default: @@ -378,16 +361,16 @@ private int writeObjectConstantLocation(DebugContext context, JavaConstant const static class LocalValueExtent { long lo; long hi; - DebugLocalValueInfo value; + LocalValueEntry value; - LocalValueExtent(long lo, long hi, DebugLocalValueInfo value) { + LocalValueExtent(long lo, long hi, LocalValueEntry value) { this.lo = lo; this.hi = hi; this.value = value; } @SuppressWarnings("unused") - boolean shouldMerge(int otherLo, int otherHi, DebugLocalValueInfo otherValue) { + boolean shouldMerge(long otherLo, long otherHi, LocalValueEntry otherValue) { // ranges need to be contiguous to merge if (hi != otherLo) { return false; @@ -395,7 +378,7 @@ boolean shouldMerge(int otherLo, int otherHi, DebugLocalValueInfo otherValue) { return value.equals(otherValue); } - private LocalValueExtent maybeMerge(int otherLo, int otherHi, DebugLocalValueInfo otherValue) { + private LocalValueExtent maybeMerge(long otherLo, long otherHi, LocalValueEntry otherValue) { if (shouldMerge(otherLo, otherHi, otherValue)) { // We can extend the current extent to cover the next one. this.hi = otherHi; @@ -414,14 +397,14 @@ public long getHi() { return hi; } - public DebugLocalValueInfo getValue() { + public LocalValueEntry getValue() { return value; } - public static List coalesce(DebugLocalInfo local, List rangeList) { + public static List coalesce(LocalEntry local, List rangeList) { List extents = new ArrayList<>(); LocalValueExtent current = null; - for (SubRange range : rangeList) { + for (Range range : rangeList) { if (current == null) { current = new LocalValueExtent(range.getLo(), range.getHi(), range.lookupValue(local)); extents.add(current); @@ -632,10 +615,6 @@ public enum DwarfRegEncodingAMD64 implements DwarfRegEncoding { this.graalEncoding = graalEncoding; } - public static int graalOrder(DwarfRegEncodingAMD64 e1, DwarfRegEncodingAMD64 e2) { - return Integer.compare(e1.graalEncoding, e2.graalEncoding); - } - @Override public int getDwarfEncoding() { return dwarfEncoding; diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfRangesSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfRangesSectionImpl.java index 1d289c6307c8..2709733098de 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfRangesSectionImpl.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfRangesSectionImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,11 +26,6 @@ package com.oracle.objectfile.elf.dwarf; -import java.util.Map; - -import com.oracle.objectfile.LayoutDecision; -import com.oracle.objectfile.LayoutDecisionMap; -import com.oracle.objectfile.ObjectFile; import com.oracle.objectfile.debugentry.ClassEntry; import com.oracle.objectfile.elf.dwarf.constants.DwarfRangeListEntry; import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName; @@ -55,23 +50,6 @@ public void createContent() { super.setContent(buffer); } - @Override - public byte[] getOrDecideContent(Map alreadyDecided, byte[] contentHint) { - ObjectFile.Element textElement = getElement().getOwner().elementForName(".text"); - LayoutDecisionMap decisionMap = alreadyDecided.get(textElement); - if (decisionMap != null) { - Object valueObj = decisionMap.getDecidedValue(LayoutDecision.Kind.VADDR); - if (valueObj != null && valueObj instanceof Number) { - /* - * This may not be the final vaddr for the text segment but it will be close enough - * to make debug easier i.e. to within a 4k page or two. - */ - debugTextBase = ((Number) valueObj).longValue(); - } - } - return super.getOrDecideContent(alreadyDecided, contentHint); - } - @Override public void writeContent(DebugContext context) { assert contentByteArrayCreated(); @@ -80,7 +58,7 @@ public void writeContent(DebugContext context) { int size = buffer.length; int pos = 0; - enableLog(context, pos); + enableLog(context); log(context, " [0x%08x] DEBUG_RANGES", pos); log(context, " [0x%08x] size = 0x%08x", pos, size); @@ -119,7 +97,7 @@ private int writeRangeListsHeader(byte[] buffer, int p) { private int writeRangeLists(DebugContext context, byte[] buffer, int p) { Cursor entryCursor = new Cursor(p); - instanceClassStream().filter(ClassEntry::hasCompiledEntries).forEachOrdered(classEntry -> { + instanceClassWithCompilationStream().forEachOrdered(classEntry -> { int pos = entryCursor.get(); setCodeRangesIndex(classEntry, pos); /* Write range list for a class */ @@ -131,18 +109,18 @@ private int writeRangeLists(DebugContext context, byte[] buffer, int p) { private int writeRangeList(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { int pos = p; log(context, " [0x%08x] ranges start for class %s", pos, classEntry.getTypeName()); - int base = classEntry.compiledEntriesBase(); + long base = classEntry.lowpc(); log(context, " [0x%08x] base 0x%x", pos, base); pos = writeRangeListEntry(DwarfRangeListEntry.DW_RLE_base_address, buffer, pos); - pos = writeRelocatableCodeOffset(base, buffer, pos); + pos = writeCodeOffset(base, buffer, pos); Cursor cursor = new Cursor(pos); - classEntry.compiledEntries().forEach(compiledMethodEntry -> { + classEntry.compiledMethods().forEach(compiledMethodEntry -> { cursor.set(writeRangeListEntry(DwarfRangeListEntry.DW_RLE_offset_pair, buffer, cursor.get())); - int loOffset = compiledMethodEntry.getPrimary().getLo() - base; - int hiOffset = compiledMethodEntry.getPrimary().getHi() - base; - log(context, " [0x%08x] lo 0x%x (%s)", cursor.get(), loOffset, compiledMethodEntry.getPrimary().getFullMethodNameWithParams()); + long loOffset = compiledMethodEntry.primary().getLo() - base; + long hiOffset = compiledMethodEntry.primary().getHi() - base; + log(context, " [0x%08x] lo 0x%x (%s)", cursor.get(), loOffset, compiledMethodEntry.primary().getFullMethodNameWithParams()); cursor.set(writeULEB(loOffset, buffer, cursor.get())); log(context, " [0x%08x] hi 0x%x", cursor.get(), hiOffset); cursor.set(writeULEB(hiOffset, buffer, cursor.get())); diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java index 04b550b7e9a8..3c66cfabf351 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 @@ -40,16 +40,15 @@ import com.oracle.objectfile.debugentry.ArrayTypeEntry; import com.oracle.objectfile.debugentry.ClassEntry; import com.oracle.objectfile.debugentry.CompiledMethodEntry; -import com.oracle.objectfile.debugentry.DirEntry; -import com.oracle.objectfile.debugentry.FileEntry; +import com.oracle.objectfile.debugentry.ForeignStructTypeEntry; import com.oracle.objectfile.debugentry.HeaderTypeEntry; +import com.oracle.objectfile.debugentry.LocalEntry; import com.oracle.objectfile.debugentry.MethodEntry; +import com.oracle.objectfile.debugentry.PointerToTypeEntry; import com.oracle.objectfile.debugentry.PrimitiveTypeEntry; import com.oracle.objectfile.debugentry.StructureTypeEntry; import com.oracle.objectfile.debugentry.TypeEntry; import com.oracle.objectfile.debugentry.range.Range; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalInfo; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLocalValueInfo; import com.oracle.objectfile.elf.ELFMachine; import com.oracle.objectfile.elf.ELFObjectFile; import com.oracle.objectfile.elf.dwarf.DwarfDebugInfo.AbbrevCode; @@ -63,14 +62,13 @@ import com.oracle.objectfile.elf.dwarf.constants.DwarfVersion; import jdk.graal.compiler.debug.DebugContext; -import jdk.vm.ci.meta.ResolvedJavaType; /** * A class from which all DWARF debug sections inherit providing common behaviours. */ public abstract class DwarfSectionImpl extends BasicProgbitsSectionImpl { // auxiliary class used to track byte array positions - protected class Cursor { + protected static class Cursor { private int pos; public Cursor() { @@ -100,9 +98,7 @@ public int get() { protected final DwarfDebugInfo dwarfSections; protected boolean debug = false; - protected long debugTextBase = 0; protected long debugAddress = 0; - protected int debugBase = 0; /** * The name of this section. @@ -188,7 +184,7 @@ private String debugSectionLogName() { return "dwarf" + getSectionName(); } - protected void enableLog(DebugContext context, int pos) { + protected void enableLog(DebugContext context) { /* * Debug output is disabled during the first pass where we size the buffer. this is called * to enable it during the second pass where the buffer gets written, but only if the scope @@ -196,10 +192,8 @@ protected void enableLog(DebugContext context, int pos) { */ assert contentByteArrayCreated(); - if (context.areScopesEnabled()) { + if (context.areScopesEnabled() && context.isLogEnabled()) { debug = true; - debugBase = pos; - debugAddress = debugTextBase; } } @@ -281,33 +275,45 @@ protected int putLong(long l, byte[] buffer, int p) { return pos; } - protected int putRelocatableCodeOffset(long l, byte[] buffer, int p) { + protected int putCodeOffset(long l, byte[] buffer, int p) { int pos = p; - /* - * Mark address so it is relocated relative to the start of the text segment. - */ - markRelocationSite(pos, ObjectFile.RelocationKind.DIRECT_8, DwarfSectionName.TEXT_SECTION.value(), l); - pos = writeLong(0, buffer, pos); + if (dwarfSections.isRuntimeCompilation()) { + pos = writeLong(l, buffer, p); + } else { + /* + * Mark address so it is relocated relative to the start of the text segment. + */ + markRelocationSite(pos, ObjectFile.RelocationKind.DIRECT_8, DwarfSectionName.TEXT_SECTION.value(), l); + pos = writeLong(0, buffer, pos); + } return pos; } - protected int putRelocatableHeapOffset(long l, byte[] buffer, int p) { + protected int putHeapOffset(long l, byte[] buffer, int p) { int pos = p; - /* - * Mark address so it is relocated relative to the start of the heap. - */ - markRelocationSite(pos, ObjectFile.RelocationKind.DIRECT_8, DwarfDebugInfo.HEAP_BEGIN_NAME, l); - pos = writeLong(0, buffer, pos); + if (dwarfSections.isRuntimeCompilation()) { + pos = writeLong(l, buffer, pos); + } else { + /* + * Mark address so it is relocated relative to the start of the heap. + */ + markRelocationSite(pos, ObjectFile.RelocationKind.DIRECT_8, DwarfDebugInfo.HEAP_BEGIN_NAME, l); + pos = writeLong(0, buffer, pos); + } return pos; } - protected int putRelocatableDwarfSectionOffset(int offset, byte[] buffer, String referencedSectionName, int p) { + protected int putDwarfSectionOffset(int offset, byte[] buffer, String referencedSectionName, int p) { int pos = p; - /* - * Mark address so it is relocated relative to the start of the desired section. - */ - markRelocationSite(pos, ObjectFile.RelocationKind.DIRECT_4, referencedSectionName, offset); - pos = writeInt(0, buffer, pos); + if (dwarfSections.isRuntimeCompilation()) { + pos = writeInt(offset, buffer, pos); + } else { + /* + * Mark address so it is relocated relative to the start of the desired section. + */ + markRelocationSite(pos, ObjectFile.RelocationKind.DIRECT_4, referencedSectionName, offset); + pos = writeInt(0, buffer, pos); + } return pos; } @@ -402,17 +408,17 @@ protected int writeLong(long l, byte[] buffer, int p) { } } - protected int writeRelocatableCodeOffset(long l, byte[] buffer, int p) { + protected int writeCodeOffset(long l, byte[] buffer, int p) { if (buffer != null) { - return putRelocatableCodeOffset(l, buffer, p); + return putCodeOffset(l, buffer, p); } else { return p + 8; } } - protected int writeRelocatableHeapOffset(long l, byte[] buffer, int p) { + protected int writeHeapOffset(long l, byte[] buffer, int p) { if (buffer != null) { - return putRelocatableHeapOffset(l, buffer, p); + return putHeapOffset(l, buffer, p); } else { return p + 8; } @@ -446,7 +452,8 @@ protected int writeUTF8StringBytes(String s, int startChar, byte[] buffer, int p if (buffer != null) { return putUTF8StringBytes(s, startChar, buffer, p); } else { - return s.substring(startChar).getBytes(StandardCharsets.UTF_8).length; + // +1 for null termination + return p + s.substring(startChar).getBytes(StandardCharsets.UTF_8).length + 1; } } @@ -522,7 +529,7 @@ protected int writeFlag(DwarfFlag flag, byte[] buffer, int pos) { } protected int writeAttrAddress(long address, byte[] buffer, int pos) { - return writeRelocatableCodeOffset(address, buffer, pos); + return writeCodeOffset(address, buffer, pos); } @SuppressWarnings("unused") @@ -559,15 +566,23 @@ protected int writeAbbrevSectionOffset(int offset, byte[] buffer, int pos) { } protected int writeStrSectionOffset(String value, byte[] buffer, int p) { - int pos = p; int idx = debugStringIndex(value); - return writeStrSectionOffset(idx, buffer, pos); + return writeStrSectionOffset(idx, buffer, p); } private int writeStrSectionOffset(int offset, byte[] buffer, int pos) { return writeDwarfSectionOffset(offset, buffer, DwarfSectionName.DW_STR_SECTION, pos); } + protected int writeLineStrSectionOffset(String value, byte[] buffer, int p) { + int idx = debugLineStringIndex(value); + return writeLineStrSectionOffset(idx, buffer, p); + } + + private int writeLineStrSectionOffset(int offset, byte[] buffer, int pos) { + return writeDwarfSectionOffset(offset, buffer, DwarfSectionName.DW_LINE_STR_SECTION, pos); + } + protected int writeLocSectionOffset(int offset, byte[] buffer, int pos) { return writeDwarfSectionOffset(offset, buffer, DwarfSectionName.DW_LOCLISTS_SECTION, pos); } @@ -576,7 +591,7 @@ protected int writeDwarfSectionOffset(int offset, byte[] buffer, DwarfSectionNam // offsets to abbrev section DIEs need a relocation // the linker uses this to update the offset when info sections are merged if (buffer != null) { - return putRelocatableDwarfSectionOffset(offset, buffer, referencedSectionName.value(), pos); + return putDwarfSectionOffset(offset, buffer, referencedSectionName.value(), pos); } else { return pos + 4; } @@ -639,7 +654,7 @@ protected int writeHeapLocation(long offset, byte[] buffer, int p) { if (dwarfSections.useHeapBase()) { return writeHeapLocationBaseRelative(offset, buffer, p); } else { - return writeHeapLocationRelocatable(offset, buffer, p); + return writeHeapLocationOffset(offset, buffer, p); } } @@ -650,25 +665,11 @@ private int writeHeapLocationBaseRelative(long offset, byte[] buffer, int p) { return writeSLEB(offset, buffer, pos); } - private int writeHeapLocationRelocatable(long offset, byte[] buffer, int p) { + private int writeHeapLocationOffset(long offset, byte[] buffer, int p) { int pos = p; /* Write a relocatable address relative to the heap section start. */ pos = writeExprOpcode(DwarfExpressionOpcode.DW_OP_addr, buffer, pos); - return writeRelocatableHeapOffset(offset, buffer, pos); - } - - protected static String formatValue(DebugLocalValueInfo value) { - switch (value.localKind()) { - case REGISTER: - return "REG:" + value.regIndex(); - case STACKSLOT: - return "STACK:" + value.stackSlot(); - case CONSTANT: - return "CONST:" + value.constantValue() + "[" + Long.toHexString(value.heapOffset()) + "]"; - case UNDEFINED: - default: - return "-"; - } + return writeHeapOffset(offset, buffer, pos); } /** @@ -763,7 +764,25 @@ protected Stream typeStream() { * @return a stream of all primitive types notified via the DebugTypeInfo API. */ protected Stream primitiveTypeStream() { - return typeStream().filter(TypeEntry::isPrimitive).map(entry -> ((PrimitiveTypeEntry) entry)); + return dwarfSections.getPrimitiveTypes().stream(); + } + + /** + * Retrieve a stream of all pointer types notified via the DebugTypeInfo API. + * + * @return a stream of all pointer types notified via the DebugTypeInfo API. + */ + protected Stream pointerTypeStream() { + return dwarfSections.getPointerTypes().stream(); + } + + /** + * Retrieve a stream of all pointer types notified via the DebugTypeInfo API. + * + * @return a stream of all pointer types notified via the DebugTypeInfo API. + */ + protected Stream foreignStructTypeStream() { + return dwarfSections.getForeignStructTypes().stream(); } /** @@ -772,7 +791,7 @@ protected Stream primitiveTypeStream() { * @return a stream of all array types notified via the DebugTypeInfo API. */ protected Stream arrayTypeStream() { - return typeStream().filter(TypeEntry::isArray).map(entry -> ((ArrayTypeEntry) entry)); + return dwarfSections.getArrayTypes().stream(); } /** @@ -803,6 +822,10 @@ protected Stream instanceClassStream() { return dwarfSections.getInstanceClasses().stream(); } + protected Stream instanceClassWithCompilationStream() { + return dwarfSections.getInstanceClassesWithCompilation().stream(); + } + /** * Retrieve a stream of all compiled methods notified via the DebugTypeInfo API. * @@ -812,26 +835,6 @@ protected Stream compiledMethodsStream() { return dwarfSections.getCompiledMethods().stream(); } - protected int compiledMethodsCount() { - return dwarfSections.getCompiledMethods().size(); - } - - protected Stream fileStream() { - return dwarfSections.getFiles().stream(); - } - - protected int fileCount() { - return dwarfSections.getFiles().size(); - } - - protected Stream dirStream() { - return dwarfSections.getDirs().stream(); - } - - protected int dirCount() { - return dwarfSections.getDirs().size(); - } - /** * Retrieve an iterable for all instance classes, including interfaces and enums, notified via * the DebugTypeInfo API. @@ -849,12 +852,19 @@ protected int debugStringIndex(String str) { return dwarfSections.debugStringIndex(str); } + protected int debugLineStringIndex(String str) { + if (!contentByteArrayCreated()) { + return 0; + } + return dwarfSections.debugLineStringIndex(str); + } + protected String uniqueDebugString(String str) { return dwarfSections.uniqueDebugString(str); } - protected TypeEntry lookupType(ResolvedJavaType type) { - return dwarfSections.lookupTypeEntry(type); + protected String uniqueDebugLineString(String str) { + return dwarfSections.uniqueDebugLineString(str); } protected ClassEntry lookupObjectClass() { @@ -955,7 +965,7 @@ protected int getAbstractInlineMethodIndex(ClassEntry classEntry, MethodEntry me * @param localInfo the local or param whose index is to be recorded. * @param index the info section offset to be recorded. */ - protected void setMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, DebugLocalInfo localInfo, int index) { + protected void setMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, LocalEntry localInfo, int index) { dwarfSections.setMethodLocalIndex(classEntry, methodEntry, localInfo, index); } @@ -968,7 +978,7 @@ protected void setMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntr * @param localInfo the local or param whose index is to be retrieved. * @return the associated info section offset. */ - protected int getMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, DebugLocalInfo localInfo) { + protected int getMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry, LocalEntry localInfo) { if (!contentByteArrayCreated()) { return 0; } @@ -984,7 +994,7 @@ protected int getMethodLocalIndex(ClassEntry classEntry, MethodEntry methodEntry * @param localInfo the local or param whose index is to be recorded. * @param index the info section offset index to be recorded. */ - protected void setRangeLocalIndex(Range range, DebugLocalInfo localInfo, int index) { + protected void setRangeLocalIndex(Range range, LocalEntry localInfo, int index) { dwarfSections.setRangeLocalIndex(range, localInfo, index); } @@ -997,7 +1007,7 @@ protected void setRangeLocalIndex(Range range, DebugLocalInfo localInfo, int ind * @param localInfo the local or param whose index is to be retrieved. * @return the associated info section offset. */ - protected int getRangeLocalIndex(Range range, DebugLocalInfo localInfo) { + protected int getRangeLocalIndex(Range range, LocalEntry localInfo) { return dwarfSections.getRangeLocalIndex(range, localInfo); } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfStrSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfStrSectionImpl.java index 34cddd22e18f..303e82a2af1e 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfStrSectionImpl.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfStrSectionImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -28,6 +28,7 @@ import com.oracle.objectfile.debugentry.StringEntry; import com.oracle.objectfile.elf.dwarf.constants.DwarfSectionName; + import jdk.graal.compiler.debug.DebugContext; /** @@ -45,11 +46,9 @@ public void createContent() { int pos = 0; for (StringEntry stringEntry : dwarfSections.getStringTable()) { - if (stringEntry.isAddToStrSection()) { - stringEntry.setOffset(pos); - String string = stringEntry.getString(); - pos += countUTF8Bytes(string) + 1; - } + stringEntry.setOffset(pos); + String string = stringEntry.getString(); + pos = writeUTF8StringBytes(string, null, pos); } byte[] buffer = new byte[pos]; super.setContent(buffer); @@ -63,16 +62,14 @@ public void writeContent(DebugContext context) { int size = buffer.length; int pos = 0; - enableLog(context, pos); + enableLog(context); verboseLog(context, " [0x%08x] DEBUG_STR", pos); for (StringEntry stringEntry : dwarfSections.getStringTable()) { - if (stringEntry.isAddToStrSection()) { - assert stringEntry.getOffset() == pos; - String string = stringEntry.getString(); - pos = writeUTF8StringBytes(string, buffer, pos); - verboseLog(context, " [0x%08x] string = %s", pos, string); - } + assert stringEntry.getOffset() == pos; + String string = stringEntry.getString(); + pos = writeUTF8StringBytes(string, buffer, pos); + verboseLog(context, " [0x%08x] string = %s", pos, string); } assert pos == size; } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfAttribute.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfAttribute.java index 050b9e9c726d..b6fec32e5ffe 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfAttribute.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfAttribute.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -47,16 +47,16 @@ public enum DwarfAttribute { DW_AT_artificial(0x34), DW_AT_count(0x37), DW_AT_data_member_location(0x38), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_AT_decl_column(0x39), DW_AT_decl_file(0x3a), DW_AT_decl_line(0x3b), DW_AT_declaration(0x3c), DW_AT_encoding(0x3e), DW_AT_external(0x3f), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_AT_return_addr(0x2a), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_AT_frame_base(0x40), DW_AT_specification(0x47), DW_AT_type(0x49), diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfEncoding.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfEncoding.java index c5cb704e0022..3adbebe9716b 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfEncoding.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfEncoding.java @@ -30,14 +30,14 @@ * DW_AT_encoding attribute values. */ public enum DwarfEncoding { - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_ATE_address((byte) 0x1), DW_ATE_boolean((byte) 0x2), DW_ATE_float((byte) 0x4), DW_ATE_signed((byte) 0x5), DW_ATE_signed_char((byte) 0x6), DW_ATE_unsigned((byte) 0x7), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_ATE_unsigned_char((byte) 0x8); private final byte value; diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfExpressionOpcode.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfExpressionOpcode.java index c55014c908cb..3da675084ff3 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfExpressionOpcode.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfExpressionOpcode.java @@ -31,7 +31,7 @@ */ public enum DwarfExpressionOpcode { DW_OP_addr((byte) 0x03), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_OP_deref((byte) 0x06), DW_OP_dup((byte) 0x12), DW_OP_and((byte) 0x1a), diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfFlag.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfFlag.java index f83fe93b8bfd..3ae00282ff4b 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfFlag.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfFlag.java @@ -30,7 +30,7 @@ * DW_FORM_flag only has two possible attribute values. */ public enum DwarfFlag { - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_FLAG_false((byte) 0), DW_FLAG_true((byte) 1); diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfForm.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfForm.java index 1855bb16850e..0a50b51b506c 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfForm.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfForm.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -32,27 +32,29 @@ public enum DwarfForm { DW_FORM_null(0x0), DW_FORM_addr(0x1), - DW_FORM_data2(0x05), + DW_FORM_data2(0x5), DW_FORM_data4(0x6), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_FORM_data8(0x7), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_FORM_string(0x8), - @SuppressWarnings("unused") - DW_FORM_block1(0x0a), + @SuppressWarnings("unused")// + DW_FORM_block1(0xa), + DW_FORM_data1(0xb), + DW_FORM_flag(0xc), + DW_FORM_strp(0xe), + DW_FORM_udata(0xf), DW_FORM_ref_addr(0x10), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_FORM_ref1(0x11), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_FORM_ref2(0x12), DW_FORM_ref4(0x13), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_FORM_ref8(0x14), DW_FORM_sec_offset(0x17), - DW_FORM_data1(0x0b), - DW_FORM_flag(0xc), - DW_FORM_strp(0xe), DW_FORM_expr_loc(0x18), + DW_FORM_line_strp(0x1f), DW_FORM_ref_sig8(0x20), DW_FORM_loclistx(0x22); diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfFrameValue.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfFrameValue.java index 77ba64cecf37..992c2e942700 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfFrameValue.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfFrameValue.java @@ -37,22 +37,22 @@ public enum DwarfFrameValue { DW_CFA_restore((byte) 0x3), /* Values encoded in low 6 bits. */ DW_CFA_nop((byte) 0x0), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_CFA_set_loc1((byte) 0x1), DW_CFA_advance_loc1((byte) 0x2), DW_CFA_advance_loc2((byte) 0x3), DW_CFA_advance_loc4((byte) 0x4), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_CFA_offset_extended((byte) 0x5), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_CFA_restore_extended((byte) 0x6), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_CFA_undefined((byte) 0x7), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_CFA_same_value((byte) 0x8), DW_CFA_register((byte) 0x9), DW_CFA_def_cfa((byte) 0xc), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_CFA_def_cfa_register((byte) 0xd), DW_CFA_def_cfa_offset((byte) 0xe); diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfInline.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfInline.java index 91498c4a1ed0..76f81bde00d3 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfInline.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfInline.java @@ -30,12 +30,12 @@ * Values for DW_AT_inline attribute. */ public enum DwarfInline { - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_INL_not_inlined((byte) 0), DW_INL_inlined((byte) 1), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_INL_declared_not_inlined((byte) 2), - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_INL_declared_inlined((byte) 3); private final byte value; diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfLineNumberHeaderEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfLineNumberHeaderEntry.java new file mode 100644 index 000000000000..0c651d62392f --- /dev/null +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfLineNumberHeaderEntry.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.objectfile.elf.dwarf.constants; + +/** + * All the Dwarf attribute forms needed to type attribute values generated by GraalVM. + */ +public enum DwarfLineNumberHeaderEntry { + DW_LNCT_path(0x1), + DW_LNCT_directory_index(0x2), + DW_LNCT_timestamp(0x3), + DW_LNCT_size(0x4), + DW_LNCT_MD5(0x5); + + private final int value; + + DwarfLineNumberHeaderEntry(int i) { + value = i; + } + + public int value() { + return value; + } +} diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfLineOpcode.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfLineOpcode.java index 2bb3269ee327..406af4ce539f 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfLineOpcode.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfLineOpcode.java @@ -73,12 +73,12 @@ public enum DwarfLineOpcode { /* * Increment address 1 ushort arg. */ - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_LNS_set_prologue_end((byte) 10), /* * Increment address 1 ushort arg. */ - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_LNS_set_epilogue_begin((byte) 11), /* * Extended line section opcodes defined by DWARF 2. @@ -86,7 +86,7 @@ public enum DwarfLineOpcode { /* * There is no extended opcode 0. */ - @SuppressWarnings("unused") + @SuppressWarnings("unused")// DW_LNE_undefined((byte) 0), /* * End sequence of addresses. diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfSectionName.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfSectionName.java index 9d91ebf24437..09d0366fa9c6 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfSectionName.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfSectionName.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -33,6 +33,7 @@ public enum DwarfSectionName { TEXT_SECTION(".text"), DW_STR_SECTION(".debug_str"), + DW_LINE_STR_SECTION(".debug_line_str"), DW_LINE_SECTION(".debug_line"), DW_FRAME_SECTION(".debug_frame"), DW_ABBREV_SECTION(".debug_abbrev"), diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfTag.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfTag.java index 77527e493900..50ce270354b4 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfTag.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfTag.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfVersion.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfVersion.java index de98c7e83fb2..970c748638ff 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfVersion.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/constants/DwarfVersion.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVDebugInfo.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVDebugInfo.java index 27e0efc5cf04..66b9df7c5e22 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVDebugInfo.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVDebugInfo.java @@ -26,13 +26,14 @@ package com.oracle.objectfile.pecoff.cv; +import java.nio.ByteOrder; + import com.oracle.objectfile.debugentry.DebugInfoBase; import com.oracle.objectfile.pecoff.PECoffMachine; + import jdk.graal.compiler.debug.DebugContext; import jdk.graal.compiler.debug.GraalError; -import java.nio.ByteOrder; - /** * CVDebugInfo is a container class for all the CodeView sections to be emitted in the object file. * Currently, those are.debug$S (CVSymbolSectionImpl) and .debug$T (CVTypeSectionImpl). diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVLineRecordBuilder.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVLineRecordBuilder.java index dd7f777b3bbe..e80c0730a478 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVLineRecordBuilder.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVLineRecordBuilder.java @@ -26,12 +26,11 @@ package com.oracle.objectfile.pecoff.cv; -import com.oracle.objectfile.debugentry.FileEntry; +import java.util.Iterator; + import com.oracle.objectfile.debugentry.CompiledMethodEntry; +import com.oracle.objectfile.debugentry.FileEntry; import com.oracle.objectfile.debugentry.range.Range; -import com.oracle.objectfile.debugentry.range.SubRange; - -import java.util.Iterator; /* * In CV4, the line table consists of a series of file headers followed by line number entries. @@ -60,16 +59,16 @@ public void debug(String format, Object... args) { */ CVLineRecord build(CompiledMethodEntry entry) { this.compiledEntry = entry; - Range primaryRange = compiledEntry.getPrimary(); + Range primaryRange = compiledEntry.primary(); debug("DEBUG_S_LINES linerecord for 0x%05x file: %s:%d", primaryRange.getLo(), primaryRange.getFileName(), primaryRange.getLine()); this.lineRecord = new CVLineRecord(cvDebugInfo, primaryRange.getSymbolName()); debug("CVLineRecord.computeContents: processing primary range %s", primaryRange); processRange(primaryRange); - Iterator iterator = compiledEntry.leafRangeIterator(); + Iterator iterator = compiledEntry.leafRangeStream().iterator(); while (iterator.hasNext()) { - SubRange subRange = iterator.next(); + Range subRange = iterator.next(); debug("CVLineRecord.computeContents: processing range %s", subRange); processRange(subRange); } @@ -102,9 +101,9 @@ private void processRange(Range range) { } /* Add line record. */ - int lineLoAddr = range.getLo() - compiledEntry.getPrimary().getLo(); + int lineLoAddr = (int) (range.getLo() - compiledEntry.primary().getLo()); int line = Math.max(range.getLine(), 1); - debug(" processRange: addNewLine: 0x%05x-0x%05x %s", lineLoAddr, range.getHi() - compiledEntry.getPrimary().getLo(), line); + debug(" processRange: addNewLine: 0x%05x-0x%05x %s", lineLoAddr, range.getHi() - compiledEntry.primary().getLo(), line); lineRecord.addNewLine(lineLoAddr, line); } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVNames.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVNames.java index a1666dc08ce3..1c71b03e5d02 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVNames.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVNames.java @@ -41,10 +41,10 @@ static String typeNameToCodeViewName(TypeEntry typeEntry) { } static String methodNameToCodeViewName(MethodEntry memberEntry) { - return typeNameToCodeViewName(memberEntry.ownerType()) + "::" + memberEntry.methodName(); + return typeNameToCodeViewName(memberEntry.getOwnerType()) + "::" + memberEntry.getMethodName(); } static String fieldNameToCodeViewName(FieldEntry memberEntry) { - return typeNameToCodeViewName(memberEntry.ownerType()) + "::" + memberEntry.fieldName(); + return typeNameToCodeViewName(memberEntry.getOwnerType()) + "::" + memberEntry.fieldName(); } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubrecord.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubrecord.java index 29d819c743f1..a1df75b6763f 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubrecord.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubrecord.java @@ -90,7 +90,7 @@ private static String findObjectName(CVDebugInfo cvDebugInfo) { String fn = null; for (ClassEntry classEntry : cvDebugInfo.getInstanceClasses()) { if (classEntry.getFileName() != null) { - fn = classEntry.getFileEntry().getFileName(); + fn = classEntry.getFileEntry().fileName(); if (fn.endsWith(".java")) { fn = fn.substring(0, fn.lastIndexOf(".java")) + ".obj"; } @@ -216,7 +216,7 @@ private static String findFirstFile(CVDebugInfo cvDebugInfo) { String fn = null; for (ClassEntry classEntry : cvDebugInfo.getInstanceClasses()) { if (classEntry.getFileName() != null) { - fn = classEntry.getFileEntry().getFileName(); + fn = classEntry.getFileEntry().fileName(); break; } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubsectionBuilder.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubsectionBuilder.java index 3d4722d384ac..3694ea3ad3fc 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubsectionBuilder.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVSymbolSubsectionBuilder.java @@ -26,6 +26,11 @@ package com.oracle.objectfile.pecoff.cv; +import static com.oracle.objectfile.pecoff.cv.CVConstants.CV_AMD64_R8; +import static com.oracle.objectfile.pecoff.cv.CVTypeConstants.MAX_PRIMITIVE; + +import java.lang.reflect.Modifier; + import com.oracle.objectfile.SectionName; import com.oracle.objectfile.debugentry.ClassEntry; import com.oracle.objectfile.debugentry.CompiledMethodEntry; @@ -33,11 +38,6 @@ import com.oracle.objectfile.debugentry.TypeEntry; import com.oracle.objectfile.debugentry.range.Range; -import java.lang.reflect.Modifier; - -import static com.oracle.objectfile.pecoff.cv.CVConstants.CV_AMD64_R8; -import static com.oracle.objectfile.pecoff.cv.CVTypeConstants.MAX_PRIMITIVE; - final class CVSymbolSubsectionBuilder { private final CVDebugInfo cvDebugInfo; @@ -68,8 +68,8 @@ void build() { /* Loop over all classes defined in this module. */ for (TypeEntry typeEntry : cvDebugInfo.getTypes()) { /* Add type record for this entry. */ - if (typeEntry.isClass()) { - buildClass((ClassEntry) typeEntry); + if (typeEntry instanceof ClassEntry classEntry) { + buildClass(classEntry); } else { addTypeRecords(typeEntry); } @@ -85,13 +85,13 @@ void build() { private void buildClass(ClassEntry classEntry) { /* Define all the functions in this class all functions defined in this class. */ - classEntry.compiledEntries().forEach(this::buildFunction); + classEntry.compiledMethods().forEach(this::buildFunction); /* Define the class itself. */ addTypeRecords(classEntry); /* Add manifested static fields as S_GDATA32 records. */ - classEntry.fields().filter(CVSymbolSubsectionBuilder::isManifestedStaticField).forEach(f -> { + classEntry.getFields().stream().filter(CVSymbolSubsectionBuilder::isManifestedStaticField).forEach(f -> { int typeIndex = cvDebugInfo.getCVTypeSection().getIndexForPointer(f.getValueType()); String displayName = CVNames.fieldNameToCodeViewName(f); if (cvDebugInfo.useHeapBase()) { @@ -118,7 +118,7 @@ private static boolean isManifestedStaticField(FieldEntry fieldEntry) { * @param compiledEntry compiled method for this function */ private void buildFunction(CompiledMethodEntry compiledEntry) { - final Range primaryRange = compiledEntry.getPrimary(); + final Range primaryRange = compiledEntry.primary(); /* The name as it will appear in the debugger. */ final String debuggerName = CVNames.methodNameToCodeViewName(primaryRange.getMethodEntry()); @@ -129,8 +129,8 @@ private void buildFunction(CompiledMethodEntry compiledEntry) { /* S_PROC32 add function definition. */ int functionTypeIndex = addTypeRecords(compiledEntry); byte funcFlags = 0; - CVSymbolSubrecord.CVSymbolGProc32Record proc32 = new CVSymbolSubrecord.CVSymbolGProc32Record(cvDebugInfo, externalName, debuggerName, 0, 0, 0, primaryRange.getHi() - primaryRange.getLo(), 0, - 0, functionTypeIndex, (short) 0, funcFlags); + CVSymbolSubrecord.CVSymbolGProc32Record proc32 = new CVSymbolSubrecord.CVSymbolGProc32Record(cvDebugInfo, externalName, debuggerName, 0, 0, 0, + primaryRange.getHiOffset() - primaryRange.getLoOffset(), 0, 0, functionTypeIndex, (short) 0, funcFlags); addSymbolRecord(proc32); /* S_FRAMEPROC add frame definitions. */ @@ -139,7 +139,7 @@ private void buildFunction(CompiledMethodEntry compiledEntry) { int localBP = 1 << 14; /* Local base pointer = SP (0=none, 1=sp, 2=bp 3=r13). */ int paramBP = 1 << 16; /* Param base pointer = SP. */ int frameFlags = asynceh + localBP + paramBP; /* NB: LLVM uses 0x14000. */ - addSymbolRecord(new CVSymbolSubrecord.CVSymbolFrameProcRecord(cvDebugInfo, compiledEntry.getFrameSize(), frameFlags)); + addSymbolRecord(new CVSymbolSubrecord.CVSymbolFrameProcRecord(cvDebugInfo, compiledEntry.frameSize(), frameFlags)); /* TODO: add parameter definitions (types have been added already). */ /* TODO: add local variables, and their types. */ diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVTypeSectionBuilder.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVTypeSectionBuilder.java index 6ebd0088c30b..b4645425c661 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVTypeSectionBuilder.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVTypeSectionBuilder.java @@ -25,28 +25,6 @@ */ package com.oracle.objectfile.pecoff.cv; -import com.oracle.objectfile.debugentry.ArrayTypeEntry; -import com.oracle.objectfile.debugentry.ClassEntry; -import com.oracle.objectfile.debugentry.CompiledMethodEntry; -import com.oracle.objectfile.debugentry.FieldEntry; -import com.oracle.objectfile.debugentry.HeaderTypeEntry; -import com.oracle.objectfile.debugentry.MemberEntry; -import com.oracle.objectfile.debugentry.MethodEntry; -import com.oracle.objectfile.debugentry.StructureTypeEntry; -import com.oracle.objectfile.debugentry.TypeEntry; - -import jdk.graal.compiler.debug.GraalError; - -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Deque; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - import static com.oracle.objectfile.pecoff.cv.CVTypeConstants.ADDRESS_BITS; import static com.oracle.objectfile.pecoff.cv.CVTypeConstants.CV_CALL_NEAR_C; import static com.oracle.objectfile.pecoff.cv.CVTypeConstants.FUNC_IS_CONSTRUCTOR; @@ -82,8 +60,34 @@ import static com.oracle.objectfile.pecoff.cv.CVTypeConstants.T_UINT4; import static com.oracle.objectfile.pecoff.cv.CVTypeConstants.T_VOID; import static com.oracle.objectfile.pecoff.cv.CVTypeConstants.T_WCHAR; -import static com.oracle.objectfile.pecoff.cv.CVTypeRecord.CVClassRecord.ATTR_FORWARD_REF; import static com.oracle.objectfile.pecoff.cv.CVTypeRecord.CV_TYPE_RECORD_MAX_SIZE; +import static com.oracle.objectfile.pecoff.cv.CVTypeRecord.CVClassRecord.ATTR_FORWARD_REF; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Deque; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import com.oracle.objectfile.debugentry.ArrayTypeEntry; +import com.oracle.objectfile.debugentry.ClassEntry; +import com.oracle.objectfile.debugentry.CompiledMethodEntry; +import com.oracle.objectfile.debugentry.FieldEntry; +import com.oracle.objectfile.debugentry.ForeignStructTypeEntry; +import com.oracle.objectfile.debugentry.HeaderTypeEntry; +import com.oracle.objectfile.debugentry.InterfaceClassEntry; +import com.oracle.objectfile.debugentry.MemberEntry; +import com.oracle.objectfile.debugentry.MethodEntry; +import com.oracle.objectfile.debugentry.PointerToTypeEntry; +import com.oracle.objectfile.debugentry.PrimitiveTypeEntry; +import com.oracle.objectfile.debugentry.StructureTypeEntry; +import com.oracle.objectfile.debugentry.TypeEntry; + +import jdk.graal.compiler.debug.GraalError; class CVTypeSectionBuilder { @@ -112,31 +116,23 @@ CVTypeRecord buildType(TypeEntry typeEntry) { * If we've never seen the class or only defined it as a forward reference, define it now. */ if (typeRecord != null && typeRecord.type == LF_CLASS && !((CVTypeRecord.CVClassRecord) typeRecord).isForwardRef()) { - log("buildType() type %s(%s) is known %s", typeEntry.getTypeName(), typeEntry.typeKind().name(), typeRecord); + log("buildType() type %s(%s) is known %s", typeEntry.getTypeName(), "typeEntry.typeKind().name()", typeRecord); } else { - log("buildType() %s %s size=%d - begin", typeEntry.typeKind().name(), typeEntry.getTypeName(), typeEntry.getSize()); - switch (typeEntry.typeKind()) { - case PRIMITIVE: { - typeRecord = types.getExistingType(typeEntry); - break; - } - case ARRAY: - case ENUM: - case INSTANCE: - case INTERFACE: - // TODO continue treat foreign types as interfaces/classes but fix this later - case FOREIGN: { - typeRecord = buildStructureTypeEntry((StructureTypeEntry) typeEntry); - break; - } - case HEADER: { + log("buildType() %s %s size=%d - begin", "typeEntry.typeKind().name()", typeEntry.getTypeName(), typeEntry.getSize()); + switch (typeEntry) { + case PrimitiveTypeEntry primitiveTypeEntry -> typeRecord = getPrimitiveTypeEntry(primitiveTypeEntry); + case PointerToTypeEntry pointerToTypeEntry -> typeRecord = buildPointerToTypeEntry(pointerToTypeEntry); + case HeaderTypeEntry headerTypeEntry -> { /* * The bits at the beginning of an Object: contains pointer to DynamicHub. */ assert typeEntry.getTypeName().equals(OBJ_HEADER_NAME); - typeRecord = buildStructureTypeEntry((HeaderTypeEntry) typeEntry); - break; + typeRecord = buildStructureTypeEntry(headerTypeEntry); } + case StructureTypeEntry structureTypeEntry -> + // typeEntry is either ArrayTypeEntry, ClassEntry, or ForeignStructTypeEntry + // TODO continue treat foreign types as interfaces/classes but fix this later + typeRecord = buildStructureTypeEntry(structureTypeEntry); } } assert typeRecord != null; @@ -151,7 +147,9 @@ CVTypeRecord buildType(TypeEntry typeEntry) { * @return type record for this function (may return existing matching record) */ CVTypeRecord buildFunction(CompiledMethodEntry entry) { - return buildMemberFunction(entry.getClassEntry(), entry.getPrimary().getMethodEntry()); + ClassEntry ownerType = entry.ownerType(); + assert ownerType != null; + return buildMemberFunction(ownerType, entry.primary().getMethodEntry()); } static class FieldListBuilder { @@ -215,26 +213,90 @@ CVTypeRecord.CVFieldListRecord buildFieldListRecords(CVTypeSectionBuilder builde } } + private CVTypeRecord getPrimitiveTypeEntry(final PrimitiveTypeEntry typeEntry) { + // Check if we have already seen this primitive type + CVTypeRecord primitiveType = types.getExistingType(typeEntry); + if (primitiveType != null) { + return primitiveType; + } + + /* + * Primitive types are pre-defined and do not get written out to the typeInfo section. We + * may need to fetch the correct sequence numbers for foreign primitives + */ + short typeId; + short pointerTypeId; + int size = typeEntry.getSize(); + if (typeEntry.isNumericFloat()) { + assert size == 4 || size == 8; + if (size == 4) { + typeId = T_REAL32; + pointerTypeId = T_64PREAL32; + } else { + typeId = T_REAL64; + pointerTypeId = T_64PREAL64; + } + } else { + assert typeEntry.isNumericInteger(); + assert size == 1 || size == 2 || size == 4 || size == 8; + if (size == 1) { + typeId = T_INT1; + pointerTypeId = T_64PINT1; + } else if (size == 2) { + typeId = T_INT2; + pointerTypeId = T_64PINT2; + } else if (size == 4) { + typeId = T_INT4; + pointerTypeId = T_64PINT4; + } else { + typeId = T_INT8; + pointerTypeId = T_64PINT8; + } + + if (typeEntry.isUnsigned()) { + // signed/unsigned differs by the LSB for 'Real int' types + typeId++; + pointerTypeId++; + } + } + + types.definePrimitiveType(typeEntry.getTypeName(), typeId, size, pointerTypeId); + return types.getExistingType(typeEntry); + } + + private CVTypeRecord buildPointerToTypeEntry(final PointerToTypeEntry typeEntry) { + CVTypeRecord pointedToType = buildType(typeEntry.getPointerTo()); + CVTypeRecord pointerType = addTypeRecord(new CVTypeRecord.CVTypePointerRecord(pointedToType.getSequenceNumber(), CVTypeRecord.CVTypePointerRecord.NORMAL_64)); + + types.typeNameMap.put(typeEntry.getTypeName(), pointerType); + return pointerType; + } + private CVTypeRecord buildStructureTypeEntry(final StructureTypeEntry typeEntry) { - log("buildStructureTypeEntry size=%d kind=%s %s", typeEntry.getSize(), typeEntry.typeKind().name(), typeEntry.getTypeName()); + log("buildStructureTypeEntry size=%d kind=%s %s", typeEntry.getSize(), "typeEntry.typeKind().name()", typeEntry.getTypeName()); - ClassEntry superType = typeEntry.isClass() ? ((ClassEntry) typeEntry).getSuperClass() : null; + StructureTypeEntry superType = null; + if (typeEntry instanceof ClassEntry classEntry) { + superType = classEntry.getSuperClass(); + } else if (typeEntry instanceof ForeignStructTypeEntry foreignStructTypeEntry) { + superType = foreignStructTypeEntry.getParent(); + } int superTypeIndex = superType != null ? types.getIndexForForwardRef(superType) : 0; /* Arrays are implemented as classes, but the inheritance from Object() is implicit. */ - if (superTypeIndex == 0 && typeEntry.isArray()) { + if (superTypeIndex == 0 && typeEntry instanceof ArrayTypeEntry) { superTypeIndex = types.getIndexForForwardRef(JAVA_LANG_OBJECT_NAME); } /* Both java.lang.Object and __objhdr have null superclass. */ /* Force java.lang.Object to have __objhdr as a superclass. */ /* Force interfaces to have __objhdr as a superclass. */ - if (superTypeIndex == 0 && (typeEntry.getTypeName().equals(JAVA_LANG_OBJECT_NAME) || typeEntry.isInterface())) { + if (superTypeIndex == 0 && (typeEntry.getTypeName().equals(JAVA_LANG_OBJECT_NAME) || typeEntry instanceof InterfaceClassEntry)) { superTypeIndex = objectHeaderRecordIndex; } - final List methods = typeEntry.isClass() ? ((ClassEntry) typeEntry).getMethods() : Collections.emptyList(); + final List methods = typeEntry instanceof ClassEntry classEntry ? classEntry.getMethods() : Collections.emptyList(); /* Build fieldlist record */ FieldListBuilder fieldListBuilder = new FieldListBuilder(); @@ -247,20 +309,18 @@ private CVTypeRecord buildStructureTypeEntry(final StructureTypeEntry typeEntry) } /* Only define manifested fields. */ - typeEntry.fields().filter(CVTypeSectionBuilder::isManifestedField).forEach(f -> { + typeEntry.getFields().stream().filter(CVTypeSectionBuilder::isManifestedField).forEach(f -> { log("field %s attr=(%s) offset=%d size=%d valuetype=%s", f.fieldName(), f.getModifiersString(), f.getOffset(), f.getSize(), f.getValueType().getTypeName()); CVTypeRecord.FieldRecord fieldRecord = buildField(f); log("field %s", fieldRecord); fieldListBuilder.addField(fieldRecord); }); - if (typeEntry.isArray()) { + if (typeEntry instanceof ArrayTypeEntry arrayEntry) { /* * Model an array as a struct with a pointer, length and then array of length 0. * String[] becomes struct String[] : Object { int length; String*[0]; } */ - assert typeEntry instanceof ArrayTypeEntry; - ArrayTypeEntry arrayEntry = (ArrayTypeEntry) typeEntry; /* Build 0 length array - this index could be cached. */ final TypeEntry elementType = arrayEntry.getElementType(); @@ -286,10 +346,10 @@ private CVTypeRecord buildStructureTypeEntry(final StructureTypeEntry typeEntry) HashSet overloaded = new HashSet<>(methods.size()); HashSet allFunctions = new HashSet<>(methods.size()); methods.forEach(m -> { - if (allFunctions.contains(m.methodName())) { - overloaded.add(m.methodName()); + if (allFunctions.contains(m.getMethodName())) { + overloaded.add(m.getMethodName()); } else { - allFunctions.add(m.methodName()); + allFunctions.add(m.getMethodName()); } }); @@ -300,12 +360,12 @@ private CVTypeRecord buildStructureTypeEntry(final StructureTypeEntry typeEntry) CVTypeRecord.CVTypeMethodListRecord mlist = new CVTypeRecord.CVTypeMethodListRecord(); /* LF_MFUNCTION records */ - methods.stream().filter(methodEntry -> methodEntry.methodName().equals(mname)).forEach(m -> { - log("overloaded method %s attr=(%s) valuetype=%s", m.methodName(), m.getModifiersString(), m.getValueType().getTypeName()); + methods.stream().filter(methodEntry -> methodEntry.getMethodName().equals(mname)).forEach(m -> { + log("overloaded method %s attr=(%s) valuetype=%s", m.getMethodName(), m.getModifiersString(), m.getValueType().getTypeName()); CVTypeRecord.CVTypeMFunctionRecord mFunctionRecord = buildMemberFunction((ClassEntry) typeEntry, m); short attr = modifiersToAttr(m); log(" overloaded method %s", mFunctionRecord); - mlist.add(attr, mFunctionRecord.getSequenceNumber(), m.getVtableOffset(), m.methodName()); + mlist.add(attr, mFunctionRecord.getSequenceNumber(), m.getVtableOffset(), m.getMethodName()); }); CVTypeRecord.CVTypeMethodListRecord nmlist = addTypeRecord(mlist); @@ -315,8 +375,8 @@ private CVTypeRecord buildStructureTypeEntry(final StructureTypeEntry typeEntry) fieldListBuilder.addField(methodRecord); }); - methods.stream().filter(methodEntry -> !overloaded.contains(methodEntry.methodName())).forEach(m -> { - log("`unique method %s %s(...)", m.methodName(), m.getModifiersString(), m.getValueType().getTypeName(), m.methodName()); + methods.stream().filter(methodEntry -> !overloaded.contains(methodEntry.getMethodName())).forEach(m -> { + log("`unique method %s %s(...)", m.getMethodName(), m.getModifiersString(), m.getValueType().getTypeName(), m.getMethodName()); CVTypeRecord.CVOneMethodRecord method = buildMethod((ClassEntry) typeEntry, m); log(" unique method %s", method); fieldListBuilder.addField(method); @@ -333,9 +393,8 @@ private CVTypeRecord buildStructureTypeEntry(final StructureTypeEntry typeEntry) CVTypeRecord typeRecord = new CVTypeRecord.CVClassRecord(LF_CLASS, (short) fieldCount, attrs, fieldListIdx, 0, 0, typeEntry.getSize(), typeEntry.getTypeName(), null); typeRecord = addTypeRecord(typeRecord); - if (typeEntry.isClass()) { + if (typeEntry instanceof ClassEntry classEntry) { /* Add a UDT record (if we have the information) */ - ClassEntry classEntry = (ClassEntry) typeEntry; /* * Try to find a line number for the first function - if none, don't bother to create * the record. @@ -395,7 +454,7 @@ private static short modifiersToAttr(FieldEntry member) { private CVTypeRecord.CVOneMethodRecord buildMethod(ClassEntry classEntry, MethodEntry methodEntry) { CVTypeRecord.CVTypeMFunctionRecord funcRecord = buildMemberFunction(classEntry, methodEntry); short attr = modifiersToAttr(methodEntry); - return new CVTypeRecord.CVOneMethodRecord(attr, funcRecord.getSequenceNumber(), methodEntry.getVtableOffset(), methodEntry.methodName()); + return new CVTypeRecord.CVOneMethodRecord(attr, funcRecord.getSequenceNumber(), methodEntry.getVtableOffset(), methodEntry.getMethodName()); } private static short accessToAttr(MemberEntry member) { @@ -423,8 +482,8 @@ CVTypeRecord.CVTypeMFunctionRecord buildMemberFunction(ClassEntry classEntry, Me mFunctionRecord.setFuncAttr(attr); mFunctionRecord.setReturnType(types.getIndexForPointerOrPrimitive(methodEntry.getValueType())); CVTypeRecord.CVTypeArglistRecord argListType = new CVTypeRecord.CVTypeArglistRecord(); - for (int i = 0; i < methodEntry.getParamCount(); i++) { - argListType.add(types.getIndexForPointerOrPrimitive(methodEntry.getParamType(i))); + for (TypeEntry paramType : methodEntry.getParamTypes()) { + argListType.add(types.getIndexForPointerOrPrimitive(paramType)); } argListType = addTypeRecord(argListType); mFunctionRecord.setArgList(argListType); @@ -482,7 +541,8 @@ private T addTypeRecord(T record) { } /** - * Return a CV type index for a pointer to a java type, or the type itself if a primitive. + * Return a CV type index for a pointer to a java type, or the type itself if a primitive or + * pointer type. * * @param entry The java type to return a typeindex for. If the type has not been seen, a * forward reference is generated. @@ -490,8 +550,8 @@ private T addTypeRecord(T record) { * type, the index returned is for the type, not a pointer to the type. */ int getIndexForPointerOrPrimitive(TypeEntry entry) { - if (entry.isPrimitive()) { - CVTypeRecord record = getExistingType(entry); + if (entry instanceof PrimitiveTypeEntry || entry instanceof PointerToTypeEntry) { + CVTypeRecord record = typeSection.addTypeRecords(entry); assert record != null; return record.getSequenceNumber(); } @@ -509,7 +569,7 @@ assert record != null; return ptrRecord.getSequenceNumber(); } - private int getIndexForForwardRef(ClassEntry entry) { + private int getIndexForForwardRef(StructureTypeEntry entry) { return getIndexForForwardRef(entry.getTypeName()); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java index 2a4b71183c50..1ee089173aee 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/SubstrateOptions.java @@ -513,6 +513,9 @@ public static void setImageLayerCreateEnabledHandler(OptionEnabledHandler IncludeNodeSourcePositions = new HostedOptionKey<>(false); + @Option(help = "Provide debuginfo for runtime-compiled code.")// + public static final HostedOptionKey RuntimeDebugInfo = new HostedOptionKey<>(false); + @Option(help = "Search path for C libraries passed to the linker (list of comma-separated directories)", stability = OptionStability.STABLE)// @BundleMember(role = BundleMember.Role.Input)// public static final HostedOptionKey CLibraryPath = new HostedOptionKey<>(AccumulatingLocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); @@ -1099,6 +1102,16 @@ public static boolean useDebugInfoGeneration() { return useLIRBackend() && GenerateDebugInfo.getValue() > 0; } + @Option(help = "Number of threads used to generate debug info.") // + public static final HostedOptionKey DebugInfoGenerationThreadCount = new HostedOptionKey<>(0, SubstrateOptions::validateDebugInfoGenerationThreadCount); + + private static void validateDebugInfoGenerationThreadCount(HostedOptionKey optionKey) { + int value = optionKey.getValue(); + if (value < 0) { + throw UserError.invalidOptionValue(optionKey, value, "The value must be bigger than 0"); + } + } + @Option(help = "Directory under which to create source file cache for Application or GraalVM classes")// static final HostedOptionKey DebugInfoSourceCacheRoot = new HostedOptionKey<>("sources"); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoDecoder.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoDecoder.java index f82497f7e3cc..fd16011867dc 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoDecoder.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CodeInfoDecoder.java @@ -347,7 +347,7 @@ private static long loadReferenceMapIndex(CodeInfo info, long entryOffset, int e static final int FRAME_SIZE_ENTRY_POINT = 0b010; static final int FRAME_SIZE_HAS_CALLEE_SAVED_REGISTERS = 0b100; - static final int FRAME_SIZE_STATUS_MASK = FRAME_SIZE_METHOD_START | FRAME_SIZE_ENTRY_POINT | FRAME_SIZE_HAS_CALLEE_SAVED_REGISTERS; + public static final int FRAME_SIZE_STATUS_MASK = FRAME_SIZE_METHOD_START | FRAME_SIZE_ENTRY_POINT | FRAME_SIZE_HAS_CALLEE_SAVED_REGISTERS; @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static boolean decodeIsEntryPoint(long sizeEncoding) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CompilationResultFrameTree.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CompilationResultFrameTree.java index c31ee7208676..48d2270ef190 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CompilationResultFrameTree.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/CompilationResultFrameTree.java @@ -476,8 +476,9 @@ public Builder(DebugContext debug, int targetCodeSize, int maxDepth, boolean use @SuppressWarnings("try") public CallNode build(CompilationResult compilationResult) { try (DebugContext.Scope s = debug.scope("FrameTree.Builder", compilationResult)) { - debug.log(DebugContext.VERBOSE_LEVEL, "Building FrameTree for %s", compilationResult); - + if (debug.isLogEnabled()) { + debug.log(DebugContext.VERBOSE_LEVEL, "Building FrameTree for %s", compilationResult); + } List infopoints = compilationResult.getInfopoints(); List sourceMappings = compilationResult.getSourceMappings(); List sourcePosData = new ArrayList<>(infopoints.size() + sourceMappings.size()); @@ -488,7 +489,9 @@ public CallNode build(CompilationResult compilationResult) { sourcePosData.add(wrapper); infopointForRoot = wrapper; } else { - debug.log(DebugContext.DETAILED_LEVEL, " Discard Infopoint %s", infopoint); + if (debug.isLogEnabled(DebugContext.DETAILED_LEVEL)) { + debug.log(" Discard Infopoint %s", infopoint); + } } } if (useSourceMappings) { @@ -611,7 +614,9 @@ void nullifyOverlappingSourcePositions(List sourcePosDat SourcePositionSupplier rightPos = sourcePosData.get(indexRight); if (overlappingSourcePosition(leftPos, rightPos)) { - debug.log(DebugContext.DETAILED_LEVEL, "Handle Overlapping SourcePositions: %s | %s", leftPos, rightPos); + if (debug.isLogEnabled(DebugContext.DETAILED_LEVEL)) { + debug.log("Handle Overlapping SourcePositions: %s | %s", leftPos, rightPos); + } /* Handle infopoint overlapping */ if (leftPos instanceof InfopointSourceWrapper && rightPos instanceof InfopointSourceWrapper) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/InstalledCodeObserverSupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/InstalledCodeObserverSupport.java index 8a3b92ec2b51..cb68d519897b 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/InstalledCodeObserverSupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/code/InstalledCodeObserverSupport.java @@ -27,7 +27,6 @@ import java.util.ArrayList; import java.util.List; -import jdk.graal.compiler.word.Word; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; import org.graalvm.word.Pointer; @@ -42,6 +41,7 @@ import jdk.graal.compiler.code.CompilationResult; import jdk.graal.compiler.debug.DebugContext; +import jdk.graal.compiler.word.Word; @AutomaticallyRegisteredImageSingleton public final class InstalledCodeObserverSupport { @@ -103,6 +103,7 @@ public static void attachToCurrentIsolate(NonmovableArray observerHandles) { forEach(observerHandles, ACTION_RELEASE); + clearObserverHandles(observerHandles); } private interface InstalledCodeObserverHandleAction { @@ -121,6 +122,18 @@ private static void forEach(NonmovableArray array, } } + private static void clearObserverHandles(NonmovableArray array) { + if (array.isNonNull()) { + int length = NonmovableArrays.lengthOf(array); + for (int i = 0; i < length; i++) { + InstalledCodeObserverHandle handle = NonmovableArrays.getWord(array, i); + if (handle.isNonNull()) { + NonmovableArrays.setWord(array, i, Word.nullPointer()); + } + } + } + } + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) public static void removeObserversOnTearDown(NonmovableArray observerHandles) { if (observerHandles.isNonNull()) { diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageBFDNameProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/BFDNameProvider.java similarity index 95% rename from substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageBFDNameProvider.java rename to substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/BFDNameProvider.java index e90a74dd6e52..9b0a2e95568a 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageBFDNameProvider.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/BFDNameProvider.java @@ -22,33 +22,33 @@ * or visit www.oracle.com if you need additional information or have any * questions. */ -package com.oracle.svm.hosted.image; +package com.oracle.svm.core.debug; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.List; + +import org.graalvm.collections.EconomicMap; -import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.UniqueShortNameProvider; +import com.oracle.svm.core.meta.SharedType; import com.oracle.svm.core.util.VMError; -import com.oracle.svm.hosted.c.NativeLibraries; -import com.oracle.svm.hosted.meta.HostedType; + import jdk.vm.ci.meta.JavaKind; import jdk.vm.ci.meta.JavaType; import jdk.vm.ci.meta.ResolvedJavaType; import jdk.vm.ci.meta.Signature; import jdk.vm.ci.meta.UnresolvedJavaType; -import org.graalvm.collections.EconomicMap; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Executable; -import java.lang.reflect.Member; -import java.lang.reflect.Method; -import java.lang.reflect.Parameter; -import java.util.List; /** * A unique name provider employed when debug info generation is enabled on Linux. Names are * generated in the C++ mangled format that is understood by the Linux binutils BFD library. An ELF * symbol defined using a BFD mangled name is linked to a corresponding DWARF method or field info - * declaration (DIE) by inserting it as the value of the the linkage_name attribute. In + * declaration (DIE) by inserting it as the value of the linkage_name attribute. In * order for this linkage to be correctly recognised by the debugger and Linux tools the * name attributes of the associated class, method, field, parameter types and return * type must be the same as the corresponding names encoded in the mangled name. @@ -56,15 +56,11 @@ * Note that when the class component of a mangled name needs to be qualified with a class loader id * the corresponding DWARF class record must embed the class in a namespace whose name matches the * classloader id otherwise the mangled name will not be recognised and demangled successfully. - * TODO: Namespace embedding is not yet implemented. */ -class NativeImageBFDNameProvider implements UniqueShortNameProvider { +public class BFDNameProvider implements UniqueShortNameProvider { - private NativeLibraries nativeLibs; - - NativeImageBFDNameProvider(List ignore) { + public BFDNameProvider(List ignore) { this.ignoredLoaders = ignore; - this.nativeLibs = null; } @Override @@ -113,9 +109,8 @@ private static ClassLoader getClassLoader(ResolvedJavaType type) { if (type.isArray()) { return getClassLoader(type.getElementalType()); } - if (type instanceof HostedType) { - HostedType hostedType = (HostedType) type; - return hostedType.getJavaClass().getClassLoader(); + if (type instanceof SharedType sharedType && sharedType.getHub().isLoaded()) { + return sharedType.getHub().getClassLoader(); } return null; } @@ -410,17 +405,6 @@ 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 { /* @@ -464,7 +448,7 @@ private static class BFDMangler { * arise. A name can always be correctly encoded without repeats. In the above example that * would be _ZN5Hello9compareToEJiPS_. */ - final NativeImageBFDNameProvider nameProvider; + final BFDNameProvider nameProvider; final StringBuilder sb; // A list of lookup names identifying substituted elements. A prospective name for an @@ -524,7 +508,7 @@ public String toString() { } } - BFDMangler(NativeImageBFDNameProvider provider) { + BFDMangler(BFDNameProvider provider) { nameProvider = provider; sb = new StringBuilder("_Z"); bindings = EconomicMap.create(); @@ -748,7 +732,7 @@ private void mangleType(ResolvedJavaType type) { } else { String loaderName = nameProvider.classLoaderNameAndId(type); String className = type.toJavaName(); - if (nameProvider.needsPointerPrefix(type)) { + if (needsPointerPrefix(type)) { mangleClassPointer(loaderName, className); } else { mangleClassName(loaderName, className); @@ -907,17 +891,11 @@ private void recordName(LookupName name) { * @param type The type to be checked. * @return true if the type needs to be encoded using pointer prefix P otherwise false. */ - private boolean needsPointerPrefix(ResolvedJavaType type) { - ResolvedJavaType target = type; - if (type instanceof HostedType) { - // unwrap to analysis type as that is what native libs checks test against - target = ((HostedType) target).getWrapped(); + private static boolean needsPointerPrefix(ResolvedJavaType type) { + if (type instanceof SharedType sharedType) { + /* Word types have the kind Object, but a primitive storageKind. */ + return sharedType.getJavaKind() == JavaKind.Object && sharedType.getStorageKind() == sharedType.getJavaKind(); } - if (target instanceof AnalysisType) { - assert nativeLibs != null : "No native libs during or after analysis!"; - return !nativeLibs.isWordBase(target); - } - return true; + return false; } - } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GdbJitInterface.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GdbJitInterface.java new file mode 100644 index 000000000000..04e720a4f071 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/GdbJitInterface.java @@ -0,0 +1,207 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.debug; + +import java.util.Collections; +import java.util.List; +import java.util.function.BooleanSupplier; + +import org.graalvm.nativeimage.CurrentIsolate; +import org.graalvm.nativeimage.IsolateThread; +import org.graalvm.nativeimage.c.CContext; +import org.graalvm.nativeimage.c.constant.CEnum; +import org.graalvm.nativeimage.c.constant.CEnumValue; +import org.graalvm.nativeimage.c.function.CEntryPoint; +import org.graalvm.nativeimage.c.struct.CField; +import org.graalvm.nativeimage.c.struct.CStruct; +import org.graalvm.nativeimage.c.type.CCharPointer; +import org.graalvm.nativeimage.c.type.CUnsigned; +import org.graalvm.word.PointerBase; + +import com.oracle.svm.core.NeverInline; +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.c.CGlobalData; +import com.oracle.svm.core.c.CGlobalDataFactory; +import com.oracle.svm.core.c.ProjectHeaderFile; + +import jdk.graal.compiler.word.Word; + +/** + * This interface is based on the GDB JIT + * compilation interface and contains implementations for registering and unregistering run-time + * compilations in GDB. + */ +@CContext(GdbJitInterface.GdbJitInterfaceDirectives.class) +public class GdbJitInterface { + + public static class GdbJitInterfaceDirectives implements CContext.Directives { + @Override + public boolean isInConfiguration() { + return SubstrateOptions.RuntimeDebugInfo.getValue(); + } + + @Override + public List getHeaderFiles() { + return Collections.singletonList(ProjectHeaderFile.resolve("", "include/gdb_jit_compilation_interface.h")); + } + } + + private static final class IncludeForRuntimeDebugOnly implements BooleanSupplier { + @Override + public boolean getAsBoolean() { + return SubstrateOptions.RuntimeDebugInfo.getValue(); + } + } + + @CEnum(value = "jit_actions_t") + public enum JITActions { + JIT_NOACTION, + JIT_REGISTER, + JIT_UNREGISTER; + + @CEnumValue + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public native int getCValue(); + } + + @CStruct(value = "jit_code_entry", addStructKeyword = true) + public interface JITCodeEntry extends PointerBase { + // struct jit_code_entry *next_entry; + @CField("next_entry") + JITCodeEntry getNextEntry(); + + @CField("next_entry") + void setNextEntry(JITCodeEntry jitCodeEntry); + + // struct jit_code_entry *prev_entry; + @CField("prev_entry") + JITCodeEntry getPrevEntry(); + + @CField("prev_entry") + void setPrevEntry(JITCodeEntry jitCodeEntry); + + // const char *symfile_addr; + @CField("symfile_addr") + CCharPointer getSymfileAddr(); + + @CField("symfile_addr") + void setSymfileAddr(CCharPointer symfileAddr); + + // uint64_t symfile_size; + @CField("symfile_size") + @CUnsigned + long getSymfileSize(); + + @CField("symfile_size") + void setSymfileSize(@CUnsigned long symfileSize); + } + + @CStruct(value = "jit_descriptor", addStructKeyword = true) + public interface JITDescriptor extends PointerBase { + // uint32_t version; + @CField("version") + @CUnsigned + int getVersion(); + + @CField("version") + void setVersion(@CUnsigned int version); + + // uint32_t action_flag; + @CField("action_flag") + @CUnsigned + int getActionFlag(); + + @CField("action_flag") + void setActionFlag(@CUnsigned int actionFlag); + + // struct jit_code_entry *relevant_entry; + @CField("relevant_entry") + JITCodeEntry getRelevantEntry(); + + @CField("relevant_entry") + void setRelevantEntry(JITCodeEntry jitCodeEntry); + + // struct jit_code_entry *first_entry; + @CField("first_entry") + JITCodeEntry getFirstEntry(); + + @CField("first_entry") + void setFirstEntry(JITCodeEntry jitCodeEntry); + } + + @NeverInline("Register JIT code stub for GDB.") + @CEntryPoint(name = "__jit_debug_register_code", include = IncludeForRuntimeDebugOnly.class, publishAs = CEntryPoint.Publish.SymbolOnly) + private static void jitDebugRegisterCode(@SuppressWarnings("unused") IsolateThread thread) { + } + + private static final CGlobalData jitDebugDescriptor = CGlobalDataFactory.forSymbol("__jit_debug_descriptor"); + + public static void registerJITCode(CCharPointer addr, @CUnsigned long size, JITCodeEntry entry) { + /* Create new jit_code_entry */ + entry.setSymfileAddr(addr); + entry.setSymfileSize(size); + + /* Insert entry at head of the list. */ + JITCodeEntry nextEntry = jitDebugDescriptor.get().getFirstEntry(); + entry.setPrevEntry(Word.nullPointer()); + entry.setNextEntry(nextEntry); + + if (nextEntry.isNonNull()) { + nextEntry.setPrevEntry(entry); + } + + /* Notify GDB. */ + jitDebugDescriptor.get().setActionFlag(JITActions.JIT_REGISTER.getCValue()); + jitDebugDescriptor.get().setFirstEntry(entry); + jitDebugDescriptor.get().setRelevantEntry(entry); + jitDebugRegisterCode(CurrentIsolate.getCurrentThread()); + } + + @Uninterruptible(reason = "Called with raw object pointer.", calleeMustBe = false) + public static void unregisterJITCode(JITCodeEntry entry) { + JITCodeEntry prevEntry = entry.getPrevEntry(); + JITCodeEntry nextEntry = entry.getNextEntry(); + + /* Fix prev and next in list */ + if (nextEntry.isNonNull()) { + nextEntry.setPrevEntry(prevEntry); + } + + if (prevEntry.isNonNull()) { + prevEntry.setNextEntry(nextEntry); + } else { + assert (jitDebugDescriptor.get().getFirstEntry().equal(entry)); + jitDebugDescriptor.get().setFirstEntry(nextEntry); + } + + /* Notify GDB. */ + jitDebugDescriptor.get().setActionFlag(JITActions.JIT_UNREGISTER.getCValue()); + jitDebugDescriptor.get().setRelevantEntry(entry); + jitDebugRegisterCode(CurrentIsolate.getCurrentThread()); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SharedDebugInfoProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SharedDebugInfoProvider.java new file mode 100644 index 000000000000..2f30433fb2f5 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SharedDebugInfoProvider.java @@ -0,0 +1,1769 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.debug; + +import java.lang.reflect.Modifier; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; + +import org.graalvm.collections.Pair; +import org.graalvm.word.WordBase; + +import com.oracle.objectfile.ObjectFile; +import com.oracle.objectfile.debugentry.ArrayTypeEntry; +import com.oracle.objectfile.debugentry.ClassEntry; +import com.oracle.objectfile.debugentry.CompiledMethodEntry; +import com.oracle.objectfile.debugentry.ConstantValueEntry; +import com.oracle.objectfile.debugentry.DirEntry; +import com.oracle.objectfile.debugentry.EnumClassEntry; +import com.oracle.objectfile.debugentry.FieldEntry; +import com.oracle.objectfile.debugentry.FileEntry; +import com.oracle.objectfile.debugentry.ForeignStructTypeEntry; +import com.oracle.objectfile.debugentry.FrameSizeChangeEntry; +import com.oracle.objectfile.debugentry.HeaderTypeEntry; +import com.oracle.objectfile.debugentry.InterfaceClassEntry; +import com.oracle.objectfile.debugentry.LoaderEntry; +import com.oracle.objectfile.debugentry.LocalEntry; +import com.oracle.objectfile.debugentry.LocalValueEntry; +import com.oracle.objectfile.debugentry.MemberEntry; +import com.oracle.objectfile.debugentry.MethodEntry; +import com.oracle.objectfile.debugentry.PointerToTypeEntry; +import com.oracle.objectfile.debugentry.PrimitiveTypeEntry; +import com.oracle.objectfile.debugentry.RegisterValueEntry; +import com.oracle.objectfile.debugentry.StackValueEntry; +import com.oracle.objectfile.debugentry.StructureTypeEntry; +import com.oracle.objectfile.debugentry.TypeEntry; +import com.oracle.objectfile.debugentry.range.CallRange; +import com.oracle.objectfile.debugentry.range.LeafRange; +import com.oracle.objectfile.debugentry.range.PrimaryRange; +import com.oracle.objectfile.debugentry.range.Range; +import com.oracle.objectfile.debuginfo.DebugInfoProvider; +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.UniqueShortNameProvider; +import com.oracle.svm.core.code.CompilationResultFrameTree; +import com.oracle.svm.core.config.ConfigurationValues; +import com.oracle.svm.core.config.ObjectLayout; +import com.oracle.svm.core.graal.code.SubstrateBackend; +import com.oracle.svm.core.graal.code.SubstrateCallingConvention; +import com.oracle.svm.core.graal.code.SubstrateCallingConventionKind; +import com.oracle.svm.core.graal.code.SubstrateCallingConventionType; +import com.oracle.svm.core.graal.meta.RuntimeConfiguration; +import com.oracle.svm.core.graal.meta.SubstrateRegisterConfig; +import com.oracle.svm.core.heap.Heap; +import com.oracle.svm.core.heap.ReferenceAccess; +import com.oracle.svm.core.meta.SharedMethod; +import com.oracle.svm.core.meta.SharedType; + +import jdk.graal.compiler.api.replacements.Fold; +import jdk.graal.compiler.code.CompilationResult; +import jdk.graal.compiler.core.common.CompilationIdentifier; +import jdk.graal.compiler.core.target.Backend; +import jdk.graal.compiler.debug.DebugContext; +import jdk.graal.compiler.graph.NodeSourcePosition; +import jdk.graal.compiler.util.Digest; +import jdk.vm.ci.code.BytecodeFrame; +import jdk.vm.ci.code.BytecodePosition; +import jdk.vm.ci.code.CallingConvention; +import jdk.vm.ci.code.RegisterConfig; +import jdk.vm.ci.code.RegisterValue; +import jdk.vm.ci.code.StackSlot; +import jdk.vm.ci.code.ValueUtil; +import jdk.vm.ci.meta.AllocatableValue; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.JavaType; +import jdk.vm.ci.meta.JavaValue; +import jdk.vm.ci.meta.LineNumberTable; +import jdk.vm.ci.meta.Local; +import jdk.vm.ci.meta.LocalVariableTable; +import jdk.vm.ci.meta.MetaAccessProvider; +import jdk.vm.ci.meta.PrimitiveConstant; +import jdk.vm.ci.meta.ResolvedJavaField; +import jdk.vm.ci.meta.ResolvedJavaMethod; +import jdk.vm.ci.meta.ResolvedJavaType; +import jdk.vm.ci.meta.Signature; + +/** + * A shared base for providing debug info that can be processed by any debug info format specified + * in {@link ObjectFile}. Implements most of the {@link DebugInfoProvider} interface. + * + *

    + * Debug Info is provided as debug entries, with each entry containing debug info for a logical + * unit, i.e. Types, ClassLoaders, Methods, Fields, Compilations as well as the underlying file + * system. Debug entries model the hosted universe to provide a normalized view on debug info data + * for all debug info implementations. + *

    + * + *
      + * The Debug Info primarily consists of the following debug entries: + *
    • {@link DirEntry}: Represents a parent directory for one or more file entries, based on the + * debug sources directory. One root directory entry contains the path to the debug sources.
    • + *
    • {@link FileEntry}: Represents a single source file, which may be used by the debugger to + * provide source code. During native-image build, the + * {@code com.oracle.svm.hosted.image.sources.SourceManager} safes all the processed source files + * and provides the debug sources directory.
    • + *
    • {@link LoaderEntry}: Represents a {@link ClassLoader}. Built-in class loaders and image + * classloaders are not stored as loader entries, because they are implicitly inferred for types + * with no {@code LoaderEntry}.
    • + *
    • {@link TypeEntry}: Represents a {@link SharedType}. For native image build time debug info + * there exists one {@code TypeEntry} per type in the hosted universe. Type entries are divided into + * following categories: + *
        + *
      • {@link PrimitiveTypeEntry}: Represents a primitive java type or any other word type.
      • + *
      • {@link HeaderTypeEntry}: A special {@code TypeEntry} that represents the object header + * information in the native image heap, as sort of a super type to {@link Object}.
      • + *
      • {@link ArrayTypeEntry}: Represents an array type.
      • + *
      • {@link ForeignStructTypeEntry}: Represents a structured type that is not a java class, e.g. + * {@link org.graalvm.nativeimage.c.struct.CStruct CStruct}, + *
      • {@link PointerToTypeEntry}: Represents a pointer type, e.g. + * {@link org.graalvm.nativeimage.c.struct.CPointerTo CPointerTo}, ... .
      • + *
      • {@link EnumClassEntry}: Represents an {@link Enum} class.
      • + *
      • {@link InterfaceClassEntry}: Represents an interface, and stores references to all + * implementors known at the time of debug info generation.
      • + *
      • {@link ClassEntry}: Represents any other java class that is not already covered by other type + * entries (Instance classes).
      • + *
      + *
    • + *
    • {@link MethodEntry}: Represents the method declaration of a {@link SharedMethod} and holds a + * list of all parameters and locals that are used within the method. Initially the list of locals + * in a {@code MethodEntry} contains locals from the method's {@code LocalVariableTable} and is + * extended if other locals are found when processing a compilation for the method.
    • + *
    • {@link CompiledMethodEntry}: Represents a {@link CompilationResult}. Is composed of ranges, + * i.e. frame states and location information of params and locals (where variables are stored). A + * {@code CompiledMethodEntry} always has a {@link PrimaryRange} that spans the whole code range of + * the compilation, which is further composed of: + *
        + *
      • {@link LeafRange}: A leaf in the {@link CompilationResultFrameTree}.
      • + *
      • {@link CallRange}: A {@code CallNode} in the {@link CompilationResultFrameTree}. Represents + * inlined calls and is therefore itself composed of ranges.
      • + *
      + *
    • + *
    + */ +public abstract class SharedDebugInfoProvider implements DebugInfoProvider { + + protected final RuntimeConfiguration runtimeConfiguration; + + protected final MetaAccessProvider metaAccess; + + protected final DebugContext debug; + + protected final boolean useHeapBase; + protected final int compressionShift; + protected final int referenceSize; + protected final int pointerSize; + protected final int objectAlignment; + protected final int reservedHubBitsMask; + + /** + * The {@code SharedType} for {@link Class}. This is the type that represents a dynamic hub in + * the native image. + */ + protected final SharedType hubType; + + /** + * The {@code SharedType} for {@link WordBase}. This is used to check for foreign types. + */ + protected final SharedType wordBaseType; + + /** + * The {@code SharedType} for {@code void}. This is used as fallback for foreign pointer types, + * if there is no type it points to. + */ + protected final SharedType voidType; + + /** + * An index map that holds all unique dir entries used for file entries in the + * {@link #fileIndex}. + */ + private final ConcurrentHashMap dirIndex = new ConcurrentHashMap<>(); + + /** + * An index map that holds all unique file entries used for type entries and method entries in + * the {@link #typeIndex} and {@link #methodIndex}. + */ + private final ConcurrentHashMap fileIndex = new ConcurrentHashMap<>(); + + /** + * An index map that holds all unique loader entries used for type entries in the + * {@link #typeIndex}. + */ + private final ConcurrentHashMap loaderIndex = new ConcurrentHashMap<>(); + + /** + * An index map that holds all unique type entries except the {@link #headerTypeEntry}. + */ + private final ConcurrentHashMap typeIndex = new ConcurrentHashMap<>(); + + /** + * An index map that holds all unique method entries used for type entries compiled method + * entries in the {@link #typeIndex} and {@link #compiledMethodIndex}. + */ + private final ConcurrentHashMap methodIndex = new ConcurrentHashMap<>(); + + /** + * An index map that holds all unique compiled method entries. + */ + private final ConcurrentHashMap compiledMethodIndex = new ConcurrentHashMap<>(); + + /** + * The header type entry which is used as a super class of {@link Object} in the debug info. It + * describes the object header of an object in the native image. + */ + private HeaderTypeEntry headerTypeEntry; + + /** + * A prefix used to label indirect types used to ensure gdb performs oop reference to raw + * address translation. + */ + public static final String INDIRECT_PREFIX = "_z_."; + + /** + * A prefix used for type signature generation with {@link #getTypeSignature} to generate unique + * type signatures for layout type units. + */ + public static final String LAYOUT_PREFIX = "_layout_."; + + /** + * A prefix used for type signature generation with {@link #getTypeSignature} to generate unique + * type signatures for foreign primitive type units. + */ + public static final String FOREIGN_PREFIX = "_foreign_."; + + public static final String FOREIGN_METHOD_LIST_TYPE = "Foreign$Method$List"; + + static final Path EMPTY_PATH = Paths.get(""); + + static final LoaderEntry NULL_LOADER_ENTRY = new LoaderEntry(""); + + /** + * A class entry that holds all compilations for function pointers. + */ + private final ClassEntry foreignMethodListClassEntry = new ClassEntry(FOREIGN_METHOD_LIST_TYPE, -1, -1, -1, -1, -1, null, null, NULL_LOADER_ENTRY); + + public SharedDebugInfoProvider(DebugContext debug, RuntimeConfiguration runtimeConfiguration, MetaAccessProvider metaAccess) { + this.runtimeConfiguration = runtimeConfiguration; + this.metaAccess = metaAccess; + + /* + * Use a disabled DebugContext if log is disabled here. We need to make sure the log stays + * disabled, as we use parallel streams if it is disabled + */ + this.debug = debug.isLogEnabledForMethod() ? debug : DebugContext.disabled(null); + + // Fetch special types that have special use cases. + // hubType: type of the 'hub' field in the object header. + // wordBaseType: for checking for foreign types. + // voidType: fallback type to point to for foreign pointer types + this.hubType = (SharedType) metaAccess.lookupJavaType(Class.class); + this.wordBaseType = (SharedType) metaAccess.lookupJavaType(WordBase.class); + this.voidType = (SharedType) metaAccess.lookupJavaType(void.class); + + // Get some information on heap layout and object/object header layout + this.useHeapBase = ReferenceAccess.singleton().haveCompressedReferences() && ReferenceAccess.singleton().getCompressEncoding().hasBase(); + this.compressionShift = ReferenceAccess.singleton().getCompressionShift(); + this.pointerSize = ConfigurationValues.getTarget().wordSize; + this.referenceSize = getObjectLayout().getReferenceSize(); + this.objectAlignment = getObjectLayout().getAlignment(); + this.reservedHubBitsMask = Heap.getHeap().getObjectHeader().getReservedHubBitsMask(); + } + + /** + * Provides a stream of shared types that are processed in {@link #installDebugInfo}. + * + * @return A stream of all {@code SharedType} objects to process + */ + protected abstract Stream typeInfo(); + + /** + * Provides a stream of shared method and compilation pairs that are processed in + * {@link #installDebugInfo}. + * + * @return A stream of all compilations to process. + */ + protected abstract Stream> codeInfo(); + + /** + * Provides a stream of data objects that are processed in {@link #installDebugInfo}. + * + * @return A stream of all data objects to process. + */ + protected abstract Stream dataInfo(); + + protected abstract long getCodeOffset(SharedMethod method); + + public abstract long objectOffset(JavaConstant constant); + + @Override + public boolean useHeapBase() { + return useHeapBase; + } + + @Override + public boolean isRuntimeCompilation() { + return false; + } + + @Override + public int reservedHubBitsMask() { + return reservedHubBitsMask; + } + + @Override + public int compressionShift() { + return compressionShift; + } + + @Override + public int referenceSize() { + return referenceSize; + } + + @Override + public int pointerSize() { + return pointerSize; + } + + @Override + public int objectAlignment() { + return objectAlignment; + } + + /** + * Collect all type entries in {@link #typeIndex} plus the {@link #headerTypeEntry} sorted by + * type signature. + * + *

    + * This method only gets called after all reachable types, fields, and methods are processed and + * all type entries have been created. + * + *

    + * This ensures that type entries are ordered when processed for the debug info object file. + * + * @return A {@code SortedSet} of all type entries found and registered in + * {@link #installDebugInfo}. + */ + @Override + public SortedSet typeEntries() { + SortedSet typeEntries = new TreeSet<>(Comparator.comparingLong(TypeEntry::getTypeSignature)); + /* + * The header type entry does not have an underlying HostedType, so we cant put it into the + * type index and have to add it manually. + */ + typeEntries.add(headerTypeEntry); + typeEntries.add(foreignMethodListClassEntry); + + for (TypeEntry typeEntry : typeIndex.values()) { + typeEntries.add(typeEntry); + + // types processed after analysis might be missed otherwise + if (typeEntry instanceof PointerToTypeEntry pointerToTypeEntry) { + typeEntries.add(pointerToTypeEntry.getPointerTo()); + } else if (typeEntry instanceof ForeignStructTypeEntry foreignStructTypeEntry && foreignStructTypeEntry.getParent() != null) { + typeEntries.add(foreignStructTypeEntry.getParent()); + } + } + + return typeEntries; + } + + /** + * Collect all compiled method entries in {@link #compiledMethodIndex} sorted by address of + * their primary range and the owner class' type signature. + * + *

    + * This method only gets called after all compilations are processed and all compiled method + * entries have been created. + * + *

    + * This ensures that compiled method entries are ordered when processed for the debug info + * object file. + * + * @return A {@code SortedSet} of all compiled method entries found and registered in + * {@link #installDebugInfo}. + */ + @Override + public SortedSet compiledMethodEntries() { + SortedSet compiledMethodEntries = new TreeSet<>( + Comparator.comparing(CompiledMethodEntry::primary).thenComparingLong(compiledMethodEntry -> compiledMethodEntry.ownerType().getTypeSignature())); + + compiledMethodEntries.addAll(compiledMethodIndex.values()); + + return compiledMethodEntries; + } + + /** + * This installs debug info into the index maps for all entries in {@link #typeInfo}, + * {@link #codeInfo}, and {@link #dataInfo}. + * + *

    + * If logging with a {@link DebugContext} is enabled, this is done sequential, otherwise in + * parallel. + */ + @Override + @SuppressWarnings("try") + public void installDebugInfo() { + // we can only meaningfully provide logging if debug info is produced sequentially + Stream typeStream = debug.isLogEnabledForMethod() ? typeInfo() : typeInfo().parallel(); + Stream> codeStream = debug.isLogEnabledForMethod() ? codeInfo() : codeInfo().parallel(); + Stream dataStream = debug.isLogEnabledForMethod() ? dataInfo() : dataInfo().parallel(); + + try (DebugContext.Scope s = debug.scope("DebugInfoProvider")) { + // Create and index an empty dir with index 0 for null paths. + lookupDirEntry(EMPTY_PATH); + + /* + * Handle types, compilations and data. Code info needs to be handled first as it + * contains source file infos of compilations which are collected in the class entry. + */ + codeStream.forEach(pair -> handleCodeInfo(pair.getLeft(), pair.getRight())); + typeStream.forEach(this::installTypeInfo); + dataStream.forEach(this::installDataInfo); + + // Create the header type. + installTypeInfo(null); + } catch (Throwable e) { + throw debug.handle(e); + } + } + + /** + * Installs debug info for any given data info. For AOT debug info generation this is used to + * log information for objects in the native image heap. + * + * @param data The data info to process. + */ + protected void installDataInfo(@SuppressWarnings("unused") Object data) { + // by default, we do not use data info for installing debug info + } + + /** + * Installs debug info for a given type info. A type info must be a {@code SharedType}. + * + *

    + * This ensures that the type info is processed and a {@link TypeEntry} is put in the type + * index. + * + * @param type The {@code SharedType} to process. + */ + private void installTypeInfo(SharedType type) { + /* + * Looking up a type will either return the existing type in the index map or create and + * process a new type entry. + */ + lookupTypeEntry(type); + } + + /** + * Installs debug info for a {@code CompilationResult} and the underlying {@code SharedMethod}. + * + *

    + * This ensures that the compilation is processed and a {@link MethodEntry} and a + * {@link CompiledMethodEntry} are put into the method index and compiled method index + * respectively. + * + *

    + * A compilation is processed for its frame states from infopoints/sourcemappings. For + * performance reasons we mostly only use infopoints for processing compilations. + * + * @param method The {@code SharedMethod} to process. + * @param compilation The {@code CompilationResult} to process + */ + private void handleCodeInfo(SharedMethod method, CompilationResult compilation) { + // First make sure the underlying MethodEntry exists. + MethodEntry methodEntry = lookupMethodEntry(method); + // Then process the compilation. + lookupCompiledMethodEntry(methodEntry, method, compilation); + } + + @Fold + static boolean omitInline() { + return SubstrateOptions.OmitInlinedMethodDebugLineInfo.getValue(); + } + + @Fold + static int debugCodeInfoMaxDepth() { + return SubstrateOptions.DebugCodeInfoMaxDepth.getValue(); + } + + @Fold + static boolean debugCodeInfoUseSourceMappings() { + return SubstrateOptions.DebugCodeInfoUseSourceMappings.getValue(); + } + + /** + * Creates a {@code CompiledMethodEntry} that holds a {@link PrimaryRange} and still needs to be + * processed in {@link #processCompilationInfo}. + * + * @param methodEntry the {@code MethodEntry} of the method + * @param method the {@code SharedMethod} of the given compilation + * @param compilation the given {@code CompilationResult}. + * @return an unprocessed {@code CompiledMethodEntry}. + */ + protected CompiledMethodEntry createCompilationInfo(MethodEntry methodEntry, SharedMethod method, CompilationResult compilation) { + int primaryLine = methodEntry.getLine(); + int frameSize = compilation.getTotalFrameSize(); + List frameSizeChanges = getFrameSizeChanges(compilation); + ClassEntry ownerType = methodEntry.getOwnerType(); + + // Create a primary range that spans over the compilation. + // The primary range entry holds the code offset information for all its sub ranges. + PrimaryRange primaryRange = Range.createPrimary(methodEntry, 0, compilation.getTargetCodeSize(), primaryLine, getCodeOffset(method)); + if (debug.isLogEnabled()) { + debug.log(DebugContext.INFO_LEVEL, "PrimaryRange %s.%s %s %s:%d [0x%x, 0x%x]", ownerType.getTypeName(), methodEntry.getMethodName(), primaryRange.getFileEntry().getPathName(), + primaryRange.getFileName(), primaryLine, primaryRange.getLo(), primaryRange.getHi()); + } + + return new CompiledMethodEntry(primaryRange, frameSizeChanges, frameSize, ownerType); + } + + /** + * Processes a {@code CompiledMethodEntry} created in {@link #createCompilationInfo}. + * + * @param methodEntry the {@code MethodEntry} of the method + * @param method the {@code SharedMethod} of the given compilation + * @param compilation the given {@code CompilationResult} + * @param compiledMethodEntry the {@code CompiledMethodEntry} to process + */ + protected void processCompilationInfo(MethodEntry methodEntry, SharedMethod method, CompilationResult compilation, CompiledMethodEntry compiledMethodEntry) { + // Mark the method entry for the compilation. + methodEntry.setInRange(); + + // If the compiled method entry was added, we still need to check the frame states + // for subranges. + + // Can we still provide locals if we have no file name? + if (methodEntry.getFileName().isEmpty()) { + return; + } + + // Restrict the frame state traversal based on options. + boolean omitInline = omitInline(); + int maxDepth = debugCodeInfoMaxDepth(); + boolean useSourceMappings = debugCodeInfoUseSourceMappings(); + if (omitInline) { + /* TopLevelVisitor will not go deeper than level 2 */ + maxDepth = 2; + } + + // The root node is represented by the primary range. + // A call nodes in the frame tree will be stored as call ranges and leaf nodes as + // leaf ranges + final CompilationResultFrameTree.CallNode root = new CompilationResultFrameTree.Builder(debug, compilation.getTargetCodeSize(), maxDepth, useSourceMappings, + true) + .build(compilation); + if (root == null) { + return; + } + PrimaryRange primaryRange = compiledMethodEntry.primary(); + int frameSize = compiledMethodEntry.frameSize(); + final List subRanges = new ArrayList<>(); + // The top level visitor will only traverse the direct children of the primary + // range. All sub call ranges will be treated as leaf ranges. + final CompilationResultFrameTree.Visitor visitor = omitInline ? new TopLevelVisitor(subRanges, frameSize, primaryRange) : new MultiLevelVisitor(subRanges, frameSize, primaryRange); + // arguments passed by visitor to apply are + // NativeImageDebugLocationInfo caller location info + // CallNode nodeToEmbed parent call node to convert to entry code leaf + // NativeImageDebugLocationInfo leaf into which current leaf may be merged + root.visitChildren(visitor, primaryRange, null, null); + // try to add a location record for offset zero + updateInitialLocation(primaryRange, subRanges, compilation, method, methodEntry); + + methodEntry.getOwnerType().addCompiledMethod(compiledMethodEntry); + } + + /** + * Installs a compilation info that was not found in {@link #lookupCompiledMethodEntry}. + * + * @param methodEntry the {@code MethodEntry} of the method + * @param method the {@code SharedMethod} of the given compilation + * @param compilation the given {@code CompilationResult} + * @return the fully processed {@code CompiledMethodEntry} for the compilation. + */ + @SuppressWarnings("try") + private CompiledMethodEntry installCompilationInfo(MethodEntry methodEntry, SharedMethod method, CompilationResult compilation) { + try (DebugContext.Scope s = debug.scope("DebugInfoCompilation")) { + if (debug.isLogEnabled()) { + debug.log(DebugContext.INFO_LEVEL, "Register compilation %s ", compilation.getName()); + } + + CompiledMethodEntry compiledMethodEntry = createCompilationInfo(methodEntry, method, compilation); + if (compiledMethodIndex.putIfAbsent(compilation.getCompilationId(), compiledMethodEntry) == null) { + // CompiledMethodEntry was added to the index, now we need to process the + // compilation. + if (debug.isLogEnabled()) { + debug.log(DebugContext.INFO_LEVEL, "Process compilation %s ", compilation.getName()); + } + processCompilationInfo(methodEntry, method, compilation, compiledMethodEntry); + return compiledMethodEntry; + } else { + // The compilation entry was created in the meantime, so we return the one unique + // type. + return compiledMethodIndex.get(compilation.getCompilationId()); + } + } catch (Throwable e) { + throw debug.handle(e); + } + } + + /** + * Collect all frame size changes as list of {@code FrameSizeChangeEntry} for a compilation. + * + * @param compilation the given {@code CompilationResult} + * @return a list of relevant frame size changes. + */ + public List getFrameSizeChanges(CompilationResult compilation) { + List frameSizeChanges = new ArrayList<>(); + for (CompilationResult.CodeMark mark : compilation.getMarks()) { + /* We only need to observe stack increment or decrement points. */ + if (mark.id.equals(SubstrateBackend.SubstrateMarkId.PROLOGUE_DECD_RSP)) { + FrameSizeChangeEntry sizeChange = new FrameSizeChangeEntry(mark.pcOffset, FrameSizeChangeType.EXTEND); + frameSizeChanges.add(sizeChange); + // } else if (mark.id.equals("PROLOGUE_END")) { + // can ignore these + // } else if (mark.id.equals("EPILOGUE_START")) { + // can ignore these + } else if (mark.id.equals(SubstrateBackend.SubstrateMarkId.EPILOGUE_INCD_RSP)) { + FrameSizeChangeEntry sizeChange = new FrameSizeChangeEntry(mark.pcOffset, FrameSizeChangeType.CONTRACT); + frameSizeChanges.add(sizeChange); + } else if (mark.id.equals(SubstrateBackend.SubstrateMarkId.EPILOGUE_END) && mark.pcOffset < compilation.getTargetCodeSize()) { + /* There is code after this return point so notify a stack extend again. */ + FrameSizeChangeEntry sizeChange = new FrameSizeChangeEntry(mark.pcOffset, FrameSizeChangeType.EXTEND); + frameSizeChanges.add(sizeChange); + } + } + return frameSizeChanges; + } + + /** + * Installs a method info that was not found in {@link #lookupMethodEntry}. + * + * @param method the {@code SharedMethod} to process + * @return the corresponding {@code MethodEntry} + */ + @SuppressWarnings("try") + private MethodEntry installMethodEntry(SharedMethod method) { + try (DebugContext.Scope s = debug.scope("DebugInfoMethod")) { + FileEntry fileEntry = lookupFileEntry(method); + + LineNumberTable lineNumberTable = method.getLineNumberTable(); + int line = lineNumberTable == null ? 0 : lineNumberTable.getLineNumber(0); + + String methodName = getMethodName(method); + TypeEntry t = lookupTypeEntry((SharedType) method.getDeclaringClass()); + if (!(t instanceof StructureTypeEntry)) { + // we can only install a foreign function pointer for a structured type + // use a dummy type to process function pointers + assert t instanceof PointerToTypeEntry; + t = foreignMethodListClassEntry; + } + StructureTypeEntry ownerType = (StructureTypeEntry) t; + TypeEntry valueType = lookupTypeEntry((SharedType) method.getSignature().getReturnType(null)); + int modifiers = method.getModifiers(); + + /* + * Check the local variable table for parameters. If the params are not in the table, we + * create synthetic ones from the method signature. + */ + SortedSet paramInfos = getParamEntries(method, line); + int lastParamSlot = paramInfos.isEmpty() ? -1 : paramInfos.getLast().slot(); + LocalEntry thisParam = Modifier.isStatic(modifiers) ? null : paramInfos.removeFirst(); + + // look for locals in the methods local variable table + List locals = getLocalEntries(method, lastParamSlot); + + String symbolName = getSymbolName(method); + int vTableOffset = getVTableOffset(method); + + boolean isOverride = isOverride(method); + boolean isDeopt = method.isDeoptTarget(); + boolean isConstructor = method.isConstructor(); + + return methodIndex.computeIfAbsent(method, m -> new MethodEntry(fileEntry, line, methodName, ownerType, + valueType, modifiers, paramInfos, thisParam, symbolName, isDeopt, isOverride, isConstructor, + vTableOffset, lastParamSlot, locals)); + } catch (Throwable e) { + throw debug.handle(e); + } + } + + /** + * Installs a type info that was not found in {@link #lookupTypeEntry}. + * + * @param type the {@code SharedType} to process + * @return a fully processed {@code TypeEntry} + */ + @SuppressWarnings("try") + private TypeEntry installTypeEntry(SharedType type) { + try (DebugContext.Scope s = debug.scope("DebugInfoType")) { + if (debug.isLogEnabled()) { + debug.log(DebugContext.INFO_LEVEL, "Register type %s ", type.getName()); + } + + TypeEntry typeEntry = createTypeEntry(type); + if (typeIndex.putIfAbsent(type, typeEntry) == null) { + // TypeEntry was added to the type index, now we need to process the type. + if (debug.isLogEnabled()) { + debug.log(DebugContext.INFO_LEVEL, "Process type %s ", type.getName()); + } + processTypeEntry(type, typeEntry); + return typeEntry; + } else { + // The type entry was created in the meantime, so we return the one unique type. + return typeIndex.get(type); + } + } catch (Throwable e) { + throw debug.handle(e); + } + } + + /** + * Creates a {@code TypeEntry} that needs to be processed with {@link #processTypeEntry}. + * + * @param type the {@code SharedType} to process + * @return an unprocessed {@code TypeEntry} + */ + protected abstract TypeEntry createTypeEntry(SharedType type); + + /** + * Processes a {@code TypeEntry} created in {@link #createTypeEntry}. + * + * @param type the {@code SharedType} of the type entry + * @param typeEntry the {@code TypeEntry} to process + */ + protected abstract void processTypeEntry(SharedType type, TypeEntry typeEntry); + + /** + * Installs the header type info. This is a made up {@link TypeEntry} that has no underlying + * {@link SharedType} and represents the {@link ObjectLayout}. + */ + private void installHeaderTypeEntry() { + String typeName = "_objhdr"; + long typeSignature = getTypeSignature(typeName); + + // create the header type entry similar to a class entry without a super type + ObjectLayout ol = getObjectLayout(); + int hubOffset = ol.getHubOffset(); + headerTypeEntry = new HeaderTypeEntry(typeName, ol.getFirstFieldOffset(), typeSignature); + headerTypeEntry.setHubField(createSyntheticFieldEntry("hub", headerTypeEntry, hubType, hubOffset, ol.getHubSize())); + + if (hubOffset > 0) { + assert hubOffset == Integer.BYTES || hubOffset == Long.BYTES; + JavaKind kind = hubOffset == Integer.BYTES ? JavaKind.Int : JavaKind.Long; + headerTypeEntry.addField(createSyntheticFieldEntry("reserved", headerTypeEntry, (SharedType) metaAccess.lookupJavaType(kind.toJavaClass()), 0, hubOffset)); + } + } + + /** + * Create a synthetic field for the debug info to add additional information as fields in the + * debug info. + * + * @param name name of the field + * @param ownerType {@code StructureTypeEntry} that owns the field + * @param type {@code TypeEntry} of the fields type + * @param offset offset of the field + * @param size size of the field + * @return a {@code FieldEntry} that represents the synthetic field + */ + protected FieldEntry createSyntheticFieldEntry(String name, StructureTypeEntry ownerType, SharedType type, int offset, int size) { + TypeEntry typeEntry = lookupTypeEntry(type); + if (debug.isLogEnabled()) { + debug.log("typename %s adding synthetic (public) field %s type %s size %d at offset 0x%x%n", + ownerType.getTypeName(), name, typeEntry.getTypeName(), size, offset); + } + return new FieldEntry(null, name, ownerType, typeEntry, size, offset, false, Modifier.PUBLIC); + } + + /** + * Create a {@code FieldEntry} for a field of a structured type. + * + * @param fileEntry {@code FileEntry} of the source file, where the field is declared + * @param name name of the field + * @param ownerType {@code StructureTypeEntry} that owns the field + * @param type {@code TypeEntry} of the fields type + * @param offset offset of the field + * @param size size of the field + * @return a {@code FieldEntry} that represents the synthetic field + */ + protected FieldEntry createFieldEntry(FileEntry fileEntry, String name, StructureTypeEntry ownerType, SharedType type, int offset, int size, boolean isEmbedded, int modifier) { + TypeEntry typeEntry = lookupTypeEntry(type); + if (debug.isLogEnabled()) { + debug.log("typename %s adding %s field %s type %s%s size %d at offset 0x%x%n", + ownerType.getTypeName(), MemberEntry.memberModifiers(modifier), name, typeEntry.getTypeName(), (isEmbedded ? "(embedded)" : ""), size, offset); + } + return new FieldEntry(fileEntry, name, ownerType, typeEntry, size, offset, isEmbedded, modifier); + } + + /** + * Produces a signature for a type name. + * + * @param typeName name of a type + * @return the signature for the type name + */ + public static long getTypeSignature(String typeName) { + return Digest.digestAsUUID(typeName).getLeastSignificantBits(); + } + + /** + * Produce a method name of a {@code SharedMethod} for the debug info. + * + * @param method method to produce a name for + * @return method name for the debug info + */ + protected String getMethodName(SharedMethod method) { + String name = method.getName(); + // replace (method name of a constructor) with the class name + if (method.isConstructor()) { + name = method.format("%h"); + if (name.indexOf('$') >= 0) { + name = name.substring(name.lastIndexOf('$') + 1); + } + } + return name; + } + + /** + * Fetches all locals that are no parameters from a methods {@link LocalVariableTable} and + * processes them into {@code LocalEntry} objects. + * + * @param method method to fetch locals from + * @param lastParamSlot the highest slot number of the methods params + * @return a list of locals in the methods local variable table + */ + private List getLocalEntries(SharedMethod method, int lastParamSlot) { + List localEntries = new ArrayList<>(); + + LineNumberTable lnt = method.getLineNumberTable(); + LocalVariableTable lvt = method.getLocalVariableTable(); + + // we do not have any information on local variables + if (lvt == null) { + return localEntries; + } + + SharedType ownerType = (SharedType) method.getDeclaringClass(); + for (Local local : lvt.getLocals()) { + // check if this is a local (slot is after last param slot) + if (local != null && local.getSlot() > lastParamSlot) { + // we have a local with a known name, type and slot + String name = local.getName(); + SharedType type = (SharedType) local.getType().resolve(ownerType); + int slot = local.getSlot(); + int bciStart = local.getStartBCI(); + int line = lnt == null ? 0 : lnt.getLineNumber(bciStart); + TypeEntry typeEntry = lookupTypeEntry(type); + localEntries.add(new LocalEntry(name, typeEntry, slot, line)); + } + } + + return localEntries; + } + + /** + * Fetches all parameters from a methods {@link Signature} and processes them into + * {@link LocalEntry} objects. The parameters are sorted by slot number. + * + *

    + * If the parameter also exists in the methods {@link LocalVariableTable} we fetch the + * parameters name from there, otherwise a name is generated. + * + * @param method method to fetch parameters from + * @param line first line of the method in its source file + * @return a {@code SortedSet} of parameters in the methods signature + */ + private SortedSet getParamEntries(SharedMethod method, int line) { + Signature signature = method.getSignature(); + int parameterCount = signature.getParameterCount(false); + SortedSet paramInfos = new TreeSet<>(Comparator.comparingInt(LocalEntry::slot)); + LocalVariableTable lvt = method.getLocalVariableTable(); + int slot = 0; + SharedType ownerType = (SharedType) method.getDeclaringClass(); + if (!method.isStatic()) { + JavaKind kind = ownerType.getJavaKind(); + assert kind == JavaKind.Object : "must be an object"; + paramInfos.add(new LocalEntry("this", lookupTypeEntry(ownerType), slot, line)); + slot += kind.getSlotCount(); + } + for (int i = 0; i < parameterCount; i++) { + Local local = null; + if (lvt != null) { + try { + local = lvt.getLocal(slot, 0); + } catch (IllegalStateException e) { + if (debug.isLogEnabled()) { + debug.log("Found invalid local variable table from method %s during debug info generation.", method.getName()); + } + } + } + SharedType paramType = (SharedType) signature.getParameterType(i, null); + JavaKind kind = paramType.getJavaKind(); + JavaKind storageKind = paramType.getStorageKind(); + String name = local != null ? local.getName() : "__" + storageKind.getJavaName() + i; + paramInfos.add(new LocalEntry(name, lookupTypeEntry(paramType), slot, line)); + slot += kind.getSlotCount(); + } + return paramInfos; + } + + /** + * Fetch a methods symbol name from the {@link UniqueShortNameProvider}. + * + * @param method method to get the symbol name for + * @return symbol name of the method + */ + public String getSymbolName(SharedMethod method) { + return UniqueShortNameProvider.singleton().uniqueShortName(null, method.getDeclaringClass(), method.getName(), method.getSignature(), method.isConstructor()); + } + + public boolean isOverride(@SuppressWarnings("unused") SharedMethod method) { + return false; + } + + public boolean isVirtual(@SuppressWarnings("unused") SharedMethod method) { + return false; + } + + public int getVTableOffset(SharedMethod method) { + return isVirtual(method) ? method.getVTableIndex() : -1; + } + + /* Lookup functions for indexed debug info entries. */ + + /** + * Lookup the header type entry and installs it if it does not exist yet. + * + * @return the header type entry + */ + protected HeaderTypeEntry lookupHeaderTypeEntry() { + if (headerTypeEntry == null) { + installHeaderTypeEntry(); + } + return headerTypeEntry; + } + + /** + * Lookup a {@code MethodEntry} for a {@code SharedMethod}. If it does not exist yet, this + * installs the {@code MethodEntry} and updates the method list of the owner type. + * + * @param method the given {@code SharedMethod} + * @return the corresponding {@code MethodEntry} + */ + protected MethodEntry lookupMethodEntry(SharedMethod method) { + if (method == null) { + return null; + } + MethodEntry methodEntry = methodIndex.get(method); + if (methodEntry == null) { + methodEntry = installMethodEntry(method); + methodEntry.getOwnerType().addMethod(methodEntry); + } + return methodEntry; + + } + + /** + * Lookup a {@code CompiledMethodEntry} for a {@code CompilationResult}. If the + * {@code CompiledMethodEntry} does not exist yet, it is installed. + * + * @param methodEntry the {@code MethodEntry} of the method param + * @param method the {@code SharedMethod} of this compilation + * @param compilation the given {@code CompilationResult} + * @return the corresponding {@code CompiledMethodEntry} + */ + protected CompiledMethodEntry lookupCompiledMethodEntry(MethodEntry methodEntry, SharedMethod method, CompilationResult compilation) { + if (method == null) { + return null; + } + CompiledMethodEntry compiledMethodEntry = compiledMethodIndex.get(compilation.getCompilationId()); + if (compiledMethodEntry == null) { + compiledMethodEntry = installCompilationInfo(methodEntry, method, compilation); + } + return compiledMethodEntry; + } + + /** + * Lookup a {@code TypeEntry} for a {@code SharedType}. If the {@code TypeEntry} does not exist + * yet, it is installed. + * + * @param type the given {@code SharedType} + * @return the corresponding {@code TypeEntry} + */ + protected TypeEntry lookupTypeEntry(SharedType type) { + if (type == null) { + // this must be the header type entry, as it is the only one with no underlying + return lookupHeaderTypeEntry(); + } + TypeEntry typeEntry = typeIndex.get(type); + if (typeEntry == null) { + typeEntry = installTypeEntry(type); + } + return typeEntry; + } + + /** + * Lookup a {@code LoaderEntry} for a {@code SharedType}. Extracts the loader name from the + * {@code SharedType} and forwards to {@link #lookupLoaderEntry(String)}. + * + * @param type the given {@code SharedType} + * @return the corresponding {@code LoaderEntry} + */ + protected LoaderEntry lookupLoaderEntry(SharedType type) { + SharedType targetType = type; + if (type.isArray()) { + targetType = (SharedType) type.getElementalType(); + } + return targetType.getHub().isLoaded() ? lookupLoaderEntry(UniqueShortNameProvider.singleton().uniqueShortLoaderName(targetType.getHub().getClassLoader())) : NULL_LOADER_ENTRY; + } + + /** + * Lookup a {@code LoaderEntry} for a string. If the {@code LoaderEntry} does not exist yet, it + * is added to the {@link #loaderIndex}. + * + * @param loaderName the given loader name string + * @return the corresponding {@code LoaderEntry} + */ + protected LoaderEntry lookupLoaderEntry(String loaderName) { + if (loaderName == null || loaderName.isEmpty()) { + return NULL_LOADER_ENTRY; + } + return loaderIndex.computeIfAbsent(loaderName, LoaderEntry::new); + } + + /** + * Lookup a {@code FileEntry} for a {@code ResolvedJavaType}. + * + * @param type the given {@code ResolvedJavaType} + * @return the corresponding {@code FileEntry} + */ + protected FileEntry lookupFileEntry(ResolvedJavaType type) { + /* + * Conjure up an appropriate, unique file name to keep tools happy even though we cannot + * find a corresponding source. + */ + return lookupFileEntry(fullFilePathFromClassName(type)); + } + + /** + * Lookup a {@code FileEntry} for the declaring class of a {@code ResolvedJavaMethod}. + * + * @param method the given {@code ResolvedJavaMethod} + * @return the corresponding {@code FileEntry} + */ + protected FileEntry lookupFileEntry(ResolvedJavaMethod method) { + return lookupFileEntry(method.getDeclaringClass()); + } + + /** + * Lookup a {@code FileEntry} for the declaring class of a {@code ResolvedJavaField}. + * + * @param field the given {@code ResolvedJavaField} + * @return the corresponding {@code FileEntry} + */ + protected FileEntry lookupFileEntry(ResolvedJavaField field) { + return lookupFileEntry(field.getDeclaringClass()); + } + + /** + * Lookup a {@code FileEntry} for a file path. First extracts the files directory and the + * corresponding {@link DirEntry} and adds a new {@code FileEntry} to {@link #fileIndex} if it + * does not exist yet. + * + * @param fullFilePath the given file path + * @return the corresponding {@code FileEntry} + */ + protected FileEntry lookupFileEntry(Path fullFilePath) { + if (fullFilePath == null) { + return null; + } + Path fileName = fullFilePath.getFileName(); + if (fileName == null) { + return null; + } + + Path dirPath = fullFilePath.getParent(); + DirEntry dirEntry = lookupDirEntry(dirPath); + + /* Reuse any existing entry if available. */ + FileEntry fileEntry = fileIndex.computeIfAbsent(fullFilePath, path -> new FileEntry(fileName.toString(), dirEntry)); + assert dirPath == null || fileEntry.dirEntry() != null && fileEntry.dirEntry().path().equals(dirPath); + return fileEntry; + } + + /** + * Lookup a {@code DirEntry} for a directory path. Adds a new {@code DirEntry} to + * {@link #dirIndex} if it does not exist yet. A {@code null} path is represented by + * {@link #EMPTY_PATH}. + * + * @param dirPath the given directory path + * @return the corresponding {@code FileEntry} + */ + protected DirEntry lookupDirEntry(Path dirPath) { + return dirIndex.computeIfAbsent(dirPath == null ? EMPTY_PATH : dirPath, DirEntry::new); + } + + /* Other helper functions. */ + protected static ObjectLayout getObjectLayout() { + return ConfigurationValues.getObjectLayout(); + } + + protected static Path fullFilePathFromClassName(ResolvedJavaType type) { + String[] elements = type.toJavaName().split("\\."); + int count = elements.length; + String name = elements[count - 1]; + while (name.startsWith("$")) { + name = name.substring(1); + } + if (name.contains("$")) { + name = name.substring(0, name.indexOf('$')); + } + if (name.isEmpty()) { + name = "_nofile_"; + } + elements[count - 1] = name + ".java"; + return FileSystems.getDefault().getPath("", elements); + } + + /** + * Identify a Java type which is being used to model a foreign memory word or pointer type. + * + * @param type the type to be tested + * @param accessingType another type relative to which the first type may need to be resolved + * @return true if the type models a foreign memory word or pointer type + */ + protected boolean isForeignWordType(JavaType type, SharedType accessingType) { + SharedType resolvedJavaType = (SharedType) type.resolve(accessingType); + return isForeignWordType(resolvedJavaType); + } + + /** + * Identify a hosted type which is being used to model a foreign memory word or pointer type. + * + * @param type the type to be tested + * @return true if the type models a foreign memory word or pointer type + */ + protected boolean isForeignWordType(SharedType type) { + return wordBaseType.isAssignableFrom(type); + } + + private static int findMarkOffset(SubstrateBackend.SubstrateMarkId markId, CompilationResult compilation) { + for (CompilationResult.CodeMark mark : compilation.getMarks()) { + if (mark.id.equals(markId)) { + return mark.pcOffset; + } + } + return -1; + } + + /** + * If there are any location info records then the first one will be for a nop which follows the + * stack decrement, stack range check and pushes of arguments into the stack frame. + * + *

    + * We can construct synthetic location info covering the first instruction based on the method + * arguments and the calling convention and that will normally be valid right up to the nop. In + * exceptional cases a call might pass arguments on the stack, in which case the stack decrement + * will invalidate the original stack locations. Providing location info for that case requires + * adding two locations, one for initial instruction that does the stack decrement and another + * for the range up to the nop. They will be essentially the same but the stack locations will + * be adjusted to account for the different value of the stack pointer. + * + * @param primary the {@code PrimaryRange} of the compilation + * @param locationInfos the location infos produced from the compilations frame states + * @param compilation the {@code CompilationResult} of a method + * @param method the given {@code SharedMethod} + * @param methodEntry the methods {@code MethodEntry} + */ + private void updateInitialLocation(PrimaryRange primary, List locationInfos, CompilationResult compilation, SharedMethod method, MethodEntry methodEntry) { + int prologueEnd = findMarkOffset(SubstrateBackend.SubstrateMarkId.PROLOGUE_END, compilation); + if (prologueEnd < 0) { + // this is not a normal compiled method so give up + return; + } + int stackDecrement = findMarkOffset(SubstrateBackend.SubstrateMarkId.PROLOGUE_DECD_RSP, compilation); + if (stackDecrement < 0) { + // this is not a normal compiled method so give up + return; + } + if (locationInfos.isEmpty()) { + // this is not a normal compiled method so give up + return; + } + + Range firstLocation = locationInfos.getFirst(); + int firstLocationOffset = firstLocation.getLoOffset(); + + if (firstLocationOffset == 0) { + // this is not a normal compiled method so give up + return; + } + if (firstLocationOffset < prologueEnd) { + // this is not a normal compiled method so give up + return; + } + + // create a synthetic location record including details of passed arguments + ParamLocationProducer locProducer = new ParamLocationProducer(method); + if (debug.isLogEnabled()) { + debug.log(DebugContext.DETAILED_LEVEL, "Add synthetic Location Info : %s (0, %d)", methodEntry.getMethodName(), firstLocationOffset - 1); + } + + Map localInfoList = initSyntheticInfoList(locProducer, methodEntry); + Range locationInfo = Range.createSubrange(primary, methodEntry, localInfoList, 0, firstLocationOffset, methodEntry.getLine(), primary, true); + + /* + * If the prologue extends beyond the stack extend and uses the stack then the info needs + * splitting at the extent point with the stack offsets adjusted in the new info. + */ + if (locProducer.usesStack() && firstLocationOffset > stackDecrement) { + Range splitLocationInfo = locationInfo.split(stackDecrement, compilation.getTotalFrameSize(), PRE_EXTEND_FRAME_SIZE); + if (debug.isLogEnabled()) { + debug.log(DebugContext.DETAILED_LEVEL, "Split synthetic Location Info : %s (0, %d) (%d, %d)", methodEntry.getMethodName(), + locationInfo.getLoOffset() - 1, locationInfo.getLoOffset(), locationInfo.getHiOffset() - 1); + } + locationInfos.addFirst(splitLocationInfo); + } + locationInfos.addFirst(locationInfo); + } + + /** + * Creates synthetic location infos for a methods parameters that spans to the first location + * info from the compilations frame states. + * + * @param locProducer the location info producer for the methods parameters + * @param methodEntry the given {@code MethodEntry} + * @return a mapping of {@code LocalEntry} to synthetic location info + */ + private Map initSyntheticInfoList(ParamLocationProducer locProducer, MethodEntry methodEntry) { + HashMap localValueInfos = new HashMap<>(); + // Create synthetic this param info + if (methodEntry.getThisParam() != null) { + JavaValue value = locProducer.thisLocation(); + LocalEntry thisParam = methodEntry.getThisParam(); + if (debug.isLogEnabled()) { + debug.log(DebugContext.DETAILED_LEVEL, "local[0] %s type %s slot %d", thisParam.name(), thisParam.type().getTypeName(), thisParam.slot()); + debug.log(DebugContext.DETAILED_LEVEL, " => %s", value); + } + LocalValueEntry localValueEntry = createLocalValueEntry(value, PRE_EXTEND_FRAME_SIZE); + if (localValueEntry != null) { + localValueInfos.put(thisParam, localValueEntry); + } + } + // Iterate over all params and create synthetic param info for each + int paramIdx = 0; + for (LocalEntry param : methodEntry.getParams()) { + JavaValue value = locProducer.paramLocation(paramIdx); + if (debug.isLogEnabled()) { + debug.log(DebugContext.DETAILED_LEVEL, "local[%d] %s type %s slot %d", paramIdx + 1, param.name(), param.type().getTypeName(), param.slot()); + debug.log(DebugContext.DETAILED_LEVEL, " => %s", value); + } + LocalValueEntry localValueEntry = createLocalValueEntry(value, PRE_EXTEND_FRAME_SIZE); + if (localValueEntry != null) { + localValueInfos.put(param, localValueEntry); + } + paramIdx++; + } + return localValueInfos; + } + + /** + * Size in bytes of the frame at call entry before any stack extend. Essentially this accounts + * for any automatically pushed return address whose presence depends upon the architecture. + */ + static final int PRE_EXTEND_FRAME_SIZE = ConfigurationValues.getTarget().arch.getReturnAddressSize(); + + /** + * Retrieve details of the native calling convention for a top level compiled method, including + * details of which registers or stack slots are used to pass parameters. + * + * @param method The method whose calling convention is required. + * @return The calling convention for the method. + */ + protected SubstrateCallingConvention getCallingConvention(SharedMethod method) { + SubstrateCallingConventionKind callingConventionKind = method.getCallingConventionKind(); + ResolvedJavaType declaringClass = method.getDeclaringClass(); + ResolvedJavaType receiverType = method.isStatic() ? null : declaringClass; + Signature signature = method.getSignature(); + final SubstrateCallingConventionType type; + if (callingConventionKind.isCustom()) { + type = method.getCustomCallingConventionType(); + } else { + type = callingConventionKind.toType(false); + } + Backend backend = runtimeConfiguration.lookupBackend(method); + RegisterConfig registerConfig = backend.getCodeCache().getRegisterConfig(); + assert registerConfig instanceof SubstrateRegisterConfig; + return (SubstrateCallingConvention) registerConfig.getCallingConvention(type, signature.getReturnType(null), signature.toParameterTypes(receiverType), backend); + } + + class ParamLocationProducer { + private final SharedMethod method; + private final CallingConvention callingConvention; + private boolean usesStack; + + ParamLocationProducer(SharedMethod method) { + this.method = method; + this.callingConvention = getCallingConvention(method); + // assume no stack slots until we find out otherwise + this.usesStack = false; + } + + JavaValue thisLocation() { + assert !method.isStatic(); + return unpack(callingConvention.getArgument(0)); + } + + JavaValue paramLocation(int paramIdx) { + assert paramIdx < method.getSignature().getParameterCount(false); + int idx = paramIdx; + if (!method.isStatic()) { + idx++; + } + return unpack(callingConvention.getArgument(idx)); + } + + private JavaValue unpack(AllocatableValue value) { + if (value instanceof RegisterValue registerValue) { + return registerValue; + } else { + // call argument must be a stack slot if it is not a register + StackSlot stackSlot = (StackSlot) value; + this.usesStack = true; + // the calling convention provides offsets from the SP relative to the current + // frame size. At the point of call the frame may or may not include a return + // address depending on the architecture. + return stackSlot; + } + } + + public boolean usesStack() { + return usesStack; + } + } + + // indices for arguments passed to SingleLevelVisitor::apply + protected static final int CALLER_INFO = 0; + protected static final int PARENT_NODE_TO_EMBED = 1; + protected static final int LAST_LEAF_INFO = 2; + + private abstract class SingleLevelVisitor implements CompilationResultFrameTree.Visitor { + + protected final List locationInfos; + protected final int frameSize; + + protected final PrimaryRange primary; + + SingleLevelVisitor(List locationInfos, int frameSize, PrimaryRange primary) { + this.locationInfos = locationInfos; + this.frameSize = frameSize; + this.primary = primary; + } + + @Override + public void apply(CompilationResultFrameTree.FrameNode node, Object... args) { + // Visits all nodes at this level and handle call nodes depth first (by default do + // nothing, just add the call nodes range info). + if (node instanceof CompilationResultFrameTree.CallNode && skipPos(node.frame)) { + node.visitChildren(this, args); + } else { + CallRange callerInfo = (CallRange) args[CALLER_INFO]; + CompilationResultFrameTree.CallNode nodeToEmbed = (CompilationResultFrameTree.CallNode) args[PARENT_NODE_TO_EMBED]; + handleNodeToEmbed(nodeToEmbed, node, callerInfo, args); + Range locationInfo = process(node, callerInfo); + if (node instanceof CompilationResultFrameTree.CallNode callNode) { + assert locationInfo instanceof CallRange; + locationInfos.add(locationInfo); + // erase last leaf (if present) since there is an intervening call range + args[LAST_LEAF_INFO] = null; + // handle inlined methods in implementors + handleCallNode(callNode, (CallRange) locationInfo); + } else { + // last leaf node added at this level is 3rd element of arg vector + Range lastLeaf = (Range) args[LAST_LEAF_INFO]; + if (lastLeaf == null || !lastLeaf.tryMerge(locationInfo)) { + // update last leaf and add new leaf to local info list + args[LAST_LEAF_INFO] = locationInfo; + locationInfos.add(locationInfo); + } else if (debug.isLogEnabled()) { + debug.log(DebugContext.DETAILED_LEVEL, "Merge leaf Location Info : %s depth %d (%d, %d) into (%d, %d)", lastLeaf.getMethodName(), lastLeaf.getDepth(), lastLeaf.getLoOffset(), + lastLeaf.getHiOffset() - 1, locationInfo.getLoOffset(), locationInfo.getHiOffset() - 1); + } + } + } + } + + @SuppressWarnings("unused") + protected void handleCallNode(CompilationResultFrameTree.CallNode callNode, CallRange locationInfo) { + // do nothing by default + } + + @SuppressWarnings("unused") + protected void handleNodeToEmbed(CompilationResultFrameTree.CallNode nodeToEmbed, CompilationResultFrameTree.FrameNode node, + CallRange callerInfo, Object... args) { + // do nothing by default + } + + public Range process(CompilationResultFrameTree.FrameNode node, CallRange callerInfo) { + BytecodePosition pos; + boolean isLeaf = true; + if (node instanceof CompilationResultFrameTree.CallNode callNode) { + // this node represents an inline call range so + // add a location info to cover the range of the call + pos = callNode.frame.getCaller(); + while (skipPos(pos)) { + pos = pos.getCaller(); + } + isLeaf = false; + } else if (isBadLeaf(node, callerInfo)) { + pos = node.frame.getCaller(); + assert pos != null : "bad leaf must have a caller"; + assert pos.getCaller() == null : "bad leaf caller must be root method"; + } else { + pos = node.frame; + } + + SharedMethod method = (SharedMethod) pos.getMethod(); + MethodEntry methodEntry = lookupMethodEntry(method); + + LineNumberTable lineNumberTable = method.getLineNumberTable(); + int line = lineNumberTable == null ? -1 : lineNumberTable.getLineNumber(pos.getBCI()); + + Map localValueInfos = initLocalInfoList(pos, methodEntry, frameSize); + Range locationInfo = Range.createSubrange(primary, methodEntry, localValueInfos, node.getStartPos(), node.getEndPos() + 1, line, callerInfo, isLeaf); + + if (debug.isLogEnabled()) { + debug.log(DebugContext.DETAILED_LEVEL, "Create %s Location Info : %s depth %d (%d, %d)", isLeaf ? "leaf" : "call", method.getName(), locationInfo.getDepth(), + locationInfo.getLoOffset(), locationInfo.getHiOffset() - 1); + } + + return locationInfo; + } + } + + /** + * Generate local info list for a location info from the local values of the current frame. + * Names and types of local variables are fetched from the methods local variable table. If we + * cant find the local in the local variable table, we use the frame information. + * + * @param pos the bytecode position of the location info + * @param methodEntry the {@code MethodEntry} corresponding to the bytecode position + * @param frameSize the current frame size + * @return a mapping from {@code LocalEntry} to {@code LocalValueEntry} + */ + protected Map initLocalInfoList(BytecodePosition pos, MethodEntry methodEntry, int frameSize) { + Map localInfos = new HashMap<>(); + + if (pos instanceof BytecodeFrame frame && frame.numLocals > 0) { + /* + * For each MethodEntry, initially local variables are loaded from the local variable + * table The local variable table is used here to get some additional information about + * locals and double-check the expected value kind + * + * A local variable that is not yet known to the method, will be added to the method + * with a synthesized name, the type according to the JavaKind (Object for all + * classes/array types) and the line of the current bytecode position + */ + SharedMethod method = (SharedMethod) pos.getMethod(); + LocalVariableTable lvt = method.getLocalVariableTable(); + LineNumberTable lnt = method.getLineNumberTable(); + int line = lnt == null ? 0 : lnt.getLineNumber(pos.getBCI()); + + // the owner type to resolve the local types against + SharedType ownerType = (SharedType) method.getDeclaringClass(); + + for (int slot = 0; slot < frame.numLocals; slot++) { + // Read locals from frame by slot - this might be an Illegal value + JavaValue value = frame.getLocalValue(slot); + JavaKind storageKind = frame.getLocalValueKind(slot); + + if (ValueUtil.isIllegalJavaValue(value)) { + /* + * If we have an illegal value, also the storage kind must be Illegal. We don't + * have any value, so we have to continue with the next slot. + */ + assert storageKind == JavaKind.Illegal; + continue; + } + + /* + * We might not have a local variable table at all, which means we can only use the + * frame local value. Even if there is a local variable table, there might not be a + * local at this slot in the local variable table. We also need to check if the + * local variable table is malformed. + */ + Local local = null; + if (lvt != null) { + try { + local = lvt.getLocal(slot, pos.getBCI()); + } catch (IllegalStateException e) { + if (debug.isLogEnabled()) { + debug.log("Found invalid local variable table from method %s during debug info generation.", method.getName()); + } + } + } + + String name; + SharedType type; + if (local == null) { + if (methodEntry.getLastParamSlot() >= slot) { + /* + * If we e.g. get an int from the frame values can we really be sure that + * this is a param and not just any other local value that happens to be an + * int? + * + * Better just skip inferring params if we have no local in the local + * variable table. + */ + continue; + } + + /* + * We don't have a corresponding local in the local variable table. Collect some + * usable information for this local from the frame local kind. + */ + name = "__" + storageKind.getJavaName() + (methodEntry.isStatic() ? slot : slot - 1); + Class clazz = storageKind.isObject() ? Object.class : storageKind.toJavaClass(); + type = (SharedType) metaAccess.lookupJavaType(clazz); + } else { + /* + * Use the information from the local variable table. This allows us to match + * the local variables with the ones we already read for the method entry. In + * this case the information from the frame local kind is just used to + * double-check the type kind from the local variable table. + */ + name = local.getName(); + type = (SharedType) local.getType().resolve(ownerType); + } + + TypeEntry typeEntry = lookupTypeEntry(type); + JavaKind kind = type.getJavaKind(); + + if (debug.isLogEnabled()) { + debug.log(DebugContext.DETAILED_LEVEL, "local %s type %s slot %d", name, typeEntry.getTypeName(), slot); + debug.log(DebugContext.DETAILED_LEVEL, " => %s", value); + } + + // Double-check the kind from the frame local value with the kind from the local + // variable table. + if (storageKind == kind || isIntegralKindPromotion(storageKind, kind) || (isForeignWordType(type, ownerType) && kind == JavaKind.Object && storageKind == JavaKind.Long)) { + /* + * Lookup a LocalEntry from the MethodEntry. If the LocalEntry was already read + * upfront from the local variable table, the LocalEntry already exists. + */ + LocalEntry localEntry = methodEntry.lookupLocalEntry(name, slot, typeEntry, line); + LocalValueEntry localValueEntry = createLocalValueEntry(value, frameSize); + if (localEntry != null && localValueEntry != null) { + localInfos.put(localEntry, localValueEntry); + } + } else if (debug.isLogEnabled()) { + debug.log(DebugContext.DETAILED_LEVEL, " value kind incompatible with var kind %s!", kind); + } + } + } + + return localInfos; + } + + /** + * Creates a {@code LocalValueEntry} for a given {@code JavaValue}. This processes register + * values, stack values, primitive constants and constant in the heap. + * + * @param value the given {@code JavaValue} + * @param frameSize the frame size for stack values + * @return the {@code LocalValueEntry} or {@code null} if the value can't be processed + */ + private LocalValueEntry createLocalValueEntry(JavaValue value, int frameSize) { + switch (value) { + case RegisterValue registerValue -> { + return new RegisterValueEntry(registerValue.getRegister().number); + } + case StackSlot stackValue -> { + int stackSlot = frameSize == 0 ? stackValue.getRawOffset() : stackValue.getOffset(frameSize); + return new StackValueEntry(stackSlot); + } + case JavaConstant constantValue -> { + if (constantValue instanceof PrimitiveConstant || constantValue.isNull()) { + return new ConstantValueEntry(-1, constantValue); + } else { + long heapOffset = objectOffset(constantValue); + if (heapOffset >= 0) { + return new ConstantValueEntry(heapOffset, constantValue); + } + } + return null; + } + default -> { + return null; + } + } + } + + private static boolean isIntegralKindPromotion(JavaKind promoted, JavaKind original) { + return (promoted == JavaKind.Int && + (original == JavaKind.Boolean || original == JavaKind.Byte || original == JavaKind.Short || original == JavaKind.Char)); + } + + // Top level visitor is just a single level visitor that starts at the primary range/root node + private class TopLevelVisitor extends SingleLevelVisitor { + TopLevelVisitor(List locationInfos, int frameSize, PrimaryRange primary) { + super(locationInfos, frameSize, primary); + } + } + + // Multi level visitor starts at the primary range and defines behavior for stepping into call + // nodes + public class MultiLevelVisitor extends SingleLevelVisitor { + MultiLevelVisitor(List locationInfos, int frameSize, PrimaryRange primary) { + super(locationInfos, frameSize, primary); + } + + @Override + protected void handleCallNode(CompilationResultFrameTree.CallNode callNode, CallRange locationInfo) { + if (hasChildren(callNode)) { + /* + * A call node may include an initial leaf range for the call that must be embedded + * under the newly created location info so pass it as an argument + */ + callNode.visitChildren(this, locationInfo, callNode, null); + } else { + // We need to embed a leaf node for the whole call range + locationInfos.add(createEmbeddedParentLocationInfo(primary, callNode, null, locationInfo, frameSize)); + } + } + + @Override + protected void handleNodeToEmbed(CompilationResultFrameTree.CallNode nodeToEmbed, CompilationResultFrameTree.FrameNode node, CallRange callerInfo, Object... args) { + if (nodeToEmbed != null) { + /* + * We only need to insert a range for the caller if it fills a gap at the start of + * the caller range before the first child. + */ + if (nodeToEmbed.getStartPos() < node.getStartPos()) { + /* + * Embed a leaf range for the method start that was included in the parent + * CallNode Its end range is determined by the start of the first node at this + * level. + */ + Range embeddedLocationInfo = createEmbeddedParentLocationInfo(primary, nodeToEmbed, node, callerInfo, frameSize); + locationInfos.add(embeddedLocationInfo); + // Since this is a leaf node we can merge later leafs into it. + args[LAST_LEAF_INFO] = embeddedLocationInfo; + } + // Reset args so we only embed the parent node before the first node at this level. + args[PARENT_NODE_TO_EMBED] = null; + } + } + } + + /** + * Report whether a call node has any children. + * + * @param callNode the node to check + * @return true if it has any children otherwise false. + */ + private static boolean hasChildren(CompilationResultFrameTree.CallNode callNode) { + Object[] result = new Object[]{false}; + callNode.visitChildren((node, args) -> args[0] = true, result); + return (boolean) result[0]; + } + + /** + * Create a location info record for the initial range associated with a parent call node whose + * position and start are defined by that call node and whose end is determined by the first + * child of the call node. + * + * @param parentToEmbed a parent call node which has already been processed to create the caller + * location info + * @param firstChild the first child of the call node + * @param callerLocation the location info created to represent the range for the call + * @return a location info to be embedded as the first child range of the caller location. + */ + private Range createEmbeddedParentLocationInfo(PrimaryRange primary, CompilationResultFrameTree.CallNode parentToEmbed, CompilationResultFrameTree.FrameNode firstChild, CallRange callerLocation, + int frameSize) { + BytecodePosition pos = parentToEmbed.frame; + int startPos = parentToEmbed.getStartPos(); + int endPos = (firstChild != null ? firstChild.getStartPos() : parentToEmbed.getEndPos() + 1); + + SharedMethod method = (SharedMethod) pos.getMethod(); + MethodEntry methodEntry = lookupMethodEntry(method); + + LineNumberTable lineNumberTable = method.getLineNumberTable(); + int line = lineNumberTable == null ? -1 : lineNumberTable.getLineNumber(pos.getBCI()); + + Map localValueInfos = initLocalInfoList(pos, methodEntry, frameSize); + Range locationInfo = Range.createSubrange(primary, methodEntry, localValueInfos, startPos, endPos, line, callerLocation, true); + + if (debug.isLogEnabled()) { + debug.log(DebugContext.DETAILED_LEVEL, "Embed leaf Location Info : %s depth %d (%d, %d)", locationInfo.getMethodName(), locationInfo.getDepth(), locationInfo.getLoOffset(), + locationInfo.getHiOffset() - 1); + } + + return locationInfo; + } + + /** + * Test whether a node is a bad leaf. + * + *

    + * Sometimes we see a leaf node marked as belonging to an inlined method that sits directly + * under the root method rather than under a call node. It needs replacing with a location info + * for the root method that covers the relevant code range. + * + * @param node the node to check + * @param callerLocation the caller location info + * @return true if the node is a bad leaf otherwise false + */ + private static boolean isBadLeaf(CompilationResultFrameTree.FrameNode node, CallRange callerLocation) { + if (callerLocation.isPrimary()) { + BytecodePosition pos = node.frame; + BytecodePosition callerPos = pos.getCaller(); + if (callerPos != null && !callerPos.getMethod().equals(pos.getMethod())) { + return callerPos.getCaller() == null; + } + } + return false; + } + + /** + * Test whether a bytecode position represents a bogus frame added by the compiler when a + * substitution or snippet call is injected. + * + * @param pos the position to be tested + * @return true if the frame is bogus otherwise false + */ + private static boolean skipPos(BytecodePosition pos) { + return pos.getBCI() == -1 && pos instanceof NodeSourcePosition sourcePos && sourcePos.isSubstitution(); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoFeature.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoFeature.java new file mode 100644 index 000000000000..a0ae63c381a3 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoFeature.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.debug; + +import java.util.List; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.hosted.Feature; + +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.code.InstalledCodeObserverSupport; +import com.oracle.svm.core.code.InstalledCodeObserverSupportFeature; +import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; +import com.oracle.svm.core.feature.InternalFeature; +import com.oracle.svm.core.graal.meta.RuntimeConfiguration; + +@AutomaticallyRegisteredFeature +public class SubstrateDebugInfoFeature implements InternalFeature { + @Override + public boolean isInConfiguration(IsInConfigurationAccess access) { + return Platform.includedIn(Platform.LINUX.class) && SubstrateOptions.useDebugInfoGeneration() && SubstrateOptions.RuntimeDebugInfo.getValue(); + } + + @Override + public List> getRequiredFeatures() { + return List.of(InstalledCodeObserverSupportFeature.class); + } + + @Override + public void registerCodeObserver(RuntimeConfiguration runtimeConfig) { + // This is called at image build-time -> the factory then creates a RuntimeDebugInfoProvider + // at runtime + ImageSingletons.lookup(InstalledCodeObserverSupport.class).addObserverFactory(new SubstrateDebugInfoInstaller.Factory(runtimeConfig.getProviders().getMetaAccess(), runtimeConfig)); + ImageSingletons.add(SubstrateDebugInfoInstaller.GdbJitAccessor.class, new SubstrateDebugInfoInstaller.GdbJitAccessor()); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java new file mode 100644 index 000000000000..12532b25d7de --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoInstaller.java @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.debug; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.ArrayList; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.c.struct.RawField; +import org.graalvm.nativeimage.c.struct.RawStructure; +import org.graalvm.nativeimage.c.struct.SizeOf; +import org.graalvm.nativeimage.c.type.CCharPointer; +import org.graalvm.word.Pointer; + +import com.oracle.objectfile.BasicNobitsSectionImpl; +import com.oracle.objectfile.ObjectFile; +import com.oracle.objectfile.SectionName; +import com.oracle.svm.core.Uninterruptible; +import com.oracle.svm.core.c.NonmovableArray; +import com.oracle.svm.core.c.NonmovableArrays; +import com.oracle.svm.core.code.InstalledCodeObserver; +import com.oracle.svm.core.graal.meta.RuntimeConfiguration; +import com.oracle.svm.core.memory.NativeMemory; +import com.oracle.svm.core.meta.SharedMethod; +import com.oracle.svm.core.nmt.NmtCategory; +import com.oracle.svm.core.os.VirtualMemoryProvider; +import com.oracle.svm.core.thread.VMOperation; +import com.oracle.svm.core.util.VMError; + +import jdk.graal.compiler.code.CompilationResult; +import jdk.graal.compiler.core.common.NumUtil; +import jdk.graal.compiler.debug.DebugContext; +import jdk.vm.ci.meta.MetaAccessProvider; + +public final class SubstrateDebugInfoInstaller implements InstalledCodeObserver { + + private final DebugContext debug; + private final SubstrateDebugInfoProvider substrateDebugInfoProvider; + private final ObjectFile objectFile; + private final ArrayList sortedObjectFileElements; + private final int debugInfoSize; + + static final class Factory implements InstalledCodeObserver.Factory { + + private final MetaAccessProvider metaAccess; + private final RuntimeConfiguration runtimeConfiguration; + + Factory(MetaAccessProvider metaAccess, RuntimeConfiguration runtimeConfiguration) { + this.metaAccess = metaAccess; + this.runtimeConfiguration = runtimeConfiguration; + } + + @Override + public InstalledCodeObserver create(DebugContext debugContext, SharedMethod method, CompilationResult compilation, Pointer code, int codeSize) { + try { + return new SubstrateDebugInfoInstaller(debugContext, method, compilation, metaAccess, runtimeConfiguration, code, codeSize); + } catch (Throwable t) { + throw VMError.shouldNotReachHere(t); + } + } + } + + private SubstrateDebugInfoInstaller(DebugContext debugContext, SharedMethod method, CompilationResult compilation, MetaAccessProvider metaAccess, RuntimeConfiguration runtimeConfiguration, + Pointer code, int codeSize) { + debug = debugContext; + substrateDebugInfoProvider = new SubstrateDebugInfoProvider(debugContext, method, compilation, runtimeConfiguration, metaAccess, code.rawValue()); + + int pageSize = NumUtil.safeToInt(ImageSingletons.lookup(VirtualMemoryProvider.class).getGranularity().rawValue()); + objectFile = ObjectFile.createRuntimeDebugInfo(pageSize); + objectFile.newNobitsSection(SectionName.TEXT.getFormatDependentName(objectFile.getFormat()), new BasicNobitsSectionImpl(codeSize)); + objectFile.installDebugInfo(substrateDebugInfoProvider); + sortedObjectFileElements = new ArrayList<>(); + debugInfoSize = objectFile.bake(sortedObjectFileElements); + + if (debugContext.isLogEnabled()) { + dumpObjectFile(); + } + } + + private void dumpObjectFile() { + StringBuilder sb = new StringBuilder(substrateDebugInfoProvider.getCompilationName()).append(".debug"); + try (FileChannel dumpFile = FileChannel.open(Paths.get(sb.toString()), + StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, + StandardOpenOption.CREATE)) { + ByteBuffer buffer = dumpFile.map(FileChannel.MapMode.READ_WRITE, 0, debugInfoSize); + objectFile.writeBuffer(sortedObjectFileElements, buffer); + } catch (IOException e) { + debug.log("Failed to dump %s", sb); + } + } + + @RawStructure + private interface Handle extends InstalledCodeObserverHandle { + int INITIALIZED = 0; + int ACTIVATED = 1; + int RELEASED = 2; + + @RawField + GdbJitInterface.JITCodeEntry getRawHandle(); + + @RawField + void setRawHandle(GdbJitInterface.JITCodeEntry value); + + @RawField + NonmovableArray getDebugInfoData(); + + @RawField + void setDebugInfoData(NonmovableArray data); + + @RawField + int getState(); + + @RawField + void setState(int value); + } + + static final class GdbJitAccessor implements InstalledCodeObserverHandleAccessor { + + static Handle createHandle(NonmovableArray debugInfoData) { + Handle handle = NativeMemory.malloc(SizeOf.get(Handle.class), NmtCategory.Code); + GdbJitInterface.JITCodeEntry entry = NativeMemory.calloc(SizeOf.get(GdbJitInterface.JITCodeEntry.class), NmtCategory.Code); + handle.setAccessor(ImageSingletons.lookup(GdbJitAccessor.class)); + handle.setRawHandle(entry); + handle.setDebugInfoData(debugInfoData); + handle.setState(Handle.INITIALIZED); + return handle; + } + + @Override + public void activate(InstalledCodeObserverHandle installedCodeObserverHandle) { + Handle handle = (Handle) installedCodeObserverHandle; + VMOperation.guaranteeInProgressAtSafepoint("SubstrateDebugInfoInstaller.Accessor.activate must run in a VMOperation"); + VMError.guarantee(handle.getState() == Handle.INITIALIZED); + + NonmovableArray debugInfoData = handle.getDebugInfoData(); + CCharPointer address = NonmovableArrays.addressOf(debugInfoData, 0); + int size = NonmovableArrays.lengthOf(debugInfoData); + GdbJitInterface.registerJITCode(address, size, handle.getRawHandle()); + + handle.setState(Handle.ACTIVATED); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void release(InstalledCodeObserverHandle installedCodeObserverHandle) { + Handle handle = (Handle) installedCodeObserverHandle; + GdbJitInterface.JITCodeEntry entry = handle.getRawHandle(); + // Handle may still be just initialized here, so it never got registered in GDB. + if (handle.getState() == Handle.ACTIVATED) { + GdbJitInterface.unregisterJITCode(entry); + handle.setState(Handle.RELEASED); + } + NativeMemory.free(entry); + NonmovableArrays.releaseUnmanagedArray(handle.getDebugInfoData()); + NativeMemory.free(handle); + } + + @Override + public void detachFromCurrentIsolate(InstalledCodeObserverHandle installedCodeObserverHandle) { + Handle handle = (Handle) installedCodeObserverHandle; + NonmovableArrays.untrackUnmanagedArray(handle.getDebugInfoData()); + } + + @Override + public void attachToCurrentIsolate(InstalledCodeObserverHandle installedCodeObserverHandle) { + Handle handle = (Handle) installedCodeObserverHandle; + NonmovableArrays.trackUnmanagedArray(handle.getDebugInfoData()); + } + + @Override + @Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true) + public void releaseOnTearDown(InstalledCodeObserverHandle installedCodeObserverHandle) { + release(installedCodeObserverHandle); + } + } + + @Override + @SuppressWarnings("try") + public InstalledCodeObserverHandle install() { + NonmovableArray debugInfoData = writeDebugInfoData(); + Handle handle = GdbJitAccessor.createHandle(debugInfoData); + if (debug.isLogEnabled()) { + try (DebugContext.Scope s = debug.scope("RuntimeCompilation")) { + debug.log(toString(handle)); + } + } + return handle; + } + + private NonmovableArray writeDebugInfoData() { + NonmovableArray array = NonmovableArrays.createByteArray(debugInfoSize, NmtCategory.Code); + objectFile.writeBuffer(sortedObjectFileElements, NonmovableArrays.asByteBuffer(array)); + return array; + } + + private static String toString(Handle handle) { + return "DebugInfoHandle(handle = 0x" + Long.toHexString(handle.getRawHandle().rawValue()) + + ", address = 0x" + + Long.toHexString(NonmovableArrays.addressOf(handle.getDebugInfoData(), 0).rawValue()) + + ", size = " + + NonmovableArrays.lengthOf(handle.getDebugInfoData()) + + ", handleState = " + + handle.getState() + + ")"; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java new file mode 100644 index 000000000000..b19935f72e1b --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugInfoProvider.java @@ -0,0 +1,279 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.core.debug; + +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.util.stream.Stream; + +import org.graalvm.collections.Pair; +import org.graalvm.nativeimage.ProcessProperties; + +import com.oracle.objectfile.debugentry.ArrayTypeEntry; +import com.oracle.objectfile.debugentry.ClassEntry; +import com.oracle.objectfile.debugentry.FileEntry; +import com.oracle.objectfile.debugentry.LoaderEntry; +import com.oracle.objectfile.debugentry.PointerToTypeEntry; +import com.oracle.objectfile.debugentry.PrimitiveTypeEntry; +import com.oracle.objectfile.debugentry.TypeEntry; +import com.oracle.svm.core.SubstrateUtil; +import com.oracle.svm.core.graal.meta.RuntimeConfiguration; +import com.oracle.svm.core.meta.SharedMethod; +import com.oracle.svm.core.meta.SharedType; +import com.oracle.svm.core.option.RuntimeOptionKey; + +import jdk.graal.compiler.code.CompilationResult; +import jdk.graal.compiler.debug.DebugContext; +import jdk.graal.compiler.options.Option; +import jdk.vm.ci.meta.JavaConstant; +import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.MetaAccessProvider; +import jdk.vm.ci.meta.ResolvedJavaType; + +/** + * Implements the {@link com.oracle.objectfile.debuginfo.DebugInfoProvider DebugInfoProvider} + * interface based on the {@code SharedDebugInfoProvider} to handle run-time compiled methods. + * + *

    + * For each run-time compilation, one {@code SubstrateDebugInfoProvider} is created and the debug + * info for the compiled method is installed. As type information is already available in the native + * image's debug info, the {@code SubstrateDebugInfoProvider} just produces as little information as + * needed and reuses debug info from the native image. Therefore, for type entries the + * {@code SubstrateDebugInfoProvider} just creates stubs that contain the type signature, which can + * then be resolved by the debugger. + */ +public class SubstrateDebugInfoProvider extends SharedDebugInfoProvider { + + public static class Options { + @Option(help = "Directory where Java source-files will be placed for the debugger")// + public static final RuntimeOptionKey RuntimeSourceDestDir = new RuntimeOptionKey<>(null, RuntimeOptionKey.RuntimeOptionKeyFlag.RelevantForCompilationIsolates); + + public static Path getRuntimeSourceDestDir() { + String sourceDestDir = RuntimeSourceDestDir.getValue(); + if (sourceDestDir != null) { + return Path.of(sourceDestDir); + } + Path result = Path.of("sources"); + Path exeDir = Path.of(ProcessProperties.getExecutableName()).getParent(); + if (exeDir != null) { + result = exeDir.resolve(result); + } + return result; + } + } + + private final SharedMethod sharedMethod; + private final CompilationResult compilation; + private final long codeAddress; + + public SubstrateDebugInfoProvider(DebugContext debug, SharedMethod sharedMethod, CompilationResult compilation, RuntimeConfiguration runtimeConfiguration, MetaAccessProvider metaAccess, + long codeAddress) { + super(debug, runtimeConfiguration, metaAccess); + this.sharedMethod = sharedMethod; + this.compilation = compilation; + this.codeAddress = codeAddress; + } + + /** + * Create a compilation unit name from the {@link CompilationResult} or the {@link SharedMethod} + * the debug info is produced for. + * + * @return the name of the compilation unit in the debug info + */ + public String getCompilationName() { + String name = null; + if (compilation != null) { + name = compilation.getName(); + } + if ((name == null || name.isEmpty()) && sharedMethod != null) { + name = sharedMethod.format("%h.%n"); + } + if (name == null || name.isEmpty()) { + name = "UnnamedCompilation"; + } + return name + "@0x" + Long.toHexString(codeAddress); + } + + @Override + public String cachePath() { + return Options.getRuntimeSourceDestDir().toString(); + } + + @Override + public boolean isRuntimeCompilation() { + return true; + } + + /** + * Returns an empty stream, because there are no types to stream here. All types needed for the + * run-time debug info are installed when needed for providing debug info of the compilation. + * + * @return an empty stream + */ + @Override + protected Stream typeInfo() { + // create type infos on demand for compilation + return Stream.empty(); + } + + /** + * Provides the single compilation with its corresponding method as code info for this object + * file. All the debug info for the object file is produced based on this compilation and + * method. + * + * @return a stream containing the run-time compilation result and method + */ + @Override + protected Stream> codeInfo() { + return Stream.of(Pair.create(sharedMethod, compilation)); + } + + /** + * Returns an empty stream, no any additional data is handled for run-time compilations. + * + * @return an empty stream + */ + @Override + protected Stream dataInfo() { + // no data info needed for run-time compilations + return Stream.empty(); + } + + @Override + protected long getCodeOffset(@SuppressWarnings("unused") SharedMethod method) { + // use the code offset from the compilation + return codeAddress; + } + + /** + * Fetches the package name from the types hub and the types source file name and produces a + * file name with that. There is no guarantee that the source file is at the location of the + * file entry, but it is the best guess we can make at run-time. + * + * @param type the given {@code ResolvedJavaType} + * @return the {@code FileEntry} of the type + */ + @Override + public FileEntry lookupFileEntry(ResolvedJavaType type) { + if (type instanceof SharedType sharedType) { + String[] packageElements = SubstrateUtil.split(sharedType.getHub().getPackageName(), "."); + String fileName = sharedType.getSourceFileName(); + if (fileName != null && !fileName.isEmpty()) { + Path filePath = FileSystems.getDefault().getPath("", packageElements).resolve(fileName); + return lookupFileEntry(filePath); + } + } + return super.lookupFileEntry(type); + } + + private static int getTypeSize(SharedType type) { + if (type.isPrimitive()) { + JavaKind javaKind = type.getStorageKind(); + return (javaKind == JavaKind.Void ? 0 : javaKind.getByteCount()); + } else if (type.isArray()) { + SharedType componentType = (SharedType) type.getComponentType(); + return getObjectLayout().getArrayBaseOffset(componentType.getStorageKind()); + } else if (type.isInterface() || type.isInstanceClass()) { + return getObjectLayout().getFirstFieldOffset(); + } else { + return 0; + } + } + + /** + * Creates a {@code TypeEntry} for use in object files produced for run-time compilations. + * + *

    + * To avoid duplicating debug info, this mainly produces the {@link #getTypeSignature type + * signatures} to link the types to type entries produced at native image build time. Connecting + * the run-time compiled type entry with the native image's type entry is left for the debugger. + * This allows the reuse of type information from the native image, where we have more + * information available to produce debug info. + * + * @param type the {@code SharedType} to process + * @return a {@code TypeEntry} for the type + */ + @Override + protected TypeEntry createTypeEntry(SharedType type) { + String typeName = type.toJavaName(); + LoaderEntry loaderEntry = lookupLoaderEntry(type); + int size = getTypeSize(type); + long classOffset = -1; + String loaderName = loaderEntry.loaderId(); + long typeSignature = getTypeSignature(typeName + loaderName); + long compressedTypeSignature = useHeapBase ? getTypeSignature(INDIRECT_PREFIX + typeName + loaderName) : typeSignature; + + if (type.isPrimitive()) { + JavaKind kind = type.getStorageKind(); + return new PrimitiveTypeEntry(typeName, size, classOffset, typeSignature, kind); + } else { + // otherwise we have a structured type + long layoutTypeSignature = getTypeSignature(LAYOUT_PREFIX + typeName + loaderName); + if (type.isArray()) { + TypeEntry elementTypeEntry = lookupTypeEntry((SharedType) type.getComponentType()); + return new ArrayTypeEntry(typeName, size, classOffset, typeSignature, compressedTypeSignature, + layoutTypeSignature, elementTypeEntry, loaderEntry); + } else { + // otherwise this is a class entry + ClassEntry superClass = type.getSuperclass() == null ? null : (ClassEntry) lookupTypeEntry((SharedType) type.getSuperclass()); + FileEntry fileEntry = lookupFileEntry(type); + // try to get an already generated version of this type + TypeEntry typeEntry = SubstrateDebugTypeEntrySupport.singleton().getTypeEntry(typeSignature); + + if (typeEntry != null) { + // this must be a foreign type (struct, pointer, or primitive) + if (typeEntry instanceof PointerToTypeEntry pointerToTypeEntry && pointerToTypeEntry.getPointerTo() == null) { + // fix-up void pointers + pointerToTypeEntry.setPointerTo(lookupTypeEntry(voidType)); + } + return typeEntry; + } else { + return new ClassEntry(typeName, size, classOffset, typeSignature, compressedTypeSignature, + layoutTypeSignature, superClass, fileEntry, loaderEntry); + } + } + } + } + + /** + * The run-time debug info relies on type entries in the native image. In + * {@link #createTypeEntry} we just produce dummy types that hold just enough information to + * connect them to the types in the native image. Therefore, there is nothing to do for + * processing types at run-time. + * + * @param type the {@code SharedType} of the type entry + * @param typeEntry the {@code TypeEntry} to process + */ + @Override + protected void processTypeEntry(@SuppressWarnings("unused") SharedType type, @SuppressWarnings("unused") TypeEntry typeEntry) { + // nothing to do here + } + + @Override + public long objectOffset(@SuppressWarnings("unused") JavaConstant constant) { + return -1; + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugTypeEntrySupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugTypeEntrySupport.java new file mode 100644 index 000000000000..de86af17f4fa --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/debug/SubstrateDebugTypeEntrySupport.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2025, 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.debug; + +import java.util.HashMap; +import java.util.Map; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.Platform; +import org.graalvm.nativeimage.Platforms; + +import com.oracle.objectfile.debugentry.TypeEntry; +import com.oracle.svm.core.heap.UnknownObjectField; + +import jdk.graal.compiler.api.replacements.Fold; + +public class SubstrateDebugTypeEntrySupport { + /** + * Stores type entries produced from {@code ElementInfo} after analysis for later use during + * debug info generation. We can't get ElementInfo at run-time, but we can reuse the type + * entries produced during the native image build for run-time debug info generation. + */ + @UnknownObjectField(fullyQualifiedTypes = {"java.util.HashMap", "java.util.ImmutableCollections$MapN", "java.util.ImmutableCollections$Map1"}) // + private Map typeEntryMap = new HashMap<>(); + + @Fold + public static SubstrateDebugTypeEntrySupport singleton() { + return ImageSingletons.lookup(SubstrateDebugTypeEntrySupport.class); + } + + @Platforms(Platform.HOSTED_ONLY.class) // + public void addTypeEntry(TypeEntry type) { + assert typeEntryMap instanceof HashMap; + this.typeEntryMap.put(type.getTypeSignature(), type); + } + + @Platforms(Platform.HOSTED_ONLY.class) // + public void trim() { + typeEntryMap = Map.copyOf(typeEntryMap); + } + + public TypeEntry getTypeEntry(Long typeSignature) { + return typeEntryMap.get(typeSignature); + } + +} diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/SubstrateGraalUtils.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/SubstrateGraalUtils.java index d5112144f8a9..9bf99b8de28b 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/SubstrateGraalUtils.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/SubstrateGraalUtils.java @@ -31,6 +31,7 @@ import java.util.EnumMap; import java.util.Map; +import com.oracle.svm.core.deopt.SubstrateInstalledCode; import org.graalvm.collections.EconomicMap; import org.graalvm.nativeimage.ImageSingletons; @@ -39,15 +40,21 @@ import com.oracle.graal.pointsto.util.GraalAccess; import com.oracle.svm.common.option.CommonOptionParser; import com.oracle.svm.core.CPUFeatureAccess; +import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.graal.code.SubstrateCompilationIdentifier; import com.oracle.svm.core.graal.code.SubstrateCompilationResult; import com.oracle.svm.core.graal.meta.RuntimeConfiguration; +import com.oracle.svm.core.log.Log; import com.oracle.svm.core.meta.SharedMethod; import com.oracle.svm.core.meta.SubstrateObjectConstant; import com.oracle.svm.core.option.RuntimeOptionKey; import com.oracle.svm.core.option.RuntimeOptionParser; +import com.oracle.svm.core.option.RuntimeOptionValues; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.graal.isolated.IsolatedGraalUtils; +import com.oracle.svm.graal.meta.RuntimeCodeInstaller; +import com.oracle.svm.graal.meta.SubstrateInstalledCodeImpl; import com.oracle.svm.graal.meta.SubstrateMethod; import jdk.graal.compiler.code.CompilationResult; @@ -70,7 +77,9 @@ import jdk.graal.compiler.phases.OptimisticOptimizations; import jdk.graal.compiler.phases.tiers.Suites; import jdk.graal.compiler.phases.util.Providers; +import jdk.graal.compiler.printer.GraalDebugHandlersFactory; import jdk.vm.ci.code.Architecture; +import jdk.vm.ci.code.InstalledCode; import jdk.vm.ci.meta.ConstantReflectionProvider; import jdk.vm.ci.meta.JavaConstant; @@ -81,6 +90,23 @@ public static CompilationResult compile(DebugContext debug, final SubstrateMetho return doCompile(debug, TruffleRuntimeCompilationSupport.getRuntimeConfig(), TruffleRuntimeCompilationSupport.getLIRSuites(), method); } + public static InstalledCode compileAndInstall(SubstrateMethod method) { + return compileAndInstall(method, () -> new SubstrateInstalledCodeImpl(method)); + } + + public static InstalledCode compileAndInstall(SubstrateMethod method, SubstrateInstalledCode.Factory installedCodeFactory) { + if (SubstrateOptions.shouldCompileInIsolates()) { + return IsolatedGraalUtils.compileInNewIsolateAndInstall(method, installedCodeFactory); + } + RuntimeConfiguration runtimeConfiguration = TruffleRuntimeCompilationSupport.getRuntimeConfig(); + DebugContext debug = new DebugContext.Builder(RuntimeOptionValues.singleton(), new GraalDebugHandlersFactory(runtimeConfiguration.getProviders().getSnippetReflection())).build(); + SubstrateInstalledCode installedCode = installedCodeFactory.createSubstrateInstalledCode(); + CompilationResult compilationResult = doCompile(debug, TruffleRuntimeCompilationSupport.getRuntimeConfig(), TruffleRuntimeCompilationSupport.getLIRSuites(), method); + RuntimeCodeInstaller.install(method, compilationResult, installedCode); + Log.log().string("Code for " + method.format("%H.%n(%p)") + ": " + compilationResult.getTargetCodeSize() + " bytes").newline(); + return (InstalledCode) installedCode; + } + private static final Map compilationProblemsPerAction = new EnumMap<>(ExceptionAction.class); private static final CompilationWatchDog.EventHandler COMPILATION_WATCH_DOG_EVENT_HANDLER = new CompilationWatchDog.EventHandler() { diff --git a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/GraalGraphObjectReplacer.java b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/GraalGraphObjectReplacer.java index 45dcb98224f4..493779d9dfad 100644 --- a/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/GraalGraphObjectReplacer.java +++ b/substratevm/src/com.oracle.svm.graal/src/com/oracle/svm/graal/hosted/runtimecompilation/GraalGraphObjectReplacer.java @@ -43,6 +43,7 @@ import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.svm.common.meta.MultiMethod; +import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.code.CodeInfoTable; import com.oracle.svm.core.code.ImageCodeInfo; import com.oracle.svm.core.graal.meta.SharedRuntimeMethod; @@ -70,6 +71,7 @@ import com.oracle.svm.hosted.meta.HostedMethod; import com.oracle.svm.hosted.meta.HostedType; import com.oracle.svm.hosted.meta.HostedUniverse; +import com.oracle.svm.util.LogUtils; import com.oracle.svm.util.ReflectionUtil; import jdk.graal.compiler.api.replacements.SnippetReflectionProvider; @@ -89,6 +91,8 @@ import jdk.vm.ci.hotspot.HotSpotJVMCIRuntime; import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaType; +import jdk.vm.ci.meta.Local; +import jdk.vm.ci.meta.LocalVariableTable; import jdk.vm.ci.meta.MetaAccessProvider; import jdk.vm.ci.meta.ResolvedJavaField; import jdk.vm.ci.meta.ResolvedJavaMethod; @@ -275,11 +279,26 @@ public synchronized SubstrateMethod createMethod(ResolvedJavaMethod original) { } }, baseType.getJavaClass()); + /* + * With run-time debug info support enabled, ensure LocalVariableTables are + * available in SubstrateMethods if possible. + */ + LocalVariableTable localVariableTable; + if (SubstrateOptions.RuntimeDebugInfo.getValue()) { + try { + localVariableTable = createLocalVariableTable(aMethod.getLocalVariableTable()); + } catch (IllegalStateException e) { + LogUtils.warning("Omit invalid local variable table from method %s", sMethod.getName()); + localVariableTable = null; + } + } else { + localVariableTable = null; + } /* * The links to other meta objects must be set after adding to the methods to avoid * infinite recursion. */ - sMethod.setLinks(createSignature(aMethod.getSignature()), createType(aMethod.getDeclaringClass())); + sMethod.setLinks(createSignature(aMethod.getSignature()), createType(aMethod.getDeclaringClass()), localVariableTable); } } return sMethod; @@ -408,6 +427,31 @@ private synchronized SubstrateSignature createSignature(ResolvedSignature> foreignTypeEntryClasses = Set.of(PrimitiveTypeEntry.class, PointerToTypeEntry.class, ForeignStructTypeEntry.class); + + /* + * A set of fields accessed during run-time debug info generation that are not seen as written + * during analysis, but still reachable through the SubstrateDebugTypeEntrySupport singleton. + */ + public static final Set foreignTypeEntryFields = Set.of( + ReflectionUtil.lookupField(TypeEntry.class, "typeName"), + ReflectionUtil.lookupField(TypeEntry.class, "typeSignature"), + ReflectionUtil.lookupField(ForeignStructTypeEntry.class, "typedefName")); @Override public boolean isInConfiguration(IsInConfigurationAccess access) { @@ -82,52 +116,141 @@ public void afterRegistration(AfterRegistrationAccess access) { */ if (!UniqueShortNameProviderDefaultImpl.UseDefault.useDefaultProvider()) { if (!ImageSingletons.contains(UniqueShortNameProvider.class)) { - // configure a BFD mangler to provide unique short names for method and field - // symbols + /* + * Configure a BFD mangler to provide unique short names for methods, fields and + * classloaders. + */ FeatureImpl.AfterRegistrationAccessImpl accessImpl = (FeatureImpl.AfterRegistrationAccessImpl) access; - // the Graal system loader will not duplicate JDK builtin loader classes + + /* + * Ensure the mangle ignores prefix generation for Graal loaders. + * + * The Graal system loader will not duplicate JDK builtin loader classes. + * + * The Graal app loader and image loader and their parent loader will not duplicate + * classes. The app and image loader should both have the same parent. + */ ClassLoader systemLoader = ClassLoader.getSystemClassLoader(); - // the Graal app loader and image loader and their parent loader will not duplicate - // classes ClassLoader appLoader = accessImpl.getApplicationClassLoader(); ClassLoader imageLoader = accessImpl.getImageClassLoader().getClassLoader(); ClassLoader imageLoaderParent = imageLoader.getParent(); - // the app and image loader should both have the same parent assert imageLoaderParent == appLoader.getParent(); - // ensure the mangle ignores prefix generation for Graal loaders List ignored = List.of(systemLoader, imageLoaderParent, appLoader, imageLoader); - bfdNameProvider = new NativeImageBFDNameProvider(ignored); + + BFDNameProvider bfdNameProvider = new BFDNameProvider(ignored); ImageSingletons.add(UniqueShortNameProvider.class, bfdNameProvider); } } + + /* + * Foreign Type entries are produced upfront. This way we can also use them for run-time + * debug info. + */ + SubstrateDebugTypeEntrySupport typeEntrySupport = new SubstrateDebugTypeEntrySupport(); + ImageSingletons.add(SubstrateDebugTypeEntrySupport.class, typeEntrySupport); } @Override public void beforeAnalysis(BeforeAnalysisAccess access) { /* - * Make the name provider aware of the native libs + * Ensure ClassLoader.nameAndId is available at runtime for type lookup from GDB. */ - if (bfdNameProvider != null) { - var accessImpl = (FeatureImpl.BeforeAnalysisAccessImpl) access; - bfdNameProvider.setNativeLibs(accessImpl.getNativeLibraries()); - } + access.registerAsAccessed(ReflectionUtil.lookupField(ClassLoader.class, "nameAndId")); + var accessImpl = (FeatureImpl.BeforeAnalysisAccessImpl) access; + nativeLibs = accessImpl.getNativeLibraries(); /* - * Ensure ClassLoader.nameAndId is available at runtime for type lookup from gdb + * Make sure classes and fields for foreign type entries stored in the + * SubstrateDebugTypeEntrySupport are available during run-time debug info generation. */ - access.registerAsAccessed(ReflectionUtil.lookupField(ClassLoader.class, "nameAndId")); + for (Class foreignTypeEntryClass : foreignTypeEntryClasses) { + accessImpl.registerAsInHeap(foreignTypeEntryClass); + } + for (Field foreignTypeEntryField : foreignTypeEntryFields) { + access.registerFieldValueTransformer(foreignTypeEntryField, new FieldValueTransformerWithAvailability() { + @Override + public Object transform(Object receiver, Object originalValue) { + return originalValue; + } + + @Override + public boolean isAvailable() { + return BuildPhaseProvider.isAnalysisFinished(); + } + }); + } + /* + * Provide some global symbol for the gdb-debughelpers script. + */ CompressEncoding compressEncoding = ImageSingletons.lookup(CompressEncoding.class); CGlobalData compressionShift = CGlobalDataFactory.createWord(Word.signed(compressEncoding.getShift()), "__svm_compression_shift"); CGlobalData useHeapBase = CGlobalDataFactory.createWord(Word.unsigned(compressEncoding.hasBase() ? 1 : 0), "__svm_use_heap_base"); CGlobalData reservedHubBitsMask = CGlobalDataFactory.createWord(Word.unsigned(Heap.getHeap().getObjectHeader().getReservedHubBitsMask()), "__svm_reserved_bits_mask"); CGlobalData objectAlignment = CGlobalDataFactory.createWord(Word.unsigned(ConfigurationValues.getObjectLayout().getAlignment()), "__svm_object_alignment"); + CGlobalData frameSizeStatusMask = CGlobalDataFactory.createWord(Word.unsigned(CodeInfoDecoder.FRAME_SIZE_STATUS_MASK), "__svm_frame_size_status_mask"); CGlobalData heapBaseRegnum = CGlobalDataFactory.createWord(Word.unsigned(ReservedRegisters.singleton().getHeapBaseRegister().number), "__svm_heap_base_regnum"); CGlobalDataFeature.singleton().registerWithGlobalHiddenSymbol(compressionShift); CGlobalDataFeature.singleton().registerWithGlobalHiddenSymbol(useHeapBase); CGlobalDataFeature.singleton().registerWithGlobalHiddenSymbol(reservedHubBitsMask); CGlobalDataFeature.singleton().registerWithGlobalHiddenSymbol(objectAlignment); + CGlobalDataFeature.singleton().registerWithGlobalHiddenSymbol(frameSizeStatusMask); CGlobalDataFeature.singleton().registerWithGlobalHiddenSymbol(heapBaseRegnum); + + /* + * Create a global symbol for the jit debug descriptor with proper initial values for the + * GDB JIT compilation interface. + */ + if (SubstrateOptions.RuntimeDebugInfo.getValue()) { + Architecture arch = ConfigurationValues.getTarget().arch; + ByteBuffer buffer = ByteBuffer.allocate(SizeOf.get(GdbJitInterface.JITDescriptor.class)).order(arch.getByteOrder()); + + /* + * Set version to 1. Must be 1 otherwise GDB does not register breakpoints for the GDB + * JIT Compilation interface. + */ + buffer.putInt(1); + + /* Set action flag to JIT_NOACTION (0). */ + buffer.putInt(GdbJitInterface.JITActions.JIT_NOACTION.ordinal()); + + /* + * Set relevant entry to nullptr. This is the pointer to the debug info entry that is + * affected by the GDB JIT interface action. + */ + buffer.putLong(0); + + /* + * Set first entry to nullptr. This is the pointer to the last debug info entry notified + * to the GDB JIT interface We will prepend new entries here. + */ + buffer.putLong(0); + + CGlobalDataFeature.singleton().registerWithGlobalSymbol(CGlobalDataFactory.createBytes(buffer::array, "__jit_debug_descriptor")); + } + } + + @Override + public void afterAnalysis(AfterAnalysisAccess access) { + var accessImpl = (FeatureImpl.AfterAnalysisAccessImpl) access; + var metaAccess = accessImpl.getMetaAccess(); + + /* + * Traverse all types and process native types into type entries. These type entries can + * also be reused for run-time debug info generation. + */ + for (AnalysisType t : accessImpl.getUniverse().getTypes()) { + if (nativeLibs.isWordBase(t)) { + TypeEntry typeEntry = NativeImageDebugInfoProvider.processElementInfo(nativeLibs, metaAccess, t); + /* + * We will always create a type entry here. If no element info is found, or we can't + * find the pointed to type we create a pointer to void/primitive type. + */ + SubstrateDebugTypeEntrySupport.singleton().addTypeEntry(typeEntry); + } + } + // the map is complete now -> trim it for storing it more efficiently + SubstrateDebugTypeEntrySupport.singleton().trim(); } @Override @@ -143,7 +266,15 @@ public void beforeImageWrite(BeforeImageWriteAccess access) { DebugInfoProvider provider = new NativeImageDebugInfoProvider(debugContext, image.getCodeCache(), image.getHeap(), image.getNativeLibs(), accessImpl.getHostedMetaAccess(), runtimeConfiguration); var objectFile = image.getObjectFile(); - objectFile.installDebugInfo(provider); + + int debugInfoGenerationThreadCount = SubstrateOptions.DebugInfoGenerationThreadCount.getValue(); + if (debugInfoGenerationThreadCount > 0) { + try (ForkJoinPool threadPool = new ForkJoinPool(debugInfoGenerationThreadCount)) { + threadPool.submit(() -> objectFile.installDebugInfo(provider)).join(); + } + } else { + objectFile.installDebugInfo(provider); + } if (Platform.includedIn(Platform.LINUX.class) && SubstrateOptions.UseImagebuildDebugSections.getValue()) { /*- @@ -165,6 +296,10 @@ public boolean isLoadable() { }; }; + /* + * Create a section that triggers GDB to read debugging assistance information from + * gdb-debughelpers.py in the current working directory. + */ Supplier makeGDBSectionImpl = () -> { var content = AssemblyBuffer.createOutputAssembler(objectFile.getByteOrder()); // 1 -> python file @@ -184,7 +319,7 @@ public boolean isLoadable() { objectFile.newUserDefinedSection(".debug.svm.imagebuild.arguments", makeSectionImpl.apply(DiagnosticUtils.getBuilderArguments(imageClassLoader))); objectFile.newUserDefinedSection(".debug.svm.imagebuild.java.properties", makeSectionImpl.apply(DiagnosticUtils.getBuilderProperties())); - Path svmDebugHelper = Path.of(System.getProperty("java.home"), "lib/svm/debug/gdb-debughelpers.py"); + Path svmDebugHelper = Path.of(System.getProperty("java.home"), "lib", "svm", "debug", "gdb-debughelpers.py"); if (Files.exists(svmDebugHelper)) { objectFile.newUserDefinedSection(".debug_gdb_scripts", makeGDBSectionImpl.get()); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java index ff90084f7ec4..05e05832390c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -25,24 +25,21 @@ */ package com.oracle.svm.hosted.image; -import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFrameSizeChange.Type.CONTRACT; -import static com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFrameSizeChange.Type.EXTEND; +import static com.oracle.svm.hosted.c.info.AccessorInfo.AccessorKind.ADDRESS; +import static com.oracle.svm.hosted.c.info.AccessorInfo.AccessorKind.GETTER; +import static com.oracle.svm.hosted.c.info.AccessorInfo.AccessorKind.SETTER; import java.lang.reflect.Modifier; import java.nio.file.Path; -import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedList; +import java.util.Comparator; import java.util.List; -import java.util.Objects; import java.util.Set; -import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.graalvm.collections.EconomicMap; +import org.graalvm.collections.Pair; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.c.struct.CPointerTo; import org.graalvm.nativeimage.c.struct.RawPointerTo; @@ -50,34 +47,46 @@ import com.oracle.graal.pointsto.infrastructure.OriginalClassProvider; import com.oracle.graal.pointsto.infrastructure.WrappedJavaMethod; import com.oracle.graal.pointsto.infrastructure.WrappedJavaType; -import com.oracle.graal.pointsto.meta.AnalysisMethod; -import com.oracle.objectfile.debuginfo.DebugInfoProvider; +import com.oracle.graal.pointsto.meta.AnalysisMetaAccess; +import com.oracle.graal.pointsto.meta.AnalysisType; +import com.oracle.objectfile.debugentry.ArrayTypeEntry; +import com.oracle.objectfile.debugentry.ClassEntry; +import com.oracle.objectfile.debugentry.EnumClassEntry; +import com.oracle.objectfile.debugentry.FieldEntry; +import com.oracle.objectfile.debugentry.FileEntry; +import com.oracle.objectfile.debugentry.ForeignStructTypeEntry; +import com.oracle.objectfile.debugentry.InterfaceClassEntry; +import com.oracle.objectfile.debugentry.LoaderEntry; +import com.oracle.objectfile.debugentry.LocalEntry; +import com.oracle.objectfile.debugentry.MethodEntry; +import com.oracle.objectfile.debugentry.PointerToTypeEntry; +import com.oracle.objectfile.debugentry.PrimitiveTypeEntry; +import com.oracle.objectfile.debugentry.StructureTypeEntry; +import com.oracle.objectfile.debugentry.TypeEntry; +import com.oracle.svm.core.StaticFieldsSupport; import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.UniqueShortNameProvider; -import com.oracle.svm.core.code.CompilationResultFrameTree.Builder; -import com.oracle.svm.core.code.CompilationResultFrameTree.CallNode; -import com.oracle.svm.core.code.CompilationResultFrameTree.FrameNode; -import com.oracle.svm.core.code.CompilationResultFrameTree.Visitor; import com.oracle.svm.core.config.ConfigurationValues; -import com.oracle.svm.core.config.ObjectLayout; -import com.oracle.svm.core.graal.code.SubstrateBackend.SubstrateMarkId; +import com.oracle.svm.core.debug.BFDNameProvider; +import com.oracle.svm.core.debug.SharedDebugInfoProvider; +import com.oracle.svm.core.debug.SubstrateDebugTypeEntrySupport; import com.oracle.svm.core.graal.meta.RuntimeConfiguration; import com.oracle.svm.core.image.ImageHeapPartition; -import com.oracle.svm.hosted.DeadlockWatchdog; +import com.oracle.svm.core.meta.SharedMethod; +import com.oracle.svm.core.meta.SharedType; +import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.c.NativeLibraries; import com.oracle.svm.hosted.c.info.AccessorInfo; import com.oracle.svm.hosted.c.info.ElementInfo; +import com.oracle.svm.hosted.c.info.EnumInfo; import com.oracle.svm.hosted.c.info.PointerToInfo; import com.oracle.svm.hosted.c.info.PropertyInfo; import com.oracle.svm.hosted.c.info.RawStructureInfo; import com.oracle.svm.hosted.c.info.SizableInfo; -import com.oracle.svm.hosted.c.info.SizableInfo.ElementKind; import com.oracle.svm.hosted.c.info.StructFieldInfo; import com.oracle.svm.hosted.c.info.StructInfo; -import com.oracle.svm.hosted.image.NativeImageHeap.ObjectInfo; import com.oracle.svm.hosted.image.sources.SourceManager; import com.oracle.svm.hosted.meta.HostedArrayClass; -import com.oracle.svm.hosted.meta.HostedClass; import com.oracle.svm.hosted.meta.HostedField; import com.oracle.svm.hosted.meta.HostedInstanceClass; import com.oracle.svm.hosted.meta.HostedInterface; @@ -85,49 +94,46 @@ import com.oracle.svm.hosted.meta.HostedMethod; import com.oracle.svm.hosted.meta.HostedPrimitiveType; import com.oracle.svm.hosted.meta.HostedType; -import com.oracle.svm.hosted.substitute.SubstitutionField; +import com.oracle.svm.hosted.substitute.InjectedFieldsType; import com.oracle.svm.hosted.substitute.SubstitutionMethod; +import com.oracle.svm.hosted.substitute.SubstitutionType; import com.oracle.svm.util.ClassUtil; import jdk.graal.compiler.code.CompilationResult; import jdk.graal.compiler.debug.DebugContext; -import jdk.graal.compiler.graph.NodeSourcePosition; -import jdk.graal.compiler.java.StableMethodNameFormatter; -import jdk.graal.compiler.util.Digest; -import jdk.vm.ci.aarch64.AArch64; -import jdk.vm.ci.amd64.AMD64; -import jdk.vm.ci.code.BytecodeFrame; -import jdk.vm.ci.code.BytecodePosition; -import jdk.vm.ci.code.CallingConvention; -import jdk.vm.ci.code.Register; -import jdk.vm.ci.code.RegisterValue; -import jdk.vm.ci.code.StackSlot; -import jdk.vm.ci.meta.AllocatableValue; import jdk.vm.ci.meta.JavaConstant; import jdk.vm.ci.meta.JavaKind; -import jdk.vm.ci.meta.JavaValue; -import jdk.vm.ci.meta.LineNumberTable; -import jdk.vm.ci.meta.Local; -import jdk.vm.ci.meta.LocalVariableTable; -import jdk.vm.ci.meta.PrimitiveConstant; import jdk.vm.ci.meta.ResolvedJavaField; import jdk.vm.ci.meta.ResolvedJavaMethod; import jdk.vm.ci.meta.ResolvedJavaType; -import jdk.vm.ci.meta.Signature; -import jdk.vm.ci.meta.Value; /** * Implementation of the DebugInfoProvider API interface that allows type, code and heap data info - * to be passed to an ObjectFile when generation of debug info is enabled. + * to be passed to an ObjectFile when generation of debug info is enabled at native image build + * time. */ -class NativeImageDebugInfoProvider extends NativeImageDebugInfoProviderBase implements DebugInfoProvider { - private final DebugContext debugContext; +class NativeImageDebugInfoProvider extends SharedDebugInfoProvider { + protected final NativeImageHeap heap; + protected final NativeImageCodeCache codeCache; + protected final NativeLibraries nativeLibs; + + protected final int primitiveStartOffset; + protected final int referenceStartOffset; private final Set allOverrides; - NativeImageDebugInfoProvider(DebugContext debugContext, NativeImageCodeCache codeCache, NativeImageHeap heap, NativeLibraries nativeLibs, HostedMetaAccess metaAccess, + NativeImageDebugInfoProvider(DebugContext debug, NativeImageCodeCache codeCache, NativeImageHeap heap, NativeLibraries nativeLibs, HostedMetaAccess metaAccess, RuntimeConfiguration runtimeConfiguration) { - super(codeCache, heap, nativeLibs, metaAccess, runtimeConfiguration); - this.debugContext = debugContext; + super(debug, runtimeConfiguration, metaAccess); + this.heap = heap; + this.codeCache = codeCache; + this.nativeLibs = nativeLibs; + + /* Offsets need to be adjusted relative to the heap base plus partition-specific offset. */ + NativeImageHeap.ObjectInfo primitiveFields = heap.getObjectInfo(StaticFieldsSupport.getCurrentLayerStaticPrimitiveFields()); + NativeImageHeap.ObjectInfo objectFields = heap.getObjectInfo(StaticFieldsSupport.getCurrentLayerStaticObjectFields()); + primitiveStartOffset = (int) primitiveFields.getOffset(); + referenceStartOffset = (int) objectFields.getOffset(); + /* Calculate the set of all HostedMethods that are overrides. */ allOverrides = heap.hUniverse.getMethods().stream() .filter(HostedMethod::hasVTableIndex) @@ -136,921 +142,797 @@ class NativeImageDebugInfoProvider extends NativeImageDebugInfoProviderBase impl .collect(Collectors.toSet()); } - @Override - public Stream typeInfoProvider() { - Stream headerTypeInfo = computeHeaderTypeInfo(); - Stream heapTypeInfo = heap.hUniverse.getTypes().stream().map(this::createDebugTypeInfo); - return Stream.concat(headerTypeInfo, heapTypeInfo); - } - - @Override - public Stream codeInfoProvider() { - return codeCache.getOrderedCompilations().stream().map(pair -> new NativeImageDebugCodeInfo(pair.getLeft(), pair.getRight())); - } - - @Override - public Stream dataInfoProvider() { - return heap.getObjects().stream().map(this::createDebugDataInfo); - } - - private abstract class NativeImageDebugFileInfo implements DebugFileInfo { - private final Path fullFilePath; - - @SuppressWarnings("try") - NativeImageDebugFileInfo(HostedType hostedType) { - ResolvedJavaType javaType = getDeclaringClass(hostedType, false); - Class clazz = hostedType.getJavaClass(); - SourceManager sourceManager = ImageSingletons.lookup(SourceManager.class); - try (DebugContext.Scope s = debugContext.scope("DebugFileInfo", hostedType)) { - Path filePath = sourceManager.findAndCacheSource(javaType, clazz, debugContext); - if (filePath == null && (hostedType instanceof HostedInstanceClass || hostedType instanceof HostedInterface)) { - // conjure up an appropriate, unique file name to keep tools happy - // even though we cannot find a corresponding source - filePath = fullFilePathFromClassName(hostedType); - } - fullFilePath = filePath; - } catch (Throwable e) { - throw debugContext.handle(e); - } + @SuppressWarnings("unused") + private static ResolvedJavaType getOriginal(ResolvedJavaType type) { + /* + * Unwrap then traverse through substitutions to the original. We don't want to get the + * original type of LambdaSubstitutionType to keep the stable name. + */ + ResolvedJavaType targetType = type; + while (targetType instanceof WrappedJavaType wrappedJavaType) { + targetType = wrappedJavaType.getWrapped(); } - @SuppressWarnings("try") - NativeImageDebugFileInfo(ResolvedJavaMethod method) { - /* - * Note that this constructor allows for any ResolvedJavaMethod, not just a - * HostedMethod, because it needs to provide common behaviour for DebugMethodInfo, - * DebugCodeInfo and DebugLocationInfo records. The former two are derived from a - * HostedMethod while the latter may be derived from an arbitrary ResolvedJavaMethod. - */ - ResolvedJavaType javaType; - if (method instanceof HostedMethod) { - javaType = getDeclaringClass((HostedMethod) method, false); - } else { - javaType = method.getDeclaringClass(); - } - Class clazz = OriginalClassProvider.getJavaClass(javaType); - SourceManager sourceManager = ImageSingletons.lookup(SourceManager.class); - try (DebugContext.Scope s = debugContext.scope("DebugFileInfo", javaType)) { - fullFilePath = sourceManager.findAndCacheSource(javaType, clazz, debugContext); - } catch (Throwable e) { - throw debugContext.handle(e); - } + if (targetType instanceof SubstitutionType substitutionType) { + targetType = substitutionType.getOriginal(); + } else if (targetType instanceof InjectedFieldsType injectedFieldsType) { + targetType = injectedFieldsType.getOriginal(); } - @SuppressWarnings("try") - NativeImageDebugFileInfo(HostedField hostedField) { - ResolvedJavaType javaType = getDeclaringClass(hostedField, false); - HostedType hostedType = hostedField.getDeclaringClass(); - Class clazz = hostedType.getJavaClass(); - SourceManager sourceManager = ImageSingletons.lookup(SourceManager.class); - try (DebugContext.Scope s = debugContext.scope("DebugFileInfo", hostedType)) { - fullFilePath = sourceManager.findAndCacheSource(javaType, clazz, debugContext); - } catch (Throwable e) { - throw debugContext.handle(e); - } - } + return targetType; + } - @Override - public String fileName() { - if (fullFilePath != null) { - Path filename = fullFilePath.getFileName(); - if (filename != null) { - return filename.toString(); - } - } - return ""; + @SuppressWarnings("unused") + private static ResolvedJavaMethod getAnnotatedOrOriginal(ResolvedJavaMethod method) { + ResolvedJavaMethod targetMethod = method; + while (targetMethod instanceof WrappedJavaMethod wrappedJavaMethod) { + targetMethod = wrappedJavaMethod.getWrapped(); } + /* + * This method is only used when identifying the modifiers or the declaring class of a + * HostedMethod. Normally the method unwraps to the underlying JVMCI method which is the one + * that provides bytecode to the compiler as well as, line numbers and local info. If we + * unwrap to a SubstitutionMethod then we use the annotated method, not the JVMCI method + * that the annotation refers to since that will be the one providing the bytecode etc used + * by the compiler. If we unwrap to any other, custom substitution method we simply use it + * rather than dereferencing to the original. The difference is that the annotated method's + * bytecode will be used to replace the original and the debugger needs to use it to + * identify the file and access permissions. A custom substitution may exist alongside the + * original, as is the case with some uses for reflection. So, we don't want to conflate the + * custom substituted method and the original. In this latter case the method code will be + * synthesized without reference to the bytecode of the original. Hence, there is no + * associated file and the permissions need to be determined from the custom substitution + * method itself. + */ - @Override - public Path filePath() { - if (fullFilePath != null) { - return fullFilePath.getParent(); - } - return null; + if (targetMethod instanceof SubstitutionMethod substitutionMethod) { + targetMethod = substitutionMethod.getAnnotated(); } - } - private abstract class NativeImageDebugTypeInfo extends NativeImageDebugFileInfo implements DebugTypeInfo { - protected final HostedType hostedType; + return targetMethod; + } - @SuppressWarnings("try") - protected NativeImageDebugTypeInfo(HostedType hostedType) { - super(hostedType); - this.hostedType = hostedType; - } + @Override + public String cachePath() { + return SubstrateOptions.getDebugInfoSourceCacheRoot().toString(); + } - @SuppressWarnings("try") - @Override - public void debugContext(Consumer action) { - try (DebugContext.Scope s = debugContext.scope("DebugTypeInfo", typeName())) { - action.accept(debugContext); + /** + * Logs information of {@link NativeImageHeap.ObjectInfo ObjectInfo}. + * + * @param data the data info to process + */ + @Override + @SuppressWarnings("try") + protected void installDataInfo(Object data) { + // log ObjectInfo data + if (debug.isLogEnabled(DebugContext.INFO_LEVEL) && data instanceof NativeImageHeap.ObjectInfo objectInfo) { + try (DebugContext.Scope s = debug.scope("DebugDataInfo")) { + long offset = objectInfo.getOffset(); + long size = objectInfo.getSize(); + String typeName = objectInfo.getClazz().toJavaName(); + ImageHeapPartition partition = objectInfo.getPartition(); + + debug.log(DebugContext.INFO_LEVEL, "Data: offset 0x%x size 0x%x type %s partition %s{%d}@%d provenance %s ", offset, size, typeName, partition.getName(), partition.getSize(), + partition.getStartOffset(), objectInfo); } catch (Throwable e) { - throw debugContext.handle(e); + throw debug.handle(e); } + } else { + super.installDataInfo(data); } + } - @Override - public long typeSignature(String prefix) { - return Digest.digestAsUUID(prefix + typeName()).getLeastSignificantBits(); - } - - public String toJavaName(@SuppressWarnings("hiding") HostedType hostedType) { - return getDeclaringClass(hostedType, true).toJavaName(); + private static int elementSize(ElementInfo elementInfo) { + if (!(elementInfo instanceof SizableInfo) || elementInfo instanceof StructInfo structInfo && structInfo.isIncomplete()) { + return 0; } + return ((SizableInfo) elementInfo).getSizeInBytes(); + } - @Override - public ResolvedJavaType idType() { - // always use the original type for establishing identity - return getOriginal(hostedType); + private static String elementName(ElementInfo elementInfo) { + if (elementInfo == null) { + return ""; + } else { + return elementInfo.getName(); } + } - @Override - public String typeName() { - return toJavaName(hostedType); - } + private static SizableInfo.ElementKind elementKind(SizableInfo sizableInfo) { + return sizableInfo.getKind(); + } - @Override - public long classOffset() { - /* - * Only query the heap for reachable types. These are guaranteed to have been seen by - * the analysis and to exist in the shadow heap. - */ - if (hostedType.getWrapped().isReachable()) { - ObjectInfo objectInfo = heap.getObjectInfo(hostedType.getHub()); - if (objectInfo != null) { - return objectInfo.getOffset(); - } + /** + * Fetch the typedef name of an element info for {@link StructInfo structs} or + * {@link PointerToInfo pointer types}. Otherwise, we use the name of the element info. + * + * @param elementInfo the given element info + * @return the typedef name + */ + private static String typedefName(ElementInfo elementInfo) { + String name = null; + if (elementInfo != null) { + if (elementInfo instanceof PointerToInfo) { + name = ((PointerToInfo) elementInfo).getTypedefName(); + } else if (elementInfo instanceof StructInfo) { + name = ((StructInfo) elementInfo).getTypedefName(); } - return -1; - } - - @Override - public int size() { - if (hostedType instanceof HostedInstanceClass) { - /* We know the actual instance size in bytes. */ - return ((HostedInstanceClass) hostedType).getInstanceSize(); - } else if (hostedType instanceof HostedArrayClass) { - /* Use the size of header common to all arrays of this type. */ - return getObjectLayout().getArrayBaseOffset(hostedType.getComponentType().getStorageKind()); - } else if (hostedType instanceof HostedInterface) { - /* Use the size of the header common to all implementors. */ - return getObjectLayout().getFirstFieldOffset(); - } else { - /* Use the number of bytes needed needed to store the value. */ - assert hostedType instanceof HostedPrimitiveType; - JavaKind javaKind = hostedType.getStorageKind(); - return (javaKind == JavaKind.Void ? 0 : javaKind.getByteCount()); + if (name == null) { + name = elementInfo.getName(); } } + return name; } - private class NativeImageHeaderTypeInfo implements DebugHeaderTypeInfo { - String typeName; - int size; - DebugFieldInfo hubField; - List fieldInfos; - - NativeImageHeaderTypeInfo(String typeName, int size, DebugFieldInfo hubField) { - this.typeName = typeName; - this.size = size; - this.hubField = hubField; - this.fieldInfos = new LinkedList<>(); - } - - void addField(String name, ResolvedJavaType valueType, int offset, @SuppressWarnings("hiding") int size) { - NativeImageDebugHeaderFieldInfo fieldinfo = new NativeImageDebugHeaderFieldInfo(name, valueType, offset, size); - fieldInfos.add(fieldinfo); - } + /** + * Checks if a foreign type is a pointer type with {@link NativeLibraries}. + * + * @param type the given foreign type + * @return true if the type is a foreign pointer type, otherwise false + */ + private boolean isForeignPointerType(HostedType type) { + // unwrap because native libs operates on the analysis type universe + return nativeLibs.isPointerBase(type.getWrapped()); + } - @Override - public ResolvedJavaType idType() { - // The header type is unique in that it does not have an associated ResolvedJavaType - return null; - } + /* + * Foreign pointer types have associated element info which describes the target type. The + * following helpers support querying of and access to this element info. + */ - @SuppressWarnings("try") - @Override - public void debugContext(Consumer action) { - try (DebugContext.Scope s = debugContext.scope("DebugTypeInfo", typeName())) { - action.accept(debugContext); - } catch (Throwable e) { - throw debugContext.handle(e); + protected static boolean isTypedField(ElementInfo elementInfo) { + if (elementInfo instanceof StructFieldInfo) { + for (ElementInfo child : elementInfo.getChildren()) { + if (child instanceof AccessorInfo) { + switch (((AccessorInfo) child).getAccessorKind()) { + case GETTER: + case SETTER: + case ADDRESS: + return true; + } + } } } + return false; + } - @Override - public String typeName() { - return typeName; + protected HostedType getFieldType(StructFieldInfo field) { + // we should always have some sort of accessor, preferably a GETTER or a SETTER + // but possibly an ADDRESS accessor + for (ElementInfo elt : field.getChildren()) { + if (elt instanceof AccessorInfo accessorInfo) { + if (accessorInfo.getAccessorKind() == GETTER) { + return heap.hUniverse.lookup(accessorInfo.getReturnType()); + } + } } - - @Override - public long typeSignature(String prefix) { - return Digest.digestAsUUID(typeName).getLeastSignificantBits(); + for (ElementInfo elt : field.getChildren()) { + if (elt instanceof AccessorInfo accessorInfo) { + if (accessorInfo.getAccessorKind() == SETTER) { + return heap.hUniverse.lookup(accessorInfo.getParameterType(0)); + } + } } - - @Override - public DebugTypeKind typeKind() { - return DebugTypeKind.HEADER; + for (ElementInfo elt : field.getChildren()) { + if (elt instanceof AccessorInfo accessorInfo) { + if (accessorInfo.getAccessorKind() == ADDRESS) { + return heap.hUniverse.lookup(accessorInfo.getReturnType()); + } + } } + assert false : "Field %s must have a GETTER, SETTER, ADDRESS or OFFSET accessor".formatted(field); + // treat it as a word? + // n.b. we want a hosted type not an analysis type + return heap.hUniverse.lookup(wordBaseType); + } - @Override - public String fileName() { - return ""; + protected static boolean fieldTypeIsEmbedded(StructFieldInfo field) { + // we should always have some sort of accessor, preferably a GETTER or a SETTER + // but possibly an ADDRESS + for (ElementInfo elt : field.getChildren()) { + if (elt instanceof AccessorInfo accessorInfo) { + if (accessorInfo.getAccessorKind() == GETTER) { + return false; + } + } } - - @Override - public Path filePath() { - return null; + for (ElementInfo elt : field.getChildren()) { + if (elt instanceof AccessorInfo accessorInfo) { + if (accessorInfo.getAccessorKind() == SETTER) { + return false; + } + } } - - @Override - public long classOffset() { - return -1; + for (ElementInfo elt : field.getChildren()) { + if (elt instanceof AccessorInfo accessorInfo) { + if (accessorInfo.getAccessorKind() == ADDRESS) { + return true; + } + } } + throw VMError.shouldNotReachHere("Field %s must have a GETTER, SETTER, ADDRESS or OFFSET accessor".formatted(field)); + } - @Override - public int size() { - return size; - } + /** + * Creates a stream all types from the hosted universe. + * + * @return a stream of type in the hosted universe + */ + @Override + protected Stream typeInfo() { + // null represents the header type + return heap.hUniverse.getTypes().stream().map(type -> type); + } - @Override - public Stream fieldInfoProvider() { - return fieldInfos.stream(); - } + /** + * Creates a stream of all compilations with the corresponding hosted methods from the native + * image code cache. + * + * @return a stream of compilations + */ + @Override + protected Stream> codeInfo() { + return codeCache.getOrderedCompilations().stream().map(pair -> Pair.create(pair.getLeft(), pair.getRight())); + } - @Override - public DebugFieldInfo hubField() { - return hubField; - } + /** + * Creates a stream of all {@link NativeImageHeap.ObjectInfo objects} in the native image heap. + * + * @return a stream of native image heap objects. + */ + @Override + protected Stream dataInfo() { + return heap.getObjects().stream().map(obj -> obj); } - private class NativeImageDebugHeaderFieldInfo implements DebugFieldInfo { - private final String name; - private final ResolvedJavaType valueType; - private final int offset; - private final int size; - private final int modifiers; - - NativeImageDebugHeaderFieldInfo(String name, ResolvedJavaType valueType, int offset, int size) { - this.name = name; - this.valueType = valueType; - this.offset = offset; - this.size = size; - this.modifiers = Modifier.PUBLIC; - } + @Override + protected long getCodeOffset(SharedMethod method) { + assert method instanceof HostedMethod; + return ((HostedMethod) method).getCodeAddressOffset(); + } - @Override - public String name() { - return name; - } + /** + * Processes type entries for {@link HostedType hosted types}. + * + *

    + * We need to process fields of {@link StructureTypeEntry structured types} after creating it to + * make sure that it is available for the field types. Otherwise, this would create a cycle for + * the type lookup. + * + *

    + * For a {@link ClassEntry} we also need to process interfaces and methods for the same reason + * with the fields for structured types. + * + * @param type the {@code SharedType} of the type entry + * @param typeEntry the {@code TypeEntry} to process + */ + @Override + protected void processTypeEntry(SharedType type, TypeEntry typeEntry) { + assert type instanceof HostedType; + HostedType hostedType = (HostedType) type; + + if (typeEntry instanceof StructureTypeEntry structureTypeEntry) { + if (typeEntry instanceof ArrayTypeEntry arrayTypeEntry) { + processArrayFields(hostedType, arrayTypeEntry); + } else if (typeEntry instanceof ForeignStructTypeEntry foreignStructTypeEntry) { + processForeignTypeFields(hostedType, foreignStructTypeEntry); + } else { + processFieldEntries(hostedType, structureTypeEntry); + } - @Override - public ResolvedJavaType valueType() { - if (valueType instanceof HostedType) { - return getOriginal((HostedType) valueType); + if (typeEntry instanceof ClassEntry classEntry) { + processInterfaces(hostedType, classEntry); + processMethods(hostedType, classEntry); } - return valueType; } + } - @Override - public int offset() { - return offset; - } + /** + * For processing methods of a type, we iterate over all its declared methods and lookup the + * corresponding {@link MethodEntry} objects. This ensures that all declared methods of a type + * are installed. + * + * @param type the given type + * @param classEntry the type's {@code ClassEntry} + */ + private void processMethods(HostedType type, ClassEntry classEntry) { + for (HostedMethod method : type.getAllDeclaredMethods()) { + MethodEntry methodEntry = lookupMethodEntry(method); - @Override - public int size() { - return size; + if (debug.isLogEnabled()) { + debug.log("typename %s adding %s method %s %s(%s)%n", classEntry.getTypeName(), methodEntry.getModifiersString(), methodEntry.getValueType().getTypeName(), methodEntry.getMethodName(), + formatParams(methodEntry.getThisParam(), methodEntry.getParams())); + } } + } - @Override - public boolean isEmbedded() { - return false; + private static String formatParams(LocalEntry thisParam, List paramInfo) { + if (paramInfo.isEmpty()) { + return ""; } - - @Override - public int modifiers() { - return modifiers; + StringBuilder builder = new StringBuilder(); + if (thisParam != null) { + builder.append(thisParam.type().getTypeName()); + builder.append(' '); + builder.append(thisParam.name()); } - - @Override - public String fileName() { - return ""; + for (LocalEntry param : paramInfo) { + if (!builder.isEmpty()) { + builder.append(", "); + } + builder.append(param.type().getTypeName()); + builder.append(' '); + builder.append(param.name()); } - @Override - public Path filePath() { - return null; - } + return builder.toString(); } - private Stream computeHeaderTypeInfo() { - ObjectLayout ol = getObjectLayout(); - - List infos = new LinkedList<>(); - int hubOffset = ol.getHubOffset(); - - NativeImageDebugHeaderFieldInfo hubField = new NativeImageDebugHeaderFieldInfo("hub", hubType, hubOffset, ol.getHubSize()); - NativeImageHeaderTypeInfo objHeader = new NativeImageHeaderTypeInfo("_objhdr", ol.getFirstFieldOffset(), hubField); - if (hubOffset > 0) { - assert hubOffset == Integer.BYTES || hubOffset == Long.BYTES; - JavaKind kind = hubOffset == Integer.BYTES ? JavaKind.Int : JavaKind.Long; - objHeader.addField("reserved", javaKindToHostedType.get(kind), 0, hubOffset); + /** + * Produce a method name of a {@code HostedMethod} for the debug info. + * + * @param method method to produce a name for + * @return method name for the debug info + */ + @Override + protected String getMethodName(SharedMethod method) { + String name; + if (method instanceof HostedMethod hostedMethod) { + name = hostedMethod.getName(); + // replace (method name of a constructor) with the class name + if (hostedMethod.isConstructor()) { + name = hostedMethod.getDeclaringClass().toJavaName(); + if (name.indexOf('.') >= 0) { + name = name.substring(name.lastIndexOf('.') + 1); + } + if (name.indexOf('$') >= 0) { + name = name.substring(name.lastIndexOf('$') + 1); + } + } + } else { + name = super.getMethodName(method); } - infos.add(objHeader); + return name; + } - return infos.stream(); + @Override + public boolean isOverride(SharedMethod method) { + return method instanceof HostedMethod && allOverrides.contains(method); } - private class NativeImageDebugEnumTypeInfo extends NativeImageDebugInstanceTypeInfo implements DebugEnumTypeInfo { + @Override + public boolean isVirtual(SharedMethod method) { + return method instanceof HostedMethod hostedMethod && hostedMethod.hasVTableIndex(); + } - NativeImageDebugEnumTypeInfo(HostedInstanceClass enumClass) { - super(enumClass); - } + /** + * Fetch a methods symbol produced by the {@link BFDNameProvider}. + * + * @param method method to get the symbol name for + * @return symbol name of the method + */ + @Override + public String getSymbolName(SharedMethod method) { + return NativeImage.localSymbolNameForMethod(method); + } - @Override - public DebugTypeKind typeKind() { - return DebugTypeKind.ENUM; + /** + * Process interfaces from the hosted type and add the class entry as an implementor. This + * ensures all interfaces are installed as debug entries. + * + * @param type the given type + * @param classEntry the {@code ClassEntry} of the type + */ + private void processInterfaces(HostedType type, ClassEntry classEntry) { + for (HostedType interfaceType : type.getInterfaces()) { + TypeEntry entry = lookupTypeEntry(interfaceType); + if (entry instanceof InterfaceClassEntry interfaceClassEntry) { + if (debug.isLogEnabled()) { + debug.log("typename %s adding interface %s%n", classEntry.getTypeName(), interfaceType.toJavaName()); + } + interfaceClassEntry.addImplementor(classEntry); + } } } - private class NativeImageDebugInstanceTypeInfo extends NativeImageDebugTypeInfo implements DebugInstanceTypeInfo { - NativeImageDebugInstanceTypeInfo(HostedType hostedType) { - super(hostedType); - } + /** + * For arrays, we add a synthetic field for their length. This ensures that the length can be + * exposed in the object files debug info. + * + * @param type the given array type + * @param arrayTypeEntry the {@code ArrayTypeEntry} of the type + */ + private void processArrayFields(HostedType type, ArrayTypeEntry arrayTypeEntry) { + JavaKind arrayKind = type.getBaseType().getJavaKind(); + int headerSize = getObjectLayout().getArrayBaseOffset(arrayKind); + int arrayLengthOffset = getObjectLayout().getArrayLengthOffset(); + int arrayLengthSize = getObjectLayout().sizeInBytes(JavaKind.Int); + assert arrayLengthOffset + arrayLengthSize <= headerSize; + arrayTypeEntry.addField(createSyntheticFieldEntry("len", arrayTypeEntry, (HostedType) metaAccess.lookupJavaType(JavaKind.Int.toJavaClass()), arrayLengthOffset, arrayLengthSize)); + } - @Override - public long typeSignature(String prefix) { - return super.typeSignature(prefix + loaderName()); + /** + * Process {@link StructFieldInfo fields} for {@link StructInfo foreign structs}. Fields are + * ordered by offset and added as {@link FieldEntry field entries} to the foreign type entry. + * + * @param type the given type + * @param foreignStructTypeEntry the {@code ForeignStructTypeEntry} of the type + */ + private void processForeignTypeFields(HostedType type, ForeignStructTypeEntry foreignStructTypeEntry) { + ElementInfo elementInfo = nativeLibs.findElementInfo(type); + if (elementInfo instanceof StructInfo) { + elementInfo.getChildren().stream().filter(NativeImageDebugInfoProvider::isTypedField) + .map(StructFieldInfo.class::cast) + .sorted(Comparator.comparingInt(field -> field.getOffsetInfo().getProperty())) + .forEach(field -> { + HostedType fieldType = getFieldType(field); + FieldEntry fieldEntry = createFieldEntry(null, field.getName(), foreignStructTypeEntry, fieldType, field.getOffsetInfo().getProperty(), + field.getSizeInBytes(), fieldTypeIsEmbedded(field), 0); + foreignStructTypeEntry.addField(fieldEntry); + }); } + } - @Override - public DebugTypeKind typeKind() { - return DebugTypeKind.INSTANCE; + /** + * Processes instance fields and static fields of hosted types to and adds {@link FieldEntry + * field entries} to the structured type entry. + * + * @param type the given type + * @param structureTypeEntry the {@code StructuredTypeEntry} of the type + */ + private void processFieldEntries(HostedType type, StructureTypeEntry structureTypeEntry) { + for (HostedField field : type.getInstanceFields(false)) { + structureTypeEntry.addField(createFieldEntry(field, structureTypeEntry)); } - @Override - public String loaderName() { - return UniqueShortNameProvider.singleton().uniqueShortLoaderName(hostedType.getJavaClass().getClassLoader()); + for (ResolvedJavaField field : type.getStaticFields()) { + assert field instanceof HostedField; + structureTypeEntry.addField(createFieldEntry((HostedField) field, structureTypeEntry)); } + } - @Override - public Stream fieldInfoProvider() { - Stream instanceFieldsStream = Arrays.stream(hostedType.getInstanceFields(false)).map(this::createDebugFieldInfo); - if (hostedType instanceof HostedInstanceClass && hostedType.getStaticFields().length > 0) { - Stream staticFieldsStream = Arrays.stream(hostedType.getStaticFields()).map(this::createDebugStaticFieldInfo); - return Stream.concat(instanceFieldsStream, staticFieldsStream); + /** + * Creates a new field entry for a hosted field. + * + * @param field the given field + * @param ownerType the structured type owning the hosted field + * @return a {@code FieldEntry} representing the hosted field + */ + private FieldEntry createFieldEntry(HostedField field, StructureTypeEntry ownerType) { + FileEntry fileEntry = lookupFileEntry(field); + String fieldName = field.getName(); + HostedType valueType = field.getType(); + JavaKind storageKind = field.getType().getStorageKind(); + int size = getObjectLayout().sizeInBytes(storageKind); + int modifiers = field.getModifiers(); + int offset = field.getLocation(); + /* + * For static fields we need to add in the appropriate partition base but only if we have a + * real offset + */ + if (Modifier.isStatic(modifiers) && offset >= 0) { + if (storageKind.isPrimitive()) { + offset += primitiveStartOffset; } else { - return instanceFieldsStream; + offset += referenceStartOffset; } } - @Override - public Stream methodInfoProvider() { - return Arrays.stream(hostedType.getAllDeclaredMethods()).map(this::createDebugMethodInfo); - } + return createFieldEntry(fileEntry, fieldName, ownerType, valueType, offset, size, false, modifiers); + } - @Override - public ResolvedJavaType superClass() { - HostedClass superClass = hostedType.getSuperclass(); + /** + * Creates an unprocessed type entry. The type entry contains all static information, which is + * its name, size, classOffset, loader entry and type signatures. For {@link ClassEntry} types, + * it also contains the superclass {@code ClassEntry}. + * + *

    + * The returned type entry does not hold information on its fields, methods, and interfaces + * implementors. This information is patched later in {@link #processTypeEntry}. This is done to + * avoid cycles in the type entry lookup. + * + * @param type the {@code SharedType} to process + * @return an unprocessed type entry + */ + @Override + protected TypeEntry createTypeEntry(SharedType type) { + assert type instanceof HostedType; + HostedType hostedType = (HostedType) type; + + String typeName = hostedType.toJavaName(); + int size = getTypeSize(hostedType); + long classOffset = getClassOffset(hostedType); + LoaderEntry loaderEntry = lookupLoaderEntry(hostedType); + String loaderName = loaderEntry.loaderId(); + long typeSignature = getTypeSignature(typeName + loaderName); + long compressedTypeSignature = useHeapBase ? getTypeSignature(INDIRECT_PREFIX + typeName + loaderName) : typeSignature; + + if (hostedType.isPrimitive()) { + JavaKind kind = hostedType.getStorageKind(); + if (debug.isLogEnabled()) { + debug.log("typename %s (%d bits)%n", typeName, kind == JavaKind.Void ? 0 : kind.getBitCount()); + } + return new PrimitiveTypeEntry(typeName, size, classOffset, typeSignature, kind); + } else { /* - * Unwrap the hosted type's super class to the original to provide the correct identity - * type. + * this is a structured type (array or class entry), or a foreign type entry (uses the + * layout signature even for not structured types) */ - if (superClass != null) { - return getOriginal(superClass); - } - return null; - } + long layoutTypeSignature = getTypeSignature(LAYOUT_PREFIX + typeName + loaderName); + if (hostedType.isArray()) { + TypeEntry elementTypeEntry = lookupTypeEntry(hostedType.getComponentType()); + if (debug.isLogEnabled()) { + debug.log("typename %s element type %s base size %d length offset %d%n", typeName, elementTypeEntry.getTypeName(), + getObjectLayout().getArrayBaseOffset(hostedType.getComponentType().getStorageKind()), getObjectLayout().getArrayLengthOffset()); + } + return new ArrayTypeEntry(typeName, size, classOffset, typeSignature, compressedTypeSignature, + layoutTypeSignature, elementTypeEntry, loaderEntry); + } else { + // this is a class entry or a foreign type entry + ClassEntry superClass = hostedType.getSuperclass() == null ? null : (ClassEntry) lookupTypeEntry(hostedType.getSuperclass()); - @Override - public Stream interfaces() { - // map through getOriginal so we can use the result as an id type - return Arrays.stream(hostedType.getInterfaces()).map(interfaceType -> getOriginal(interfaceType)); - } + if (debug.isLogEnabled() && superClass != null) { + debug.log("typename %s adding super %s%n", typeName, superClass.getTypeName()); + } - private NativeImageDebugFieldInfo createDebugFieldInfo(HostedField field) { - return new NativeImageDebugFieldInfo(field); - } + FileEntry fileEntry = lookupFileEntry(hostedType); + if (isForeignWordType(hostedType)) { + /* + * A foreign type is either a generic word type, struct type, or a pointer. + */ + if (debug.isLogEnabled()) { + logForeignTypeInfo(hostedType); + } + /* + * Foreign types are already produced after analysis to place them into the + * image if needed for run-time debug info generation. Fetch type entry from the + * image singleton. + */ + TypeEntry foreignTypeEntry = SubstrateDebugTypeEntrySupport.singleton().getTypeEntry(typeSignature); + + // update class offset if the class object is in the heap + foreignTypeEntry.setClassOffset(classOffset); + if (foreignTypeEntry instanceof PointerToTypeEntry pointerToTypeEntry && pointerToTypeEntry.getPointerTo() == null) { + // fix-up void pointers + pointerToTypeEntry.setPointerTo(lookupTypeEntry(voidType)); + } - private NativeImageDebugFieldInfo createDebugStaticFieldInfo(ResolvedJavaField field) { - return new NativeImageDebugFieldInfo((HostedField) field); + return foreignTypeEntry; + } else if (hostedType.isEnum()) { + // Fetch typedef name for c enum types and skip the generic 'int' typedef name. + String typedefName = ""; + ElementInfo elementInfo = nativeLibs.findElementInfo(hostedType); + if (elementInfo instanceof EnumInfo enumInfo && !enumInfo.getName().equals("int")) { + typedefName = enumInfo.getName(); + } + return new EnumClassEntry(typeName, size, classOffset, typeSignature, compressedTypeSignature, + layoutTypeSignature, superClass, fileEntry, loaderEntry, typedefName); + } else if (hostedType.isInstanceClass()) { + return new ClassEntry(typeName, size, classOffset, typeSignature, compressedTypeSignature, + layoutTypeSignature, superClass, fileEntry, loaderEntry); + } else if (hostedType.isInterface()) { + return new InterfaceClassEntry(typeName, size, classOffset, typeSignature, compressedTypeSignature, + layoutTypeSignature, superClass, fileEntry, loaderEntry); + } else { + throw new RuntimeException("Unknown type kind " + hostedType.getName()); + } + } } + } - private NativeImageDebugMethodInfo createDebugMethodInfo(HostedMethod method) { - return new NativeImageDebugMethodInfo(method); - } + /** + * Processes a word base analysis type into a type entry for use during debug info generation. + * Creates other type entries recursively if needed. + * + * @param nativeLibs The NativeLibraries used to fetch element infos. + * @param metaAccess The AnalysisMetaAccess used to lookup referenced analysis types. + * @param type The type to produce a debug info type entry for. + * @return The type entry for the given type. + */ + public static TypeEntry processElementInfo(NativeLibraries nativeLibs, AnalysisMetaAccess metaAccess, AnalysisType type) { + assert nativeLibs.isWordBase(type); - private class NativeImageDebugFieldInfo extends NativeImageDebugFileInfo implements DebugFieldInfo { - private final HostedField field; + ElementInfo elementInfo = nativeLibs.findElementInfo(type); + assert elementInfo == null || elementInfo.getAnnotatedElement() instanceof ResolvedJavaType; - NativeImageDebugFieldInfo(HostedField field) { - super(field); - this.field = field; - } + SizableInfo.ElementKind elementKind = elementInfo instanceof SizableInfo ? ((SizableInfo) elementInfo).getKind() : null; - @Override - public String name() { - return field.getName(); - } + String typeName = type.toJavaName(); + int size = elementSize(elementInfo); + // We need the loader name here to match the type signature generated later for looking up + // type entries. + String loaderName = UniqueShortNameProvider.singleton().uniqueShortLoaderName(type.getJavaClass().getClassLoader()); + long typeSignature = getTypeSignature(typeName + loaderName); - @Override - public ResolvedJavaType valueType() { - return getOriginal(field.getType()); - } + // Reuse already created type entries. + TypeEntry existingTypeEntry = SubstrateDebugTypeEntrySupport.singleton().getTypeEntry(typeSignature); + if (existingTypeEntry != null) { + return existingTypeEntry; + } - @Override - public int offset() { - int offset = field.getLocation(); + switch (elementInfo) { + case PointerToInfo pointerToInfo -> { /* - * For static fields we need to add in the appropriate partition base but only if we - * have a real offset + * This must be a pointer. If the target type is known use it to declare the pointer + * type, otherwise default to 'void *' */ - if (isStatic() && offset >= 0) { - if (isPrimitive()) { - offset += primitiveStartOffset; - } else { - offset += referenceStartOffset; + TypeEntry pointerToEntry = null; + if (elementKind == SizableInfo.ElementKind.POINTER) { + /* + * any target type for the pointer will be defined by a CPointerTo or + * RawPointerTo annotation + */ + AnalysisType pointerTo = null; + CPointerTo cPointerTo = type.getAnnotation(CPointerTo.class); + if (cPointerTo != null) { + pointerTo = metaAccess.lookupJavaType(cPointerTo.value()); + } + RawPointerTo rawPointerTo = type.getAnnotation(RawPointerTo.class); + if (rawPointerTo != null) { + pointerTo = metaAccess.lookupJavaType(rawPointerTo.value()); } + + pointerToEntry = processElementInfo(nativeLibs, metaAccess, pointerTo); + } else if (elementKind == SizableInfo.ElementKind.INTEGER || elementKind == SizableInfo.ElementKind.FLOAT) { + pointerToEntry = createNativePrimitiveType(pointerToInfo); } - return offset; - } - @Override - public int size() { - return getObjectLayout().sizeInBytes(field.getType().getStorageKind()); - } + if (pointerToEntry != null) { + // store pointer to entry in an image singleton + SubstrateDebugTypeEntrySupport.singleton().addTypeEntry(pointerToEntry); + } - @Override - public boolean isEmbedded() { - return false; + // pointer to entry may be null, e.g. if no element info was found for the pointed + // to type + // we create the type entry from the corresponding hosted type later + return new PointerToTypeEntry(typeName, size, -1, typeSignature, pointerToEntry); } - - @Override - public int modifiers() { - ResolvedJavaField targetField = field.wrapped.wrapped; - if (targetField instanceof SubstitutionField) { - targetField = ((SubstitutionField) targetField).getOriginal(); + case StructInfo structInfo -> { + long layoutTypeSignature = getTypeSignature(LAYOUT_PREFIX + typeName + loaderName); + ForeignStructTypeEntry parentEntry = null; + for (AnalysisType interfaceType : type.getInterfaces()) { + ElementInfo otherInfo = nativeLibs.findElementInfo(interfaceType); + if (otherInfo instanceof StructInfo) { + TypeEntry otherTypeEntry = processElementInfo(nativeLibs, metaAccess, interfaceType); + assert otherTypeEntry instanceof ForeignStructTypeEntry; + parentEntry = (ForeignStructTypeEntry) otherTypeEntry; + // store parent entry in an image singleton + SubstrateDebugTypeEntrySupport.singleton().addTypeEntry(parentEntry); + } } - return targetField.getModifiers(); - } - private boolean isStatic() { - return Modifier.isStatic(modifiers()); - } + String typedefName = typedefName(structInfo); + if (typedefName == null) { + // create a synthetic typedef name from the types name + typedefName = "_" + typeName; + } + if (typedefName.startsWith("struct ")) { + // remove the struct keyword from the typename to provide uniform type names for + // struct types + typedefName = typedefName.substring("struct ".length()); + } - private boolean isPrimitive() { - return field.getType().getStorageKind().isPrimitive(); + // typedefName is the name of the c struct the Java type annotates + // no field processing here, this is handled later + return new ForeignStructTypeEntry(typeName, size, -1, typeSignature, layoutTypeSignature, typedefName, parentEntry); } - } + case null, default -> { + /* + * EnumInfo should not reach here because it is no word base type. Create a pointer + * to a generic word type or void. + */ + size = ConfigurationValues.getTarget().wordSize; + TypeEntry pointerToEntry = null; + + // create a generic word type as base type or a void* if it is a pointer type + if (!nativeLibs.isPointerBase(type)) { + int genericWordSize = ConfigurationValues.getTarget().wordSize; + int genericWordBits = genericWordSize * 8; + String genericWordName = "uint" + genericWordBits + "_t"; + long genericWordTypeSignature = getTypeSignature(genericWordName); + pointerToEntry = new PrimitiveTypeEntry(genericWordName, genericWordSize, -1, genericWordTypeSignature, genericWordBits, true, false, true); + SubstrateDebugTypeEntrySupport.singleton().addTypeEntry(pointerToEntry); + } - private class NativeImageDebugMethodInfo extends NativeImageDebugHostedMethodInfo implements DebugMethodInfo { - NativeImageDebugMethodInfo(HostedMethod hostedMethod) { - super(hostedMethod); + return new PointerToTypeEntry(typeName, size, -1, typeSignature, pointerToEntry); } } } - private class NativeImageDebugInterfaceTypeInfo extends NativeImageDebugInstanceTypeInfo implements DebugInterfaceTypeInfo { - - NativeImageDebugInterfaceTypeInfo(HostedInterface interfaceClass) { - super(interfaceClass); - } + private static TypeEntry createNativePrimitiveType(PointerToInfo pointerToInfo) { + // process pointer to info into primitive types + assert pointerToInfo.getKind() == SizableInfo.ElementKind.INTEGER || pointerToInfo.getKind() == SizableInfo.ElementKind.FLOAT; + String typeName = pointerToInfo.getName(); + int classOffset = -1; + // Use the foreign prefix to make sure foreign primitives get a type signature + // different from Java primitives. + // e.g. Java char vs C char + long typeSignature = getTypeSignature(FOREIGN_PREFIX + typeName); + int size = pointerToInfo.getSizeInBytes(); + int bitCount = size * 8; + boolean isNumericInteger = pointerToInfo.getKind() == SizableInfo.ElementKind.INTEGER; + boolean isNumericFloat = pointerToInfo.getKind() == SizableInfo.ElementKind.FLOAT; + boolean isUnsigned = pointerToInfo.isUnsigned(); - @Override - public DebugTypeKind typeKind() { - return DebugTypeKind.INTERFACE; - } + return new PrimitiveTypeEntry(typeName, size, classOffset, typeSignature, bitCount, isNumericInteger, isNumericFloat, isUnsigned); } - 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"; + /* The following methods provide some logging for foreign type entries. */ + private void logForeignTypeInfo(HostedType hostedType) { + if (!isForeignPointerType(hostedType)) { + // foreign non-pointer word types never have element info + debug.log(DebugContext.VERBOSE_LEVEL, "Foreign word type %s", hostedType.toJavaName()); + } else { + ElementInfo elementInfo = nativeLibs.findElementInfo(hostedType); + logForeignPointerType(hostedType, elementInfo); } + } - private boolean verifyElementInfo() { - // word types and some pointer types do not have element info - if (elementInfo == null || !(elementInfo instanceof SizableInfo)) { - return true; + private void logForeignPointerType(HostedType hostedType, ElementInfo elementInfo) { + if (elementInfo == null) { + // can happen for a generic (void*) pointer or a class + if (hostedType.isInterface()) { + debug.log(DebugContext.VERBOSE_LEVEL, "Foreign pointer type %s", hostedType.toJavaName()); + } else { + debug.log(DebugContext.VERBOSE_LEVEL, "Foreign pointer type %s (class)", hostedType.toJavaName()); } - 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; + } 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); } } + } - @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); + private void logPointerToInfo(HostedType hostedType, PointerToInfo pointerToInfo) { + debug.log(DebugContext.VERBOSE_LEVEL, "Foreign pointer type %s %s", hostedType.toJavaName(), elementKind(pointerToInfo)); + assert hostedType.isInterface(); + int size = elementSize(pointerToInfo); + boolean isUnsigned = pointerToInfo.isUnsigned(); + String typedefName = pointerToInfo.getTypedefName(); + debug.log("element size = %d", size); + debug.log("%s", (isUnsigned ? "" : "")); + if (typedefName != null) { + debug.log("typedefname = %s", typedefName); } - - @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 elementKind((SizableInfo) elementInfo) == ElementKind.POINTER; - } else { - return false; - } - } - - @Override - public boolean isIntegral() { - if (elementInfo != null && elementInfo instanceof SizableInfo) { - return elementKind((SizableInfo) elementInfo) == ElementKind.INTEGER; - } else { - return false; - } - } - - @Override - public boolean isFloat() { - if (elementInfo != null) { - return elementKind((SizableInfo) elementInfo) == 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.getSizeInBytes(); - } - - @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; - - NativeImageDebugArrayTypeInfo(HostedArrayClass arrayClass) { - super(arrayClass); - this.arrayClass = arrayClass; - this.fieldInfos = new LinkedList<>(); - JavaKind arrayKind = arrayClass.getBaseType().getJavaKind(); - int headerSize = getObjectLayout().getArrayBaseOffset(arrayKind); - int arrayLengthOffset = getObjectLayout().getArrayLengthOffset(); - int arrayLengthSize = getObjectLayout().sizeInBytes(JavaKind.Int); - assert arrayLengthOffset + arrayLengthSize <= headerSize; - - addField("len", javaKindToHostedType.get(JavaKind.Int), arrayLengthOffset, arrayLengthSize); - } - - void addField(String name, ResolvedJavaType valueType, int offset, @SuppressWarnings("hiding") int size) { - NativeImageDebugHeaderFieldInfo fieldinfo = new NativeImageDebugHeaderFieldInfo(name, valueType, offset, size); - fieldInfos.add(fieldinfo); - } - - @Override - public long typeSignature(String prefix) { - HostedType elementType = hostedType.getComponentType(); - while (elementType.isArray()) { - elementType = elementType.getComponentType(); - } - String loaderId = ""; - if (elementType.isInstanceClass() || elementType.isInterface() || elementType.isEnum()) { - loaderId = UniqueShortNameProvider.singleton().uniqueShortLoaderName(elementType.getJavaClass().getClassLoader()); - } - return super.typeSignature(prefix + loaderId); - } - - @Override - public DebugTypeKind typeKind() { - return DebugTypeKind.ARRAY; - } - - @Override - public int baseSize() { - return getObjectLayout().getArrayBaseOffset(arrayClass.getComponentType().getStorageKind()); - } - - @Override - public int lengthOffset() { - return getObjectLayout().getArrayLengthOffset(); - } - - @Override - public ResolvedJavaType elementType() { - HostedType elementType = arrayClass.getComponentType(); - return getOriginal(elementType); - } - - @Override - public Stream fieldInfoProvider() { - return fieldInfos.stream(); - } - } - - private class NativeImageDebugPrimitiveTypeInfo extends NativeImageDebugTypeInfo implements DebugPrimitiveTypeInfo { - private final HostedPrimitiveType primitiveType; - - NativeImageDebugPrimitiveTypeInfo(HostedPrimitiveType primitiveType) { - super(primitiveType); - this.primitiveType = primitiveType; - } - - @Override - public long typeSignature(String prefix) { - /* - * primitive types never need an indirection so use the same signature for places where - * we might want a special type - */ - return super.typeSignature(""); - } - - @Override - public DebugTypeKind typeKind() { - return DebugTypeKind.PRIMITIVE; - } - - @Override - public int bitCount() { - JavaKind javaKind = primitiveType.getStorageKind(); - return (javaKind == JavaKind.Void ? 0 : javaKind.getBitCount()); - } - - @Override - public char typeChar() { - return primitiveType.getStorageKind().getTypeChar(); - } - - @Override - public int flags() { - char typeChar = primitiveType.getStorageKind().getTypeChar(); - switch (typeChar) { - case 'B': - case 'S': - case 'I': - case 'J': { - return FLAG_NUMERIC | FLAG_INTEGRAL | FLAG_SIGNED; - } - case 'C': { - return FLAG_NUMERIC | FLAG_INTEGRAL; - } - case 'F': - case 'D': { - return FLAG_NUMERIC; - } - default: { - assert typeChar == 'V' || typeChar == 'Z'; - return 0; - } - } - } - } - - @SuppressWarnings("try") - private NativeImageDebugTypeInfo createDebugTypeInfo(HostedType hostedType) { - try (DebugContext.Scope s = debugContext.scope("DebugTypeInfo", hostedType.toJavaName())) { - if (isForeignWordType(hostedType)) { - assert hostedType.isInterface() || hostedType.isInstanceClass() : "foreign type must be instance class or interface!"; - logForeignTypeInfo(hostedType); - return new NativeImageDebugForeignTypeInfo(hostedType); - } else if (hostedType.isEnum()) { - return new NativeImageDebugEnumTypeInfo((HostedInstanceClass) hostedType); - } else if (hostedType.isInstanceClass()) { - return new NativeImageDebugInstanceTypeInfo(hostedType); - } else if (hostedType.isInterface()) { - return new NativeImageDebugInterfaceTypeInfo((HostedInterface) hostedType); - } else if (hostedType.isArray()) { - return new NativeImageDebugArrayTypeInfo((HostedArrayClass) hostedType); - } else if (hostedType.isPrimitive()) { - return new NativeImageDebugPrimitiveTypeInfo((HostedPrimitiveType) hostedType); - } else { - throw new RuntimeException("Unknown type kind " + hostedType.getName()); - } - } catch (Throwable e) { - throw debugContext.handle(e); - } - } - - private void logForeignTypeInfo(HostedType hostedType) { - if (!isForeignPointerType(hostedType)) { - // foreign non-pointer word types never have element info - debugContext.log(DebugContext.VERBOSE_LEVEL, "Foreign word type %s", hostedType.toJavaName()); - } else { - ElementInfo elementInfo = nativeLibs.findElementInfo(hostedType); - logForeignPointerType(hostedType, elementInfo); - } - } - - private void logForeignPointerType(HostedType hostedType, ElementInfo elementInfo) { - if (elementInfo == null) { - // can happen for a generic (void*) pointer or a class - if (hostedType.isInterface()) { - debugContext.log(DebugContext.VERBOSE_LEVEL, "Foreign pointer type %s", hostedType.toJavaName()); - } else { - debugContext.log(DebugContext.VERBOSE_LEVEL, "Foreign pointer type %s (class)", hostedType.toJavaName()); - } - } else if (elementInfo instanceof PointerToInfo) { - logPointerToInfo(hostedType, (PointerToInfo) elementInfo); - } else if (elementInfo instanceof StructInfo) { - if (elementInfo instanceof RawStructureInfo) { - logRawStructureInfo(hostedType, (RawStructureInfo) elementInfo); - } else { - logStructInfo(hostedType, (StructInfo) elementInfo); - } - } - } - - private void logPointerToInfo(HostedType hostedType, PointerToInfo pointerToInfo) { - debugContext.log(DebugContext.VERBOSE_LEVEL, "Foreign pointer type %s %s", hostedType.toJavaName(), elementKind(pointerToInfo)); - assert hostedType.isInterface(); - int size = elementSize(pointerToInfo); - boolean isUnsigned = pointerToInfo.isUnsigned(); - String typedefName = pointerToInfo.getTypedefName(); - debugContext.log("element size = %d", size); - debugContext.log("%s", (isUnsigned ? "" : "")); - if (typedefName != null) { - debugContext.log("typedefname = %s", typedefName); - } - dumpElementInfo(pointerToInfo); - } + dumpElementInfo(pointerToInfo); + } private void logStructInfo(HostedType hostedType, StructInfo structInfo) { - debugContext.log(DebugContext.VERBOSE_LEVEL, "Foreign struct type %s %s", hostedType.toJavaName(), elementKind(structInfo)); + debug.log(DebugContext.VERBOSE_LEVEL, "Foreign struct type %s %s", hostedType.toJavaName(), elementKind(structInfo)); assert hostedType.isInterface(); boolean isIncomplete = structInfo.isIncomplete(); if (isIncomplete) { - debugContext.log(""); + debug.log(""); } else { - debugContext.log("complete : element size = %d", elementSize(structInfo)); + debug.log("complete : element size = %d", elementSize(structInfo)); } String typedefName = structInfo.getTypedefName(); if (typedefName != null) { - debugContext.log(" typedefName = %s", typedefName); + debug.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)); + debug.log(DebugContext.VERBOSE_LEVEL, "Foreign raw struct type %s %s", hostedType.toJavaName(), elementKind(rawStructureInfo)); assert hostedType.isInterface(); - debugContext.log("element size = %d", elementSize(rawStructureInfo)); + debug.log("element size = %d", elementSize(rawStructureInfo)); String typedefName = rawStructureInfo.getTypedefName(); if (typedefName != null) { - debugContext.log(" typedefName = %s", typedefName); + debug.log(" typedefName = %s", typedefName); } dumpElementInfo(rawStructureInfo); } - private int structFieldComparator(StructFieldInfo f1, StructFieldInfo f2) { - int offset1 = f1.getOffsetInfo().getProperty(); - int offset2 = f2.getOffsetInfo().getProperty(); - return offset1 - offset2; - } - - private Stream orderedFieldsStream(ElementInfo elementInfo) { - if (elementInfo instanceof RawStructureInfo || elementInfo instanceof StructInfo) { - return elementInfo.getChildren().stream().filter(elt -> isTypedField(elt)) - .map(elt -> ((StructFieldInfo) elt)) - .sorted(this::structFieldComparator); - } else { - return Stream.empty(); - } - } - private void dumpElementInfo(ElementInfo elementInfo) { if (elementInfo != null) { - debugContext.log("Element Info {%n%s}", formatElementInfo(elementInfo)); + debug.log("Element Info {%n%s}", formatElementInfo(elementInfo)); } else { - debugContext.log("Element Info {}"); + debug.log("Element Info {}"); } } @@ -1095,1575 +977,108 @@ private static void formatPropertyInfo(PropertyInfo propertyInfo, StringB } private static void indentElementInfo(StringBuilder stringBuilder, int indent) { - for (int i = 0; i <= indent; i++) { - stringBuilder.append(" "); - } + stringBuilder.append(" ".repeat(Math.max(0, indent + 1))); } - protected abstract class NativeImageDebugBaseMethodInfo extends NativeImageDebugFileInfo implements DebugMethodInfo { - protected final ResolvedJavaMethod method; - protected int line; - protected final List paramInfo; - protected final DebugLocalInfo thisParamInfo; - - NativeImageDebugBaseMethodInfo(ResolvedJavaMethod m) { - super(m); - // We occasionally see an AnalysisMethod as input to this constructor. - // That can happen if the points to analysis builds one into a node - // source position when building the initial graph. The global - // replacement that is supposed to ensure the compiler sees HostedXXX - // types rather than AnalysisXXX types appears to skip translating - // method references in node source positions. So, we do the translation - // here just to make sure we use a HostedMethod wherever possible. - method = promoteAnalysisToHosted(m); - LineNumberTable lineNumberTable = method.getLineNumberTable(); - line = (lineNumberTable != null ? lineNumberTable.getLineNumber(0) : 0); - this.paramInfo = createParamInfo(method, line); - // We use the target modifiers to decide where to install any first param - // even though we may have added it according to whether method is static. - // That's because in a few special cases method is static but the original - // DebugFrameLocals - // from which it is derived is an instance method. This appears to happen - // when a C function pointer masquerades as a method. Whatever parameters - // we pass through need to match the definition of the original. - if (Modifier.isStatic(modifiers())) { - this.thisParamInfo = null; - } else { - this.thisParamInfo = paramInfo.remove(0); + /** + * Lookup the file entry of a {@code ResolvedJavaType}. + * + *

    + * First tries to find the source file using the {@link SourceManager}. If no file was found, a + * file name is generated from the full class name. + * + * @param type the given {@code ResolvedJavaType} + * @return the {@code FileEntry} for the type + */ + @Override + @SuppressWarnings("try") + public FileEntry lookupFileEntry(ResolvedJavaType type) { + Class clazz = OriginalClassProvider.getJavaClass(type); + SourceManager sourceManager = ImageSingletons.lookup(SourceManager.class); + try (DebugContext.Scope s = debug.scope("DebugFileInfo", type)) { + Path filePath = sourceManager.findAndCacheSource(type, clazz, debug); + if (filePath != null) { + return lookupFileEntry(filePath); } + } catch (Throwable e) { + throw debug.handle(e); } - private ResolvedJavaMethod promoteAnalysisToHosted(ResolvedJavaMethod m) { - if (m instanceof AnalysisMethod) { - return heap.hUniverse.lookup(m); - } - if (!(m instanceof HostedMethod)) { - debugContext.log(DebugContext.DETAILED_LEVEL, "Method is neither Hosted nor Analysis : %s.%s%s", m.getDeclaringClass().getName(), m.getName(), - m.getSignature().toMethodDescriptor()); - } - return m; - } + // fallback to default file entry lookup + return super.lookupFileEntry(type); + } - private ResolvedJavaMethod originalMethod() { - // unwrap to an original method as far as we can - ResolvedJavaMethod targetMethod = method; - while (targetMethod instanceof WrappedJavaMethod) { - targetMethod = ((WrappedJavaMethod) targetMethod).getWrapped(); - } - // if we hit a substitution then we can translate to the original - // for identity otherwise we use whatever we unwrapped to. - if (targetMethod instanceof SubstitutionMethod) { - targetMethod = ((SubstitutionMethod) targetMethod).getOriginal(); - } - return targetMethod; + /** + * Lookup a {@code FileEntry} for a {@code HostedMethod}. For a {@link SubstitutionMethod}, this + * uses the source file of the annotated method. + * + * @param method the given {@code ResolvedJavaMethod} + * @return the {@code FilEntry} for the method + */ + @Override + public FileEntry lookupFileEntry(ResolvedJavaMethod method) { + ResolvedJavaMethod targetMethod = method; + if (targetMethod instanceof HostedMethod hostedMethod && hostedMethod.getWrapped().getWrapped() instanceof SubstitutionMethod substitutionMethod) { + // we always want to look up the file of the annotated method + targetMethod = substitutionMethod.getAnnotated(); } + return super.lookupFileEntry(targetMethod); + } - /** - * Return the unique type that owns this method. - *

    - * In the absence of substitutions the returned type result is simply the original JVMCI - * implementation type that declares the associated Java method. Identifying this type may - * involve unwrapping from Hosted universe to Analysis universe to the original JVMCI - * universe. - *

    - * - * In the case where the method itself is either an (annotated) substitution or declared by - * a class that is a (annotated) substitution then the link from substitution to original is - * also 'unwrapped' to arrive at the original type. In cases where a substituted method has - * no original the class of the substitution is used, for want of anything better. - *

    - * - * This unwrapping strategy avoids two possible ambiguities that would compromise use of the - * returned value as a unique 'owner'. Firstly, if the same method is ever presented via - * both its HostedMethod incarnation and its original ResolvedJavaMethod incarnation then - * ownership will always be signalled via the original type. This ensures that views of the - * method presented via the list of included HostedTypes and via the list of CompiledMethod - * objects and their embedded substructure (such as inline caller hierarchies) are correctly - * identified. - *

    - * - * Secondly, when a substituted method or method of a substituted class is presented it ends - * up being collated with methods of the original class rather than the class which actually - * defines it, avoiding an artificial split of methods that belong to the same underlying - * runtime type into two distinct types in the debuginfo model. As an example, this ensures - * that all methods of substitution class DynamicHub are collated with methods of class - * java.lang.Class, the latter being the type whose name gets written into the debug info - * and gets used to name the method in the debugger. Note that this still allows the - * method's line info to be associated with the correct file i.e. it does not compromise - * breaking and stepping through the code. - * - * @return the unique type that owns this method - */ - @Override - public ResolvedJavaType ownerType() { - ResolvedJavaType result; - if (method instanceof HostedMethod) { - result = getDeclaringClass((HostedMethod) method, true); - } else { - result = method.getDeclaringClass(); - } - while (result instanceof WrappedJavaType) { - result = ((WrappedJavaType) result).getWrapped(); + private static int getTypeSize(HostedType type) { + switch (type) { + case HostedInstanceClass hostedInstanceClass -> { + /* We know the actual instance size in bytes. */ + return hostedInstanceClass.getInstanceSize(); } - return result; - } - - @Override - public ResolvedJavaMethod idMethod() { - // translating to the original ensures we equate a - // substituted method with the original in case we ever see both - return originalMethod(); - } - - @Override - public String name() { - ResolvedJavaMethod targetMethod = originalMethod(); - String name = targetMethod.getName(); - if (name.equals("")) { - if (method instanceof HostedMethod) { - name = getDeclaringClass((HostedMethod) method, true).toJavaName(); - if (name.indexOf('.') >= 0) { - name = name.substring(name.lastIndexOf('.') + 1); - } - if (name.indexOf('$') >= 0) { - name = name.substring(name.lastIndexOf('$') + 1); - } - } else { - name = targetMethod.format("%h"); - if (name.indexOf('$') >= 0) { - name = name.substring(name.lastIndexOf('$') + 1); - } - } + case HostedArrayClass hostedArrayClass -> { + /* Use the size of header common to all arrays of this type. */ + return getObjectLayout().getArrayBaseOffset(hostedArrayClass.getComponentType().getStorageKind()); } - return name; - } - - @Override - public ResolvedJavaType valueType() { - ResolvedJavaType resultType = (ResolvedJavaType) method.getSignature().getReturnType(null); - if (resultType instanceof HostedType) { - return getOriginal((HostedType) resultType); + case HostedInterface hostedInterface -> { + /* Use the size of the header common to all implementors. */ + return getObjectLayout().getFirstFieldOffset(); } - return resultType; - } - - @Override - public DebugLocalInfo[] getParamInfo() { - return paramInfo.toArray(new DebugLocalInfo[paramInfo.size()]); - } - - @Override - public DebugLocalInfo getThisParamInfo() { - return thisParamInfo; - } - - @Override - public String symbolNameForMethod() { - return NativeImage.localSymbolNameForMethod(method); - } - - @Override - public boolean isDeoptTarget() { - if (method instanceof HostedMethod) { - return ((HostedMethod) method).isDeoptTarget(); + case HostedPrimitiveType hostedPrimitiveType -> { + /* Use the number of bytes needed to store the value. */ + JavaKind javaKind = hostedPrimitiveType.getStorageKind(); + return javaKind == JavaKind.Void ? 0 : javaKind.getByteCount(); } - return name().endsWith(StableMethodNameFormatter.MULTI_METHOD_KEY_SEPARATOR); - } - - @Override - public int modifiers() { - if (method instanceof HostedMethod) { - return getOriginalModifiers((HostedMethod) method); + default -> { + return 0; } - return method.getModifiers(); - } - - @Override - public boolean isConstructor() { - return method.isConstructor(); } - - @Override - public boolean isVirtual() { - return method instanceof HostedMethod && ((HostedMethod) method).hasVTableIndex(); - } - - @Override - public boolean isOverride() { - return method instanceof HostedMethod && allOverrides.contains(method); - } - - @Override - public int vtableOffset() { - /* TODO - convert index to offset (+ sizeof DynamicHub) */ - return isVirtual() ? ((HostedMethod) method).getVTableIndex() : -1; - } - } - - private List createParamInfo(ResolvedJavaMethod method, int line) { - Signature signature = method.getSignature(); - int parameterCount = signature.getParameterCount(false); - List paramInfos = new ArrayList<>(parameterCount); - LocalVariableTable table = method.getLocalVariableTable(); - int slot = 0; - ResolvedJavaType ownerType = method.getDeclaringClass(); - if (!method.isStatic()) { - JavaKind kind = ownerType.getJavaKind(); - JavaKind storageKind = isForeignWordType(ownerType, ownerType) ? JavaKind.Long : kind; - assert kind == JavaKind.Object : "must be an object"; - paramInfos.add(new NativeImageDebugLocalInfo("this", storageKind, ownerType, slot, line)); - slot += kind.getSlotCount(); - } - for (int i = 0; i < parameterCount; i++) { - Local local = (table == null ? null : table.getLocal(slot, 0)); - String name = (local != null ? local.getName() : "__" + i); - ResolvedJavaType paramType = (ResolvedJavaType) signature.getParameterType(i, null); - JavaKind kind = paramType.getJavaKind(); - JavaKind storageKind = isForeignWordType(paramType, ownerType) ? JavaKind.Long : kind; - paramInfos.add(new NativeImageDebugLocalInfo(name, storageKind, paramType, slot, line)); - slot += kind.getSlotCount(); - } - return paramInfos; - } - - private static boolean isIntegralKindPromotion(JavaKind promoted, JavaKind original) { - return (promoted == JavaKind.Int && - (original == JavaKind.Boolean || original == JavaKind.Byte || original == JavaKind.Short || original == JavaKind.Char)); } - protected abstract class NativeImageDebugHostedMethodInfo extends NativeImageDebugBaseMethodInfo { - protected final HostedMethod hostedMethod; - - NativeImageDebugHostedMethodInfo(HostedMethod method) { - super(method); - this.hostedMethod = method; - } - - @Override - public int line() { - return line; - } - - @Override - public boolean isVirtual() { - return hostedMethod.hasVTableIndex(); - } - - @Override - public int vtableOffset() { - /* - * TODO - provide correct offset, not index. In Graal, the vtable is appended after the - * dynamicHub object, so can't just multiply by sizeof(pointer). - */ - return hostedMethod.hasVTableIndex() ? hostedMethod.getVTableIndex() : -1; - } - - /** - * Returns true if this is an override virtual method. Used in Windows CodeView output. - * - * @return true if this is a virtual method and overrides an existing method. + private long getClassOffset(HostedType type) { + /* + * Only query the heap for reachable types. These are guaranteed to have been seen by the + * analysis and to exist in the shadow heap. */ - @Override - public boolean isOverride() { - return allOverrides.contains(hostedMethod); + if (type.getWrapped().isReachable()) { + NativeImageHeap.ObjectInfo objectInfo = heap.getObjectInfo(type.getHub()); + if (objectInfo != null) { + return objectInfo.getOffset(); + } } + return -1; } /** - * Implementation of the DebugCodeInfo API interface that allows code info to be passed to an - * ObjectFile when generation of debug info is enabled. + * Return the offset into the initial heap at which the object identified by constant is located + * or -1 if the object is not present in the initial heap. + * + * @param constant must have JavaKind Object and must be non-null. + * @return the offset into the initial heap at which the object identified by constant is + * located or -1 if the object is not present in the initial heap. */ - private class NativeImageDebugCodeInfo extends NativeImageDebugHostedMethodInfo implements DebugCodeInfo { - private final CompilationResult compilation; - - NativeImageDebugCodeInfo(HostedMethod method, CompilationResult compilation) { - super(method); - this.compilation = compilation; - } - - @SuppressWarnings("try") - @Override - public void debugContext(Consumer action) { - try (DebugContext.Scope s = debugContext.scope("DebugCodeInfo", hostedMethod)) { - action.accept(debugContext); - } catch (Throwable e) { - throw debugContext.handle(e); - } - } - - @Override - public int addressLo() { - return hostedMethod.getCodeAddressOffset(); - } - - @Override - public int addressHi() { - return hostedMethod.getCodeAddressOffset() + compilation.getTargetCodeSize(); - } - - @Override - public Stream locationInfoProvider() { - // can we still provide locals if we have no file name? - if (fileName().length() == 0) { - return Stream.empty(); - } - boolean omitInline = SubstrateOptions.OmitInlinedMethodDebugLineInfo.getValue(); - int maxDepth = SubstrateOptions.DebugCodeInfoMaxDepth.getValue(); - boolean useSourceMappings = SubstrateOptions.DebugCodeInfoUseSourceMappings.getValue(); - if (omitInline) { - if (!SubstrateOptions.DebugCodeInfoMaxDepth.hasBeenSet()) { - /* TopLevelVisitor will not go deeper than level 2 */ - maxDepth = 2; - } - if (!SubstrateOptions.DebugCodeInfoUseSourceMappings.hasBeenSet()) { - /* Skip expensive CompilationResultFrameTree building with SourceMappings */ - useSourceMappings = false; - } - } - final CallNode root = new Builder(debugContext, compilation.getTargetCodeSize(), maxDepth, useSourceMappings, true).build(compilation); - if (root == null) { - return Stream.empty(); - } - final List locationInfos = new ArrayList<>(); - int frameSize = getFrameSize(); - final Visitor visitor = (omitInline ? new TopLevelVisitor(locationInfos, frameSize) : new MultiLevelVisitor(locationInfos, frameSize)); - // arguments passed by visitor to apply are - // NativeImageDebugLocationInfo caller location info - // CallNode nodeToEmbed parent call node to convert to entry code leaf - // NativeImageDebugLocationInfo leaf into which current leaf may be merged - root.visitChildren(visitor, (Object) null, (Object) null, (Object) null); - // try to add a location record for offset zero - updateInitialLocation(locationInfos); - return locationInfos.stream(); - } - - private int findMarkOffset(SubstrateMarkId markId) { - for (CompilationResult.CodeMark mark : compilation.getMarks()) { - if (mark.id.equals(markId)) { - return mark.pcOffset; - } - } - return -1; - } - - private void updateInitialLocation(List locationInfos) { - int prologueEnd = findMarkOffset(SubstrateMarkId.PROLOGUE_END); - if (prologueEnd < 0) { - // this is not a normal compiled method so give up - return; - } - int stackDecrement = findMarkOffset(SubstrateMarkId.PROLOGUE_DECD_RSP); - if (stackDecrement < 0) { - // this is not a normal compiled method so give up - return; - } - // if there are any location info records then the first one will be for - // a nop which follows the stack decrement, stack range check and pushes - // of arguments into the stack frame. - // - // We can construct synthetic location info covering the first instruction - // based on the method arguments and the calling convention and that will - // normally be valid right up to the nop. In exceptional cases a call - // might pass arguments on the stack, in which case the stack decrement will - // invalidate the original stack locations. Providing location info for that - // case requires adding two locations, one for initial instruction that does - // the stack decrement and another for the range up to the nop. They will - // be essentially the same but the stack locations will be adjusted to account - // for the different value of the stack pointer. - - if (locationInfos.isEmpty()) { - // this is not a normal compiled method so give up - return; - } - NativeImageDebugLocationInfo firstLocation = (NativeImageDebugLocationInfo) locationInfos.get(0); - int firstLocationOffset = firstLocation.addressLo(); - - if (firstLocationOffset == 0) { - // this is not a normal compiled method so give up - return; - } - if (firstLocationOffset < prologueEnd) { - // this is not a normal compiled method so give up - return; - } - // create a synthetic location record including details of passed arguments - ParamLocationProducer locProducer = new ParamLocationProducer(hostedMethod); - debugContext.log(DebugContext.DETAILED_LEVEL, "Add synthetic Location Info : %s (0, %d)", method.getName(), firstLocationOffset - 1); - NativeImageDebugLocationInfo locationInfo = new NativeImageDebugLocationInfo(method, firstLocationOffset, locProducer); - // if the prologue extends beyond the stack extend and uses the stack then the info - // needs - // splitting at the extend point with the stack offsets adjusted in the new info - if (locProducer.usesStack() && firstLocationOffset > stackDecrement) { - NativeImageDebugLocationInfo splitLocationInfo = locationInfo.split(stackDecrement, getFrameSize()); - debugContext.log(DebugContext.DETAILED_LEVEL, "Split synthetic Location Info : %s (%d, %d) (%d, %d)", locationInfo.name(), 0, - locationInfo.addressLo() - 1, locationInfo.addressLo(), locationInfo.addressHi() - 1); - locationInfos.add(0, splitLocationInfo); - } - locationInfos.add(0, locationInfo); + @Override + public long objectOffset(JavaConstant constant) { + assert constant.getJavaKind() == JavaKind.Object && !constant.isNull() : "invalid constant for object offset lookup"; + NativeImageHeap.ObjectInfo objectInfo = heap.getConstantInfo(constant); + if (objectInfo != null) { + return objectInfo.getOffset(); } - - // indices for arguments passed to SingleLevelVisitor::apply - - protected static final int CALLER_INFO = 0; - protected static final int PARENT_NODE_TO_EMBED = 1; - protected static final int LAST_LEAF_INFO = 2; - - private abstract class SingleLevelVisitor implements Visitor { - - protected final List locationInfos; - protected final int frameSize; - - SingleLevelVisitor(List locationInfos, int frameSize) { - this.locationInfos = locationInfos; - this.frameSize = frameSize; - } - - public NativeImageDebugLocationInfo process(FrameNode node, NativeImageDebugLocationInfo callerInfo) { - NativeImageDebugLocationInfo locationInfo; - if (node instanceof CallNode) { - // this node represents an inline call range so - // add a locationinfo to cover the range of the call - locationInfo = createCallLocationInfo((CallNode) node, callerInfo, frameSize); - } else if (isBadLeaf(node, callerInfo)) { - locationInfo = createBadLeafLocationInfo(node, callerInfo, frameSize); - } else { - // this is leaf method code so add details of its range - locationInfo = createLeafLocationInfo(node, callerInfo, frameSize); - } - return locationInfo; - } - } - - private class TopLevelVisitor extends SingleLevelVisitor { - TopLevelVisitor(List locationInfos, int frameSize) { - super(locationInfos, frameSize); - } - - @Override - public void apply(FrameNode node, Object... args) { - if (skipNode(node)) { - // this is a bogus wrapper so skip it and transform the wrapped node instead - node.visitChildren(this, args); - } else { - NativeImageDebugLocationInfo locationInfo = process(node, null); - if (node instanceof CallNode) { - locationInfos.add(locationInfo); - // erase last leaf (if present) since there is an intervening call range - invalidateMerge(args); - } else { - locationInfo = tryMerge(locationInfo, args); - if (locationInfo != null) { - locationInfos.add(locationInfo); - } - } - } - } - } - - public class MultiLevelVisitor extends SingleLevelVisitor { - MultiLevelVisitor(List locationInfos, int frameSize) { - super(locationInfos, frameSize); - } - - @Override - public void apply(FrameNode node, Object... args) { - if (skipNode(node)) { - // this is a bogus wrapper so skip it and transform the wrapped node instead - node.visitChildren(this, args); - } else { - NativeImageDebugLocationInfo callerInfo = (NativeImageDebugLocationInfo) args[CALLER_INFO]; - CallNode nodeToEmbed = (CallNode) args[PARENT_NODE_TO_EMBED]; - if (nodeToEmbed != null) { - if (embedWithChildren(nodeToEmbed, node)) { - // embed a leaf range for the method start that was included in the - // parent CallNode - // its end range is determined by the start of the first node at this - // level - NativeImageDebugLocationInfo embeddedLocationInfo = createEmbeddedParentLocationInfo(nodeToEmbed, node, callerInfo, frameSize); - locationInfos.add(embeddedLocationInfo); - // since this is a leaf node we can merge later leafs into it - initMerge(embeddedLocationInfo, args); - } - // reset args so we only embed the parent node before the first node at - // this level - args[PARENT_NODE_TO_EMBED] = nodeToEmbed = null; - } - NativeImageDebugLocationInfo locationInfo = process(node, callerInfo); - if (node instanceof CallNode) { - CallNode callNode = (CallNode) node; - locationInfos.add(locationInfo); - // erase last leaf (if present) since there is an intervening call range - invalidateMerge(args); - if (hasChildren(callNode)) { - // a call node may include an initial leaf range for the call that must - // be - // embedded under the newly created location info so pass it as an - // argument - callNode.visitChildren(this, locationInfo, callNode, (Object) null); - } else { - // we need to embed a leaf node for the whole call range - locationInfo = createEmbeddedParentLocationInfo(callNode, null, locationInfo, frameSize); - locationInfos.add(locationInfo); - } - } else { - locationInfo = tryMerge(locationInfo, args); - if (locationInfo != null) { - locationInfos.add(locationInfo); - } - } - } - } - } - - /** - * Report whether a call node has any children. - * - * @param callNode the node to check - * @return true if it has any children otherwise false. - */ - private boolean hasChildren(CallNode callNode) { - Object[] result = new Object[]{false}; - callNode.visitChildren(new Visitor() { - @Override - public void apply(FrameNode node, Object... args) { - args[0] = true; - } - }, result); - return (boolean) result[0]; - } - - /** - * Create a location info record for a leaf subrange. - * - * @param node is a simple FrameNode - * @return the newly created location info record - */ - private NativeImageDebugLocationInfo createLeafLocationInfo(FrameNode node, NativeImageDebugLocationInfo callerInfo, int framesize) { - assert !(node instanceof CallNode); - NativeImageDebugLocationInfo locationInfo = new NativeImageDebugLocationInfo(node, callerInfo, framesize); - debugContext.log(DebugContext.DETAILED_LEVEL, "Create leaf Location Info : %s depth %d (%d, %d)", locationInfo.name(), locationInfo.depth(), locationInfo.addressLo(), - locationInfo.addressHi() - 1); - return locationInfo; - } - - /** - * Create a location info record for a subrange that encloses an inline call. - * - * @param callNode is the top level inlined call frame - * @return the newly created location info record - */ - private NativeImageDebugLocationInfo createCallLocationInfo(CallNode callNode, NativeImageDebugLocationInfo callerInfo, int framesize) { - BytecodePosition callerPos = realCaller(callNode); - NativeImageDebugLocationInfo locationInfo = new NativeImageDebugLocationInfo(callerPos, callNode.getStartPos(), callNode.getEndPos() + 1, callerInfo, framesize); - debugContext.log(DebugContext.DETAILED_LEVEL, "Create call Location Info : %s depth %d (%d, %d)", locationInfo.name(), locationInfo.depth(), locationInfo.addressLo(), - locationInfo.addressHi() - 1); - return locationInfo; - } - - /** - * Create a location info record for the initial range associated with a parent call node - * whose position and start are defined by that call node and whose end is determined by the - * first child of the call node. - * - * @param parentToEmbed a parent call node which has already been processed to create the - * caller location info - * @param firstChild the first child of the call node - * @param callerLocation the location info created to represent the range for the call - * @return a location info to be embedded as the first child range of the caller location. - */ - private NativeImageDebugLocationInfo createEmbeddedParentLocationInfo(CallNode parentToEmbed, FrameNode firstChild, NativeImageDebugLocationInfo callerLocation, int framesize) { - BytecodePosition pos = parentToEmbed.frame; - int startPos = parentToEmbed.getStartPos(); - int endPos = (firstChild != null ? firstChild.getStartPos() : parentToEmbed.getEndPos() + 1); - NativeImageDebugLocationInfo locationInfo = new NativeImageDebugLocationInfo(pos, startPos, endPos, callerLocation, framesize); - debugContext.log(DebugContext.DETAILED_LEVEL, "Embed leaf Location Info : %s depth %d (%d, %d)", locationInfo.name(), locationInfo.depth(), locationInfo.addressLo(), - locationInfo.addressHi() - 1); - return locationInfo; - } - - private NativeImageDebugLocationInfo createBadLeafLocationInfo(FrameNode node, NativeImageDebugLocationInfo callerLocation, int framesize) { - assert !(node instanceof CallNode) : "bad leaf location cannot be a call node!"; - assert callerLocation == null : "should only see bad leaf at top level!"; - BytecodePosition pos = node.frame; - BytecodePosition callerPos = pos.getCaller(); - assert callerPos != null : "bad leaf must have a caller"; - assert callerPos.getCaller() == null : "bad leaf caller must be root method"; - int startPos = node.getStartPos(); - int endPos = node.getEndPos() + 1; - NativeImageDebugLocationInfo locationInfo = new NativeImageDebugLocationInfo(callerPos, startPos, endPos, null, framesize); - debugContext.log(DebugContext.DETAILED_LEVEL, "Embed leaf Location Info : %s depth %d (%d, %d)", locationInfo.name(), locationInfo.depth(), locationInfo.addressLo(), - locationInfo.addressHi() - 1); - return locationInfo; - } - - private boolean isBadLeaf(FrameNode node, NativeImageDebugLocationInfo callerLocation) { - // Sometimes we see a leaf node marked as belonging to an inlined method - // that sits directly under the root method rather than under a call node. - // It needs replacing with a location info for the root method that covers - // the relevant code range. - if (callerLocation == null) { - BytecodePosition pos = node.frame; - BytecodePosition callerPos = pos.getCaller(); - if (callerPos != null && !callerPos.getMethod().equals(pos.getMethod())) { - if (callerPos.getCaller() == null) { - return true; - } - } - } - return false; - } - - /** - * Test whether a bytecode position represents a bogus frame added by the compiler when a - * substitution or snippet call is injected. - * - * @param pos the position to be tested - * @return true if the frame is bogus otherwise false - */ - private boolean skipPos(BytecodePosition pos) { - return (pos.getBCI() == -1 && pos instanceof NodeSourcePosition && ((NodeSourcePosition) pos).isSubstitution()); - } - - /** - * Skip caller nodes with bogus positions, as determined by - * {@link #skipPos(BytecodePosition)}, returning first caller node position that is not - * bogus. - * - * @param node the node whose callers are to be traversed - * @return the first non-bogus position in the caller chain. - */ - private BytecodePosition realCaller(CallNode node) { - BytecodePosition pos = node.frame.getCaller(); - while (skipPos(pos)) { - pos = pos.getCaller(); - } - return pos; - } - - /** - * Test whether the position associated with a child node should result in an entry in the - * inline tree. The test is for a call node with a bogus position as determined by - * {@link #skipPos(BytecodePosition)}. - * - * @param node A node associated with a child frame in the compilation result frame tree. - * @return True an entry should be included or false if it should be omitted. - */ - private boolean skipNode(FrameNode node) { - return node instanceof CallNode && skipPos(node.frame); - } - - /* - * Test whether the position associated with a call node frame should be embedded along with - * the locations generated for the node's children. This is needed because call frames - * include a valid source position that precedes the first child position. - * - * @param node A node associated with a frame in the compilation result frame tree. - * - * @return True if an inline frame should be included or false if it should be omitted. - */ - - /** - * Test whether the position associated with a call node frame should be embedded along with - * the locations generated for the node's children. This is needed because call frames may - * include a valid source position that precedes the first child position. - * - * @param parent The call node whose children are currently being visited - * @param firstChild The first child of that call node - * @return true if the node should be embedded otherwise false - */ - private boolean embedWithChildren(CallNode parent, FrameNode firstChild) { - // we only need to insert a range for the caller if it fills a gap - // at the start of the caller range before the first child - if (parent.getStartPos() < firstChild.getStartPos()) { - return true; - } - return false; - } - - /** - * Try merging a new location info for a leaf range into the location info for the last leaf - * range added at this level. - * - * @param newLeaf the new leaf location info - * @param args the visitor argument vector used to pass parameters from one child visit to - * the next possibly including the last leaf - * @return the new location info if it could not be merged or null to indicate that it was - * merged - */ - private NativeImageDebugLocationInfo tryMerge(NativeImageDebugLocationInfo newLeaf, Object[] args) { - // last leaf node added at this level is 3rd element of arg vector - NativeImageDebugLocationInfo lastLeaf = (NativeImageDebugLocationInfo) args[LAST_LEAF_INFO]; - - if (lastLeaf != null) { - // try merging new leaf into last one - lastLeaf = lastLeaf.merge(newLeaf); - if (lastLeaf != null) { - // null return indicates new leaf has been merged into last leaf - return null; - } - } - // update last leaf and return new leaf for addition to local info list - args[LAST_LEAF_INFO] = newLeaf; - return newLeaf; - } - - /** - * Set the last leaf node at the current level to the supplied leaf node. - * - * @param lastLeaf the last leaf node created at this level - * @param args the visitor argument vector used to pass parameters from one child visit to - * the next - */ - private void initMerge(NativeImageDebugLocationInfo lastLeaf, Object[] args) { - args[LAST_LEAF_INFO] = lastLeaf; - } - - /** - * Clear the last leaf node at the current level from the visitor arguments by setting the - * arg vector entry to null. - * - * @param args the visitor argument vector used to pass parameters from one child visit to - * the next - */ - private void invalidateMerge(Object[] args) { - args[LAST_LEAF_INFO] = null; - } - - @Override - public int getFrameSize() { - return compilation.getTotalFrameSize(); - } - - @Override - public List getFrameSizeChanges() { - List frameSizeChanges = new LinkedList<>(); - for (CompilationResult.CodeMark mark : compilation.getMarks()) { - /* We only need to observe stack increment or decrement points. */ - if (mark.id.equals(SubstrateMarkId.PROLOGUE_DECD_RSP)) { - NativeImageDebugFrameSizeChange sizeChange = new NativeImageDebugFrameSizeChange(mark.pcOffset, EXTEND); - frameSizeChanges.add(sizeChange); - // } else if (mark.id.equals("PROLOGUE_END")) { - // can ignore these - // } else if (mark.id.equals("EPILOGUE_START")) { - // can ignore these - } else if (mark.id.equals(SubstrateMarkId.EPILOGUE_INCD_RSP)) { - NativeImageDebugFrameSizeChange sizeChange = new NativeImageDebugFrameSizeChange(mark.pcOffset, CONTRACT); - frameSizeChanges.add(sizeChange); - } else if (mark.id.equals(SubstrateMarkId.EPILOGUE_END) && mark.pcOffset < compilation.getTargetCodeSize()) { - /* There is code after this return point so notify a stack extend again. */ - NativeImageDebugFrameSizeChange sizeChange = new NativeImageDebugFrameSizeChange(mark.pcOffset, EXTEND); - frameSizeChanges.add(sizeChange); - } - } - return frameSizeChanges; - } - } - - /** - * Implementation of the DebugLocationInfo API interface that allows line number and local var - * info to be passed to an ObjectFile when generation of debug info is enabled. - */ - private class NativeImageDebugLocationInfo extends NativeImageDebugBaseMethodInfo implements DebugLocationInfo { - private final int bci; - private int lo; - private int hi; - private DebugLocationInfo callersLocationInfo; - private List localInfoList; - private boolean isLeaf; - - NativeImageDebugLocationInfo(FrameNode frameNode, NativeImageDebugLocationInfo callersLocationInfo, int framesize) { - this(frameNode.frame, frameNode.getStartPos(), frameNode.getEndPos() + 1, callersLocationInfo, framesize); - } - - NativeImageDebugLocationInfo(BytecodePosition bcpos, int lo, int hi, NativeImageDebugLocationInfo callersLocationInfo, int framesize) { - super(bcpos.getMethod()); - this.bci = bcpos.getBCI(); - this.lo = lo; - this.hi = hi; - this.callersLocationInfo = callersLocationInfo; - this.localInfoList = initLocalInfoList(bcpos, framesize); - // assume this is a leaf until we find out otherwise - this.isLeaf = true; - // tag the caller as a non-leaf - if (callersLocationInfo != null) { - callersLocationInfo.isLeaf = false; - } - } - - // special constructor for synthetic location info added at start of method - NativeImageDebugLocationInfo(ResolvedJavaMethod method, int hi, ParamLocationProducer locProducer) { - super(method); - // bci is always 0 and lo is always 0. - this.bci = 0; - this.lo = 0; - this.hi = hi; - // this is always going to be a top-level leaf range. - this.callersLocationInfo = null; - // location info is synthesized off the method signature - this.localInfoList = initSyntheticInfoList(locProducer); - // assume this is a leaf until we find out otherwise - this.isLeaf = true; - } - - // special constructor for synthetic location info which splits off the initial segment - // of the first range to accommodate a stack access prior to the stack extend - NativeImageDebugLocationInfo(NativeImageDebugLocationInfo toSplit, int stackDecrement, int frameSize) { - super(toSplit.method); - this.lo = stackDecrement; - this.hi = toSplit.hi; - toSplit.hi = this.lo; - this.bci = toSplit.bci; - this.callersLocationInfo = toSplit.callersLocationInfo; - this.isLeaf = toSplit.isLeaf; - toSplit.isLeaf = true; - this.localInfoList = new ArrayList<>(toSplit.localInfoList.size()); - for (DebugLocalValueInfo localInfo : toSplit.localInfoList) { - if (localInfo.localKind() == DebugLocalValueInfo.LocalKind.STACKSLOT) { - // need to redefine the value for this param using a stack slot value - // that allows for the stack being extended by framesize. however we - // also need to remove any adjustment that was made to allow for the - // difference between the caller SP and the pre-extend callee SP - // because of a stacked return address. - int adjustment = frameSize - PRE_EXTEND_FRAME_SIZE; - NativeImageDebugLocalValue value = NativeImageDebugStackValue.create(localInfo, adjustment); - NativeImageDebugLocalValueInfo nativeLocalInfo = (NativeImageDebugLocalValueInfo) localInfo; - NativeImageDebugLocalValueInfo newLocalinfo = new NativeImageDebugLocalValueInfo(nativeLocalInfo.name, - value, - nativeLocalInfo.kind, - nativeLocalInfo.type, - nativeLocalInfo.slot, - nativeLocalInfo.line); - localInfoList.add(newLocalinfo); - } else { - localInfoList.add(localInfo); - } - } - } - - private List initLocalInfoList(BytecodePosition bcpos, int framesize) { - if (!(bcpos instanceof BytecodeFrame)) { - return null; - } - - BytecodeFrame frame = (BytecodeFrame) bcpos; - if (frame.numLocals == 0) { - return null; - } - // deal with any inconsistencies in the layout of the frame locals - // NativeImageDebugFrameInfo debugFrameInfo = new NativeImageDebugFrameInfo(frame); - - LineNumberTable lineNumberTable = frame.getMethod().getLineNumberTable(); - Local[] localsBySlot = getLocalsBySlot(); - if (localsBySlot == null) { - return Collections.emptyList(); - } - int count = Integer.min(localsBySlot.length, frame.numLocals); - ArrayList localInfos = new ArrayList<>(count); - for (int i = 0; i < count; i++) { - Local l = localsBySlot[i]; - if (l != null) { - // we have a local with a known name, type and slot - String name = l.getName(); - ResolvedJavaType ownerType = method.getDeclaringClass(); - ResolvedJavaType type = l.getType().resolve(ownerType); - JavaKind kind = type.getJavaKind(); - int slot = l.getSlot(); - debugContext.log(DebugContext.DETAILED_LEVEL, "locals[%d] %s type %s slot %d", i, name, type.getName(), slot); - JavaValue value = (slot < frame.numLocals ? frame.getLocalValue(slot) : Value.ILLEGAL); - JavaKind storageKind = (slot < frame.numLocals ? frame.getLocalValueKind(slot) : JavaKind.Illegal); - debugContext.log(DebugContext.DETAILED_LEVEL, " => %s kind %s", value, storageKind); - int bciStart = l.getStartBCI(); - int firstLine = (lineNumberTable != null ? lineNumberTable.getLineNumber(bciStart) : -1); - // only add the local if the kinds match - if ((storageKind == kind) || - isIntegralKindPromotion(storageKind, kind) || - (isForeignWordType(type, ownerType) && kind == JavaKind.Object && storageKind == JavaKind.Long)) { - localInfos.add(new NativeImageDebugLocalValueInfo(name, value, framesize, storageKind, type, slot, firstLine)); - } else if (storageKind != JavaKind.Illegal) { - debugContext.log(DebugContext.DETAILED_LEVEL, " value kind incompatible with var kind %s!", type.getJavaKind()); - } - } - } - return localInfos; - } - - private List initSyntheticInfoList(ParamLocationProducer locProducer) { - Signature signature = method.getSignature(); - int parameterCount = signature.getParameterCount(false); - ArrayList localInfos = new ArrayList<>(); - LocalVariableTable table = method.getLocalVariableTable(); - LineNumberTable lineNumberTable = method.getLineNumberTable(); - int firstLine = (lineNumberTable != null ? lineNumberTable.getLineNumber(0) : -1); - int slot = 0; - int localIdx = 0; - ResolvedJavaType ownerType = method.getDeclaringClass(); - if (!method.isStatic()) { - String name = "this"; - JavaKind kind = ownerType.getJavaKind(); - JavaKind storageKind = isForeignWordType(ownerType, ownerType) ? JavaKind.Long : kind; - assert kind == JavaKind.Object : "must be an object"; - NativeImageDebugLocalValue value = locProducer.thisLocation(); - debugContext.log(DebugContext.DETAILED_LEVEL, "locals[%d] %s type %s slot %d", localIdx, name, ownerType.getName(), slot); - debugContext.log(DebugContext.DETAILED_LEVEL, " => %s kind %s", value, storageKind); - localInfos.add(new NativeImageDebugLocalValueInfo(name, value, storageKind, ownerType, slot, firstLine)); - slot += kind.getSlotCount(); - localIdx++; - } - for (int i = 0; i < parameterCount; i++) { - Local local = (table == null ? null : table.getLocal(slot, 0)); - String name = (local != null ? local.getName() : "__" + i); - ResolvedJavaType paramType = (ResolvedJavaType) signature.getParameterType(i, ownerType); - JavaKind kind = paramType.getJavaKind(); - JavaKind storageKind = isForeignWordType(paramType, ownerType) ? JavaKind.Long : kind; - NativeImageDebugLocalValue value = locProducer.paramLocation(i); - debugContext.log(DebugContext.DETAILED_LEVEL, "locals[%d] %s type %s slot %d", localIdx, name, ownerType.getName(), slot); - debugContext.log(DebugContext.DETAILED_LEVEL, " => %s kind %s", value, storageKind); - localInfos.add(new NativeImageDebugLocalValueInfo(name, value, storageKind, paramType, slot, firstLine)); - slot += kind.getSlotCount(); - localIdx++; - } - return localInfos; - } - - private Local[] getLocalsBySlot() { - LocalVariableTable lvt = method.getLocalVariableTable(); - Local[] nonEmptySortedLocals = null; - if (lvt != null) { - Local[] locals = lvt.getLocalsAt(bci); - if (locals != null && locals.length > 0) { - nonEmptySortedLocals = Arrays.copyOf(locals, locals.length); - Arrays.sort(nonEmptySortedLocals, (Local l1, Local l2) -> l1.getSlot() - l2.getSlot()); - } - } - return nonEmptySortedLocals; - } - - @Override - public int addressLo() { - return lo; - } - - @Override - public int addressHi() { - return hi; - } - - @Override - public int line() { - LineNumberTable lineNumberTable = method.getLineNumberTable(); - if (lineNumberTable != null && bci >= 0) { - return lineNumberTable.getLineNumber(bci); - } - return -1; - } - - @Override - public DebugLocationInfo getCaller() { - return callersLocationInfo; - } - - @Override - public DebugLocalValueInfo[] getLocalValueInfo() { - if (localInfoList != null) { - return localInfoList.toArray(new DebugLocalValueInfo[localInfoList.size()]); - } else { - return EMPTY_LOCAL_VALUE_INFOS; - } - } - - @Override - public boolean isLeaf() { - return isLeaf; - } - - public int depth() { - int depth = 1; - DebugLocationInfo caller = getCaller(); - while (caller != null) { - depth++; - caller = caller.getCaller(); - } - return depth; - } - - private int localsSize() { - if (localInfoList != null) { - return localInfoList.size(); - } else { - return 0; - } - } - - /** - * Merge the supplied leaf location info into this leaf location info if they have - * contiguous ranges, the same method and line number and the same live local variables with - * the same values. - * - * @param that a leaf location info to be merged into this one - * @return this leaf location info if the merge was performed otherwise null - */ - NativeImageDebugLocationInfo merge(NativeImageDebugLocationInfo that) { - assert callersLocationInfo == that.callersLocationInfo; - assert isLeaf == that.isLeaf; - assert depth() == that.depth() : "should only compare sibling ranges"; - assert this.hi <= that.lo : "later nodes should not overlap earlier ones"; - if (this.hi != that.lo) { - return null; - } - if (!method.equals(that.method)) { - return null; - } - if (line() != that.line()) { - return null; - } - int size = localsSize(); - if (size != that.localsSize()) { - return null; - } - for (int i = 0; i < size; i++) { - NativeImageDebugLocalValueInfo thisLocal = (NativeImageDebugLocalValueInfo) localInfoList.get(i); - NativeImageDebugLocalValueInfo thatLocal = (NativeImageDebugLocalValueInfo) that.localInfoList.get(i); - if (!thisLocal.equals(thatLocal)) { - return null; - } - } - debugContext.log(DebugContext.DETAILED_LEVEL, "Merge leaf Location Info : %s depth %d (%d, %d) into (%d, %d)", that.name(), that.depth(), that.lo, that.hi - 1, this.lo, this.hi - 1); - // merging just requires updating lo and hi range as everything else is equal - this.hi = that.hi; - - return this; - } - - public NativeImageDebugLocationInfo split(int stackDecrement, int frameSize) { - // this should be for an initial range extending beyond the stack decrement - assert lo == 0 && lo < stackDecrement && stackDecrement < hi : "invalid split request"; - return new NativeImageDebugLocationInfo(this, stackDecrement, frameSize); - } - - } - - private static final DebugLocalValueInfo[] EMPTY_LOCAL_VALUE_INFOS = new DebugLocalValueInfo[0]; - - static final Register[] AARCH64_GPREG = { - AArch64.r0, - AArch64.r1, - AArch64.r2, - AArch64.r3, - AArch64.r4, - AArch64.r5, - AArch64.r6, - AArch64.r7 - }; - static final Register[] AARCH64_FREG = { - AArch64.v0, - AArch64.v1, - AArch64.v2, - AArch64.v3, - AArch64.v4, - AArch64.v5, - AArch64.v6, - AArch64.v7 - }; - static final Register[] AMD64_GPREG_LINUX = { - AMD64.rdi, - AMD64.rsi, - AMD64.rdx, - AMD64.rcx, - AMD64.r8, - AMD64.r9 - }; - static final Register[] AMD64_FREG_LINUX = { - AMD64.xmm0, - AMD64.xmm1, - AMD64.xmm2, - AMD64.xmm3, - AMD64.xmm4, - AMD64.xmm5, - AMD64.xmm6, - AMD64.xmm7 - }; - static final Register[] AMD64_GPREG_WINDOWS = { - AMD64.rdx, - AMD64.r8, - AMD64.r9, - AMD64.rdi, - AMD64.rsi, - AMD64.rcx - }; - static final Register[] AMD64_FREG_WINDOWS = { - AMD64.xmm0, - AMD64.xmm1, - AMD64.xmm2, - AMD64.xmm3 - }; - - /** - * Size in bytes of the frame at call entry before any stack extend. Essentially this accounts - * for any automatically pushed return address whose presence depends upon the architecture. - */ - static final int PRE_EXTEND_FRAME_SIZE = ConfigurationValues.getTarget().arch.getReturnAddressSize(); - - class ParamLocationProducer { - private final HostedMethod hostedMethod; - private final CallingConvention callingConvention; - private boolean usesStack; - - ParamLocationProducer(HostedMethod method) { - this.hostedMethod = method; - this.callingConvention = getCallingConvention(hostedMethod); - // assume no stack slots until we find out otherwise - this.usesStack = false; - } - - NativeImageDebugLocalValue thisLocation() { - assert !hostedMethod.isStatic(); - return unpack(callingConvention.getArgument(0)); - } - - NativeImageDebugLocalValue paramLocation(int paramIdx) { - assert paramIdx < hostedMethod.getSignature().getParameterCount(false); - int idx = paramIdx; - if (!hostedMethod.isStatic()) { - idx++; - } - return unpack(callingConvention.getArgument(idx)); - } - - private NativeImageDebugLocalValue unpack(AllocatableValue value) { - if (value instanceof RegisterValue) { - RegisterValue registerValue = (RegisterValue) value; - return NativeImageDebugRegisterValue.create(registerValue); - } else { - // call argument must be a stack slot if it is not a register - StackSlot stackSlot = (StackSlot) value; - this.usesStack = true; - // the calling convention provides offsets from the SP relative to the current - // frame size. At the point of call the frame may or may not include a return - // address depending on the architecture. - return NativeImageDebugStackValue.create(stackSlot, PRE_EXTEND_FRAME_SIZE); - } - } - - public boolean usesStack() { - return usesStack; - } - } - - public class NativeImageDebugLocalInfo implements DebugLocalInfo { - protected final String name; - protected ResolvedJavaType type; - protected final JavaKind kind; - protected int slot; - protected int line; - - NativeImageDebugLocalInfo(String name, JavaKind kind, ResolvedJavaType resolvedType, int slot, int line) { - this.name = name; - this.kind = kind; - this.slot = slot; - this.line = line; - // if we don't have a type default it for the JavaKind - // it may still end up null when kind is Undefined. - this.type = (resolvedType != null ? resolvedType : hostedTypeForKind(kind)); - } - - @Override - public ResolvedJavaType valueType() { - if (type != null && type instanceof HostedType) { - return getOriginal((HostedType) type); - } - return type; - } - - @Override - public String name() { - return name; - } - - @Override - public String typeName() { - ResolvedJavaType valueType = valueType(); - return (valueType == null ? "" : valueType().toJavaName()); - } - - @Override - public int slot() { - return slot; - } - - @Override - public int slotCount() { - return kind.getSlotCount(); - } - - @Override - public JavaKind javaKind() { - return kind; - } - - @Override - public int line() { - return line; - } - } - - public class NativeImageDebugLocalValueInfo extends NativeImageDebugLocalInfo implements DebugLocalValueInfo { - private final NativeImageDebugLocalValue value; - private LocalKind localKind; - - NativeImageDebugLocalValueInfo(String name, JavaValue value, int framesize, JavaKind kind, ResolvedJavaType resolvedType, int slot, int line) { - super(name, kind, resolvedType, slot, line); - if (value instanceof RegisterValue) { - this.localKind = LocalKind.REGISTER; - this.value = NativeImageDebugRegisterValue.create((RegisterValue) value); - } else if (value instanceof StackSlot) { - this.localKind = LocalKind.STACKSLOT; - this.value = NativeImageDebugStackValue.create((StackSlot) value, framesize); - } else if (value instanceof JavaConstant) { - JavaConstant constant = (JavaConstant) value; - if (constant instanceof PrimitiveConstant || constant.isNull()) { - this.localKind = LocalKind.CONSTANT; - this.value = NativeImageDebugConstantValue.create(constant); - } else { - long heapOffset = objectOffset(constant); - if (heapOffset >= 0) { - this.localKind = LocalKind.CONSTANT; - this.value = new NativeImageDebugConstantValue(constant, heapOffset); - } else { - this.localKind = LocalKind.UNDEFINED; - this.value = null; - } - } - } else { - this.localKind = LocalKind.UNDEFINED; - this.value = null; - } - } - - NativeImageDebugLocalValueInfo(String name, NativeImageDebugLocalValue value, JavaKind kind, ResolvedJavaType type, int slot, int line) { - super(name, kind, type, slot, line); - if (value == null) { - this.localKind = LocalKind.UNDEFINED; - } else if (value instanceof NativeImageDebugRegisterValue) { - this.localKind = LocalKind.REGISTER; - } else if (value instanceof NativeImageDebugStackValue) { - this.localKind = LocalKind.STACKSLOT; - } else if (value instanceof NativeImageDebugConstantValue) { - this.localKind = LocalKind.CONSTANT; - } - this.value = value; - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof NativeImageDebugLocalValueInfo)) { - return false; - } - NativeImageDebugLocalValueInfo that = (NativeImageDebugLocalValueInfo) o; - // values need to have the same name - if (!name().equals(that.name())) { - return false; - } - // values need to be for the same line - if (line != that.line) { - return false; - } - // location kinds must match - if (localKind != that.localKind) { - return false; - } - // locations must match - switch (localKind) { - case REGISTER: - case STACKSLOT: - case CONSTANT: - return value.equals(that.value); - default: - return true; - } - } - - @Override - public int hashCode() { - return Objects.hash(name(), value) * 31 + line; - } - - @Override - public String toString() { - switch (localKind) { - case REGISTER: - return "reg[" + regIndex() + "]"; - case STACKSLOT: - return "stack[" + stackSlot() + "]"; - case CONSTANT: - return "constant[" + (constantValue() != null ? constantValue().toValueString() : "null") + "]"; - default: - return "-"; - } - } - - @Override - public LocalKind localKind() { - return localKind; - } - - @Override - public int regIndex() { - return ((NativeImageDebugRegisterValue) value).getNumber(); - } - - @Override - public int stackSlot() { - return ((NativeImageDebugStackValue) value).getOffset(); - } - - @Override - public long heapOffset() { - return ((NativeImageDebugConstantValue) value).getHeapOffset(); - } - - @Override - public JavaConstant constantValue() { - return ((NativeImageDebugConstantValue) value).getConstant(); - } - } - - public abstract static class NativeImageDebugLocalValue { - } - - public static final class NativeImageDebugRegisterValue extends NativeImageDebugLocalValue { - private static EconomicMap registerValues = EconomicMap.create(); - private int number; - private String name; - - private NativeImageDebugRegisterValue(int number, String name) { - this.number = number; - this.name = "reg:" + name; - } - - static NativeImageDebugRegisterValue create(RegisterValue value) { - int number = value.getRegister().number; - String name = value.getRegister().name; - return memoizedCreate(number, name); - } - - static NativeImageDebugRegisterValue memoizedCreate(int number, String name) { - NativeImageDebugRegisterValue reg = registerValues.get(number); - if (reg == null) { - reg = new NativeImageDebugRegisterValue(number, name); - registerValues.put(number, reg); - } - return reg; - } - - public int getNumber() { - return number; - } - - @Override - public String toString() { - return name; - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof NativeImageDebugRegisterValue)) { - return false; - } - NativeImageDebugRegisterValue that = (NativeImageDebugRegisterValue) o; - return number == that.number; - } - - @Override - public int hashCode() { - return number * 31; - } - } - - public static final class NativeImageDebugStackValue extends NativeImageDebugLocalValue { - private static EconomicMap stackValues = EconomicMap.create(); - private int offset; - private String name; - - private NativeImageDebugStackValue(int offset) { - this.offset = offset; - this.name = "stack:" + offset; - } - - static NativeImageDebugStackValue create(StackSlot value, int framesize) { - // Work around a problem on AArch64 where StackSlot asserts if it is - // passed a zero frame size, even though this is what is expected - // for stack slot offsets provided at the point of entry (because, - // unlike x86, lr has not been pushed). - int offset = (framesize == 0 ? value.getRawOffset() : value.getOffset(framesize)); - return memoizedCreate(offset); - } - - static NativeImageDebugStackValue create(DebugLocalValueInfo previous, int adjustment) { - assert previous.localKind() == DebugLocalValueInfo.LocalKind.STACKSLOT; - return memoizedCreate(previous.stackSlot() + adjustment); - } - - private static NativeImageDebugStackValue memoizedCreate(int offset) { - NativeImageDebugStackValue value = stackValues.get(offset); - if (value == null) { - value = new NativeImageDebugStackValue(offset); - stackValues.put(offset, value); - } - return value; - } - - public int getOffset() { - return offset; - } - - @Override - public String toString() { - return name; - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof NativeImageDebugStackValue)) { - return false; - } - NativeImageDebugStackValue that = (NativeImageDebugStackValue) o; - return offset == that.offset; - } - - @Override - public int hashCode() { - return offset * 31; - } - } - - public static final class NativeImageDebugConstantValue extends NativeImageDebugLocalValue { - private static EconomicMap constantValues = EconomicMap.create(); - private JavaConstant value; - private long heapoffset; - - private NativeImageDebugConstantValue(JavaConstant value, long heapoffset) { - this.value = value; - this.heapoffset = heapoffset; - } - - static NativeImageDebugConstantValue create(JavaConstant value) { - return create(value, -1); - } - - static NativeImageDebugConstantValue create(JavaConstant value, long heapoffset) { - NativeImageDebugConstantValue c = constantValues.get(value); - if (c == null) { - c = new NativeImageDebugConstantValue(value, heapoffset); - constantValues.put(value, c); - } - assert c.heapoffset == heapoffset; - return c; - } - - public JavaConstant getConstant() { - return value; - } - - public long getHeapOffset() { - return heapoffset; - } - - @Override - public String toString() { - return "constant:" + value.toString(); - } - - @Override - public boolean equals(Object o) { - if (!(o instanceof NativeImageDebugConstantValue)) { - return false; - } - NativeImageDebugConstantValue that = (NativeImageDebugConstantValue) o; - return heapoffset == that.heapoffset && value.equals(that.value); - } - - @Override - public int hashCode() { - return Objects.hash(value) * 31 + (int) heapoffset; - } - } - - /** - * Implementation of the DebugFrameSizeChange API interface that allows stack frame size change - * info to be passed to an ObjectFile when generation of debug info is enabled. - */ - private class NativeImageDebugFrameSizeChange implements DebugFrameSizeChange { - private int offset; - private Type type; - - NativeImageDebugFrameSizeChange(int offset, Type type) { - this.offset = offset; - this.type = type; - } - - @Override - public int getOffset() { - return offset; - } - - @Override - public Type getType() { - return type; - } - } - - private class NativeImageDebugDataInfo implements DebugDataInfo { - private final NativeImageHeap.ObjectInfo objectInfo; - - @SuppressWarnings("try") - @Override - public void debugContext(Consumer action) { - try (DebugContext.Scope s = debugContext.scope("DebugDataInfo")) { - action.accept(debugContext); - } catch (Throwable e) { - throw debugContext.handle(e); - } - } - - /* Accessors. */ - - NativeImageDebugDataInfo(ObjectInfo objectInfo) { - this.objectInfo = objectInfo; - } - - @Override - public String getProvenance() { - return objectInfo.toString(); - } - - @Override - public String getTypeName() { - return objectInfo.getClazz().toJavaName(); - } - - @Override - public String getPartition() { - ImageHeapPartition partition = objectInfo.getPartition(); - return partition.getName() + "{" + partition.getSize() + "}@" + partition.getStartOffset(); - } - - @Override - public long getOffset() { - return objectInfo.getOffset(); - } - - @Override - public long getSize() { - return objectInfo.getSize(); - } - } - - private DebugDataInfo createDebugDataInfo(ObjectInfo objectInfo) { - return new NativeImageDebugDataInfo(objectInfo); - } - - @Override - public void recordActivity() { - DeadlockWatchdog.singleton().recordActivity(); + return -1; } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProviderBase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProviderBase.java deleted file mode 100644 index ea2984aaa3a7..000000000000 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProviderBase.java +++ /dev/null @@ -1,485 +0,0 @@ -/* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2023, 2023, Red Hat Inc. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package com.oracle.svm.hosted.image; - -import static com.oracle.svm.hosted.c.info.AccessorInfo.AccessorKind.ADDRESS; -import static com.oracle.svm.hosted.c.info.AccessorInfo.AccessorKind.GETTER; -import static com.oracle.svm.hosted.c.info.AccessorInfo.AccessorKind.SETTER; - -import java.nio.file.FileSystems; -import java.nio.file.Path; -import java.util.HashMap; - -import org.graalvm.nativeimage.ImageSingletons; -import org.graalvm.word.WordBase; - -import com.oracle.svm.core.StaticFieldsSupport; -import com.oracle.svm.core.SubstrateOptions; -import com.oracle.svm.core.config.ConfigurationValues; -import com.oracle.svm.core.config.ObjectLayout; -import com.oracle.svm.core.graal.code.SubstrateCallingConvention; -import com.oracle.svm.core.graal.code.SubstrateCallingConventionKind; -import com.oracle.svm.core.graal.code.SubstrateCallingConventionType; -import com.oracle.svm.core.graal.meta.RuntimeConfiguration; -import com.oracle.svm.core.graal.meta.SubstrateRegisterConfig; -import com.oracle.svm.core.heap.Heap; -import com.oracle.svm.core.heap.ObjectHeader; -import com.oracle.svm.core.util.VMError; -import com.oracle.svm.hosted.c.NativeLibraries; -import com.oracle.svm.hosted.c.info.AccessorInfo; -import com.oracle.svm.hosted.c.info.ElementInfo; -import com.oracle.svm.hosted.c.info.SizableInfo; -import com.oracle.svm.hosted.c.info.StructFieldInfo; -import com.oracle.svm.hosted.c.info.StructInfo; -import com.oracle.svm.hosted.meta.HostedField; -import com.oracle.svm.hosted.meta.HostedMetaAccess; -import com.oracle.svm.hosted.meta.HostedMethod; -import com.oracle.svm.hosted.meta.HostedType; -import com.oracle.svm.hosted.substitute.InjectedFieldsType; -import com.oracle.svm.hosted.substitute.SubstitutionMethod; -import com.oracle.svm.hosted.substitute.SubstitutionType; - -import jdk.graal.compiler.core.common.CompressEncoding; -import jdk.graal.compiler.core.target.Backend; -import jdk.vm.ci.code.RegisterConfig; -import jdk.vm.ci.meta.JavaConstant; -import jdk.vm.ci.meta.JavaKind; -import jdk.vm.ci.meta.JavaType; -import jdk.vm.ci.meta.ResolvedJavaMethod; -import jdk.vm.ci.meta.ResolvedJavaType; - -/** - * Abstract base class for implementation of DebugInfoProvider API providing a suite of useful - * static and instance methods useful to provider implementations. - */ -public abstract class NativeImageDebugInfoProviderBase { - protected final NativeImageHeap heap; - protected final NativeImageCodeCache codeCache; - protected final NativeLibraries nativeLibs; - protected final RuntimeConfiguration runtimeConfiguration; - protected final boolean useHeapBase; - protected final int compressionShift; - protected final int referenceSize; - protected final int pointerSize; - protected final int objectAlignment; - protected final int primitiveStartOffset; - protected final int referenceStartOffset; - protected final int reservedHubBitsMask; - - protected final HostedType hubType; - protected final HostedType wordBaseType; - final HashMap javaKindToHostedType; - private final Path cachePath = SubstrateOptions.getDebugInfoSourceCacheRoot(); - - public NativeImageDebugInfoProviderBase(NativeImageCodeCache codeCache, NativeImageHeap heap, NativeLibraries nativeLibs, HostedMetaAccess metaAccess, RuntimeConfiguration runtimeConfiguration) { - this.heap = heap; - this.codeCache = codeCache; - this.nativeLibs = nativeLibs; - this.runtimeConfiguration = runtimeConfiguration; - this.hubType = metaAccess.lookupJavaType(Class.class); - this.wordBaseType = metaAccess.lookupJavaType(WordBase.class); - this.pointerSize = ConfigurationValues.getTarget().wordSize; - ObjectHeader objectHeader = Heap.getHeap().getObjectHeader(); - NativeImageHeap.ObjectInfo primitiveFields = heap.getObjectInfo(StaticFieldsSupport.getCurrentLayerStaticPrimitiveFields()); - NativeImageHeap.ObjectInfo objectFields = heap.getObjectInfo(StaticFieldsSupport.getCurrentLayerStaticObjectFields()); - this.reservedHubBitsMask = objectHeader.getReservedHubBitsMask(); - if (SubstrateOptions.SpawnIsolates.getValue()) { - CompressEncoding compressEncoding = ImageSingletons.lookup(CompressEncoding.class); - this.useHeapBase = compressEncoding.hasBase(); - this.compressionShift = (compressEncoding.hasShift() ? compressEncoding.getShift() : 0); - } else { - this.useHeapBase = false; - this.compressionShift = 0; - } - this.referenceSize = getObjectLayout().getReferenceSize(); - this.objectAlignment = getObjectLayout().getAlignment(); - /* Offsets need to be adjusted relative to the heap base plus partition-specific offset. */ - primitiveStartOffset = (int) primitiveFields.getOffset(); - referenceStartOffset = (int) objectFields.getOffset(); - javaKindToHostedType = initJavaKindToHostedTypes(metaAccess); - } - - /* - * HostedType wraps an AnalysisType and both HostedType and AnalysisType punt calls to - * getSourceFilename to the wrapped class so for consistency we need to do type names and path - * lookup relative to the doubly unwrapped HostedType. - * - * However, note that the result of the unwrap on the AnalysisType may be a SubstitutionType - * which wraps both an original type and the annotated type that substitutes it. Unwrapping - * normally returns the AnnotatedType which we need to use to resolve the file name. However, we - * frequently (but not always) need to use the original to name the owning type to ensure that - * names found in method param and return types resolve correctly. - * - * Likewise, unwrapping of an AnalysisMethod or AnalysisField may encounter a SubstitutionMethod - * or SubstitutionField. It may also encounter a SubstitutionType when unwrapping the owner type - * is unwrapped. In those cases also we may need to use the substituted metadata rather than the - * substitution to ensure that names resolve correctly. - * - * The following static routines provide logic to perform unwrapping and, where necessary, - * traversal from the various substitution metadata instance to the corresponding substituted - * metadata instance. - */ - - protected static ResolvedJavaType getDeclaringClass(HostedType hostedType, boolean wantOriginal) { - // unwrap to the underlying class either the original or target class - if (wantOriginal) { - return getOriginal(hostedType); - } - // we want any substituted target if there is one. directly unwrapping will - // do what we want. - return hostedType.getWrapped().getWrapped(); - } - - protected static ResolvedJavaType getDeclaringClass(HostedMethod hostedMethod, boolean wantOriginal) { - if (wantOriginal) { - return getOriginal(hostedMethod.getDeclaringClass()); - } - // we want a substituted target if there is one. if there is a substitution at the end of - // the method chain fetch the annotated target class - ResolvedJavaMethod javaMethod = NativeImageDebugInfoProviderBase.getAnnotatedOrOriginal(hostedMethod); - return javaMethod.getDeclaringClass(); - } - - @SuppressWarnings("unused") - protected static ResolvedJavaType getDeclaringClass(HostedField hostedField, boolean wantOriginal) { - /* for now fields are always reported as belonging to the original class */ - return getOriginal(hostedField.getDeclaringClass()); - } - - protected static ResolvedJavaType getOriginal(HostedType hostedType) { - /* - * partially unwrap then traverse through substitutions to the original. We don't want to - * get the original type of LambdaSubstitutionType to keep the stable name - */ - ResolvedJavaType javaType = hostedType.getWrapped().getWrapped(); - if (javaType instanceof SubstitutionType) { - return ((SubstitutionType) javaType).getOriginal(); - } else if (javaType instanceof InjectedFieldsType) { - return ((InjectedFieldsType) javaType).getOriginal(); - } - return javaType; - } - - private static ResolvedJavaMethod getAnnotatedOrOriginal(HostedMethod hostedMethod) { - ResolvedJavaMethod javaMethod = hostedMethod.getWrapped().getWrapped(); - // This method is only used when identifying the modifiers or the declaring class - // of a HostedMethod. Normally the method unwraps to the underlying JVMCI method - // which is the one that provides bytecode to the compiler as well as, line numbers - // and local info. If we unwrap to a SubstitutionMethod then we use the annotated - // method, not the JVMCI method that the annotation refers to since that will be the - // one providing the bytecode etc used by the compiler. If we unwrap to any other, - // custom substitution method we simply use it rather than dereferencing to the - // original. The difference is that the annotated method's bytecode will be used to - // replace the original and the debugger needs to use it to identify the file and access - // permissions. A custom substitution may exist alongside the original, as is the case - // with some uses for reflection. So, we don't want to conflate the custom substituted - // method and the original. In this latter case the method code will be synthesized without - // reference to the bytecode of the original. Hence there is no associated file and the - // permissions need to be determined from the custom substitution method itself. - - if (javaMethod instanceof SubstitutionMethod) { - SubstitutionMethod substitutionMethod = (SubstitutionMethod) javaMethod; - javaMethod = substitutionMethod.getAnnotated(); - } - return javaMethod; - } - - protected static int getOriginalModifiers(HostedMethod hostedMethod) { - return NativeImageDebugInfoProviderBase.getAnnotatedOrOriginal(hostedMethod).getModifiers(); - } - - /* - * GraalVM uses annotated interfaces to model foreign types. The following helpers support - * detection and categorization of these types, whether presented as a JavaType or HostedType. - */ - - /** - * Identify a Java type which is being used to model a foreign memory word or pointer type. - * - * @param type the type to be tested - * @param accessingType another type relative to which the first type may need to be resolved - * @return true if the type models a foreign memory word or pointer type - */ - protected boolean isForeignWordType(JavaType type, ResolvedJavaType accessingType) { - HostedType resolvedJavaType = (HostedType) type.resolve(accessingType); - return isForeignWordType(resolvedJavaType); - } - - /** - * Identify a hosted type which is being used to model a foreign memory word or pointer type. - * - * @param hostedType the type to be tested - * @return true if the type models a foreign memory word or pointer type - */ - protected boolean isForeignWordType(HostedType hostedType) { - // unwrap because native libs operates on the analysis type universe - return nativeLibs.isWordBase(hostedType.getWrapped()); - } - - /** - * Identify a hosted type which is being used to model a foreign pointer type. - * - * @param hostedType the type to be tested - * @return true if the type models a foreign pointer type - */ - protected boolean isForeignPointerType(HostedType hostedType) { - // unwrap because native libs operates on the analysis type universe - return nativeLibs.isPointerBase(hostedType.getWrapped()); - } - - /* - * Foreign pointer types have associated element info which describes the target type. The - * following helpers support querying of and access to this element info. - */ - - protected static boolean isTypedField(ElementInfo elementInfo) { - if (elementInfo instanceof StructFieldInfo) { - for (ElementInfo child : elementInfo.getChildren()) { - if (child instanceof AccessorInfo) { - switch (((AccessorInfo) child).getAccessorKind()) { - case GETTER: - case SETTER: - case ADDRESS: - return true; - } - } - } - } - return false; - } - - protected HostedType getFieldType(StructFieldInfo field) { - // we should always have some sort of accessor, preferably a GETTER or a SETTER - // but possibly an ADDRESS accessor - for (ElementInfo elt : field.getChildren()) { - if (elt instanceof AccessorInfo) { - AccessorInfo accessorInfo = (AccessorInfo) elt; - if (accessorInfo.getAccessorKind() == GETTER) { - return heap.hUniverse.lookup(accessorInfo.getReturnType()); - } - } - } - for (ElementInfo elt : field.getChildren()) { - if (elt instanceof AccessorInfo) { - AccessorInfo accessorInfo = (AccessorInfo) elt; - if (accessorInfo.getAccessorKind() == SETTER) { - return heap.hUniverse.lookup(accessorInfo.getParameterType(0)); - } - } - } - for (ElementInfo elt : field.getChildren()) { - if (elt instanceof AccessorInfo) { - AccessorInfo accessorInfo = (AccessorInfo) elt; - if (accessorInfo.getAccessorKind() == ADDRESS) { - return heap.hUniverse.lookup(accessorInfo.getReturnType()); - } - } - } - assert false : "Field %s must have a GETTER, SETTER, ADDRESS or OFFSET accessor".formatted(field); - // treat it as a word? - // n.b. we want a hosted type not an analysis type - return heap.hUniverse.lookup(wordBaseType); - } - - protected static boolean fieldTypeIsEmbedded(StructFieldInfo field) { - // we should always have some sort of accessor, preferably a GETTER or a SETTER - // but possibly an ADDRESS - for (ElementInfo elt : field.getChildren()) { - if (elt instanceof AccessorInfo) { - AccessorInfo accessorInfo = (AccessorInfo) elt; - if (accessorInfo.getAccessorKind() == GETTER) { - return false; - } - } - } - for (ElementInfo elt : field.getChildren()) { - if (elt instanceof AccessorInfo) { - AccessorInfo accessorInfo = (AccessorInfo) elt; - if (accessorInfo.getAccessorKind() == SETTER) { - return false; - } - } - } - for (ElementInfo elt : field.getChildren()) { - if (elt instanceof AccessorInfo) { - AccessorInfo accessorInfo = (AccessorInfo) elt; - if (accessorInfo.getAccessorKind() == ADDRESS) { - return true; - } - } - } - throw VMError.shouldNotReachHere("Field %s must have a GETTER, SETTER, ADDRESS or OFFSET accessor".formatted(field)); - } - - protected static int elementSize(ElementInfo elementInfo) { - if (!(elementInfo instanceof SizableInfo) || elementInfo instanceof StructInfo structInfo && structInfo.isIncomplete()) { - return 0; - } - Integer size = ((SizableInfo) elementInfo).getSizeInBytes(); - assert size != null; - return size; - } - - protected static String elementName(ElementInfo elementInfo) { - if (elementInfo == null) { - return ""; - } else { - return elementInfo.getName(); - } - } - - protected static SizableInfo.ElementKind elementKind(SizableInfo sizableInfo) { - return sizableInfo.getKind(); - } - - /* - * Debug info generation requires knowledge of a variety of parameters that determine object - * sizes and layouts and the organization of the code and code cache. The following helper - * methods provide access to this information. - */ - - static ObjectLayout getObjectLayout() { - return ConfigurationValues.getObjectLayout(); - } - - public boolean useHeapBase() { - return useHeapBase; - } - - public int compressionShift() { - return compressionShift; - } - - public int referenceSize() { - return referenceSize; - } - - public int pointerSize() { - return pointerSize; - } - - public int objectAlignment() { - return objectAlignment; - } - - public int reservedHubBitsMask() { - return reservedHubBitsMask; - } - - public int compiledCodeMax() { - return codeCache.getCodeCacheSize(); - } - - /** - * Return the offset into the initial heap at which the object identified by constant is located - * or -1 if the object is not present in the initial heap. - * - * @param constant must have JavaKind Object and must be non-null. - * @return the offset into the initial heap at which the object identified by constant is - * located or -1 if the object is not present in the initial heap. - */ - public long objectOffset(JavaConstant constant) { - assert constant.getJavaKind() == JavaKind.Object && !constant.isNull() : "invalid constant for object offset lookup"; - NativeImageHeap.ObjectInfo objectInfo = heap.getConstantInfo(constant); - if (objectInfo != null) { - return objectInfo.getOffset(); - } - return -1; - } - - protected HostedType hostedTypeForKind(JavaKind kind) { - return javaKindToHostedType.get(kind); - } - - private static HashMap initJavaKindToHostedTypes(HostedMetaAccess metaAccess) { - HashMap map = new HashMap<>(); - for (JavaKind kind : JavaKind.values()) { - Class clazz; - switch (kind) { - case Illegal: - clazz = null; - break; - case Object: - clazz = java.lang.Object.class; - break; - default: - clazz = kind.toJavaClass(); - } - HostedType javaType = clazz != null ? metaAccess.lookupJavaType(clazz) : null; - map.put(kind, javaType); - } - return map; - } - - /** - * Retrieve details of the native calling convention for a top level compiled method, including - * details of which registers or stack slots are used to pass parameters. - * - * @param method The method whose calling convention is required. - * @return The calling convention for the method. - */ - protected SubstrateCallingConvention getCallingConvention(HostedMethod method) { - SubstrateCallingConventionKind callingConventionKind = method.getCallingConventionKind(); - HostedType declaringClass = method.getDeclaringClass(); - HostedType receiverType = method.isStatic() ? null : declaringClass; - var signature = method.getSignature(); - final SubstrateCallingConventionType type; - if (callingConventionKind.isCustom()) { - type = method.getCustomCallingConventionType(); - } else { - type = callingConventionKind.toType(false); - } - Backend backend = runtimeConfiguration.lookupBackend(method); - RegisterConfig registerConfig = backend.getCodeCache().getRegisterConfig(); - assert registerConfig instanceof SubstrateRegisterConfig; - return (SubstrateCallingConvention) registerConfig.getCallingConvention(type, signature.getReturnType(), signature.toParameterTypes(receiverType), backend); - } - - /* - * The following helpers aid construction of file paths for class source files. - */ - - protected static Path fullFilePathFromClassName(HostedType hostedInstanceClass) { - String[] elements = hostedInstanceClass.toJavaName().split("\\."); - int count = elements.length; - String name = elements[count - 1]; - while (name.startsWith("$")) { - name = name.substring(1); - } - if (name.contains("$")) { - name = name.substring(0, name.indexOf('$')); - } - if (name.equals("")) { - name = "_nofile_"; - } - elements[count - 1] = name + ".java"; - return FileSystems.getDefault().getPath("", elements); - } - - public Path getCachePath() { - return cachePath; - } -} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageViaCC.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageViaCC.java index 5c4109768b64..9ee02216e24c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageViaCC.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageViaCC.java @@ -168,7 +168,7 @@ private void runLinkerCommand(String imageName, LinkerInvocation inv, List clazz, De path = locateSource(fileName, packageName, clazz); if (path == null) { // as a last ditch effort derive path from the Java class name - if (debugContext.areScopesEnabled()) { + if (debugContext.isLogEnabled()) { debugContext.log(DebugContext.INFO_LEVEL, "Failed to find source file for class %s%n", resolvedType.toJavaName()); } - if (packageName.length() > 0) { + if (!packageName.isEmpty()) { path = Paths.get("", packageName.split("\\.")); path = path.resolve(fileName); } @@ -93,7 +92,7 @@ public Path findAndCacheSource(ResolvedJavaType resolvedType, Class clazz, De * the source name embedded in the class file or the class name itself. * * @param resolvedType the resolved java type whose source file name is required - * @return the file name or null if it the class cannot be associated with a source file + * @return the file name or null if the class cannot be associated with a source file */ private static String computeBaseName(ResolvedJavaType resolvedType) { if (resolvedType.isPrimitive()) { @@ -146,10 +145,10 @@ private static String computePackageName(ResolvedJavaType javaType) { * * @param fileName the base file name for the source file * @param packageName the name of the package for the associated Java class - * @return a protoype name for the source file + * @return a prototype name for the source file */ private static Path computePrototypeName(String fileName, String packageName) { - if (packageName.length() == 0) { + if (packageName.isEmpty()) { return Paths.get("", fileName); } else { return Paths.get("", packageName.split("\\.")).resolve(fileName); @@ -160,7 +159,7 @@ private static Path computePrototypeName(String fileName, String packageName) { * A map from a Java type to an associated source paths which is known to have an up to date * entry in the relevant source file cache. This is used to memoize previous lookups. */ - private static HashMap verifiedPaths = new HashMap<>(); + private static final ConcurrentHashMap verifiedPaths = new ConcurrentHashMap<>(); /** * An invalid path used as a marker to track failed lookups so we don't waste time looking up diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/RuntimeCompileDebugInfoTest.java b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/RuntimeCompileDebugInfoTest.java new file mode 100644 index 000000000000..2709bc47b8ec --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/RuntimeCompileDebugInfoTest.java @@ -0,0 +1,477 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package com.oracle.svm.test.debug.helper; + +import static com.oracle.svm.core.util.VMError.shouldNotReachHere; + +import java.util.ArrayList; +import java.util.List; + +import org.graalvm.nativeimage.ImageSingletons; +import org.graalvm.nativeimage.UnmanagedMemory; +import org.graalvm.nativeimage.VMRuntime; +import org.graalvm.nativeimage.c.function.CFunctionPointer; +import org.graalvm.nativeimage.c.struct.SizeOf; +import org.graalvm.nativeimage.c.type.CCharPointer; +import org.graalvm.nativeimage.c.type.CCharPointerPointer; +import org.graalvm.nativeimage.c.type.CTypeConversion; +import org.graalvm.nativeimage.hosted.Feature; +import org.graalvm.nativeimage.hosted.RuntimeClassInitialization; +import org.graalvm.word.WordBase; +import org.graalvm.word.WordFactory; + +import com.oracle.svm.core.AlwaysInline; +import com.oracle.svm.core.NeverInline; +import com.oracle.svm.core.c.InvokeJavaFunctionPointer; +import com.oracle.svm.graal.SubstrateGraalUtils; +import com.oracle.svm.graal.hosted.runtimecompilation.RuntimeCompilationFeature; +import com.oracle.svm.graal.meta.SubstrateMethod; +import com.oracle.svm.hosted.FeatureImpl.BeforeAnalysisAccessImpl; +import com.oracle.svm.test.debug.CStructTests; +import com.oracle.svm.util.ModuleSupport; + +import jdk.vm.ci.code.InstalledCode; + +class RuntimeCompilations { + + Integer a; + int b; + String c; + Object d; + + static Integer sa = 42; + static int sb; + static String sc; + static Object sd; + + RuntimeCompilations(int a) { + this.a = a; + } + + @NeverInline("For testing") + private void breakHere() { + } + + @NeverInline("For testing") + private void breakHere(@SuppressWarnings("unused") Object... pin) { + } + + @NeverInline("For testing") + private void breakHere(@SuppressWarnings("unused") WordBase... pin) { + } + + @NeverInline("For testing") + public Integer paramMethod(Integer param1, int param2, String param3, Object param4) { + a = param1; + breakHere(); + b = param2; + breakHere(); + c = param3; + breakHere(); + d = param4; + breakHere(param1); + return a; + } + + @NeverInline("For testing") + public Integer noParamMethod() { + return a; + } + + @NeverInline("For testing") + public void voidMethod() { + a *= 2; + breakHere(); + } + + @NeverInline("For testing") + public int primitiveReturnMethod(int param1) { + b += param1; + breakHere(); + return b; + } + + @NeverInline("For testing") + public Integer[] arrayMethod(int[] param1, @SuppressWarnings("unused") String[] param2) { + a = param1[0]; + breakHere(); + return new Integer[]{a}; + } + + @NeverInline("For testing") + @SuppressWarnings("unused") + public float localsMethod(String param1) { + float f = 1.5f; + String x = param1; + breakHere(); + byte[] bytes = x.getBytes(); + breakHere(); + return f + bytes.length; + } + + @NeverInline("For testing") + public static Integer staticMethod(Integer param1, int param2, String param3, Object param4) { + sa = param1; + sb = param2; + sc = param3; + sd = param4; + return sa + sb; + } + + @NeverInline("For testing") + public void cPointerTypes(CCharPointer charPtr, CCharPointerPointer charPtrPtr) { + breakHere(charPtr, charPtrPtr); + } + + @NeverInline("For testing") + public void inlineTest(String param1) { + inlineMethod1(param1); + breakHere(); + inlineMethod2(param1); + breakHere(); + noInlineMethod1(param1); + breakHere(); + noInlineMethod2(param1); + } + + @AlwaysInline("For testing") + private void inlineMethod1(String param1) { + String inlineParam = param1; + breakHere(inlineParam); + } + + @AlwaysInline("For testing") + protected void inlineMethod2(@SuppressWarnings("unused") String param1) { + String p1 = "1"; + inlineMethod1(p1); + breakHere(); + + String p2 = "2"; + noInlineMethod1(p2); + breakHere(); + } + + @NeverInline("For testing") + private void noInlineMethod1(String param1) { + String noInlineParam = param1; + breakHere(noInlineParam); + } + + @NeverInline("For testing") + protected void noInlineMethod2(@SuppressWarnings("unused") String param1) { + String p1 = "a"; + noInlineMethod1(p1); + breakHere(); + + String p2 = "b"; + inlineMethod1(p2); + breakHere(); + + String p3 = "c"; + inlineMethod2(p3); + breakHere(); + } +} + +public class RuntimeCompileDebugInfoTest { + static class RuntimeHolder { + SubstrateMethod testMethod1; + SubstrateMethod testMethod2; + SubstrateMethod testMethod3; + SubstrateMethod testMethod4; + SubstrateMethod testMethod5; + SubstrateMethod testMethod6; + SubstrateMethod testMethod7; + SubstrateMethod testMethod8; + SubstrateMethod testMethod9; + SubstrateMethod testMethod10; + SubstrateMethod testMethod11; + } + + public static class RegisterMethodsFeature implements Feature { + RegisterMethodsFeature() { + ModuleSupport.accessPackagesToClass(ModuleSupport.Access.EXPORT, RegisterMethodsFeature.class, false, + "org.graalvm.nativeimage.builder", + "com.oracle.svm.graal", "com.oracle.svm.graal.hosted.runtimecompilation", "com.oracle.svm.hosted", "com.oracle.svm.core.util"); + ModuleSupport.accessPackagesToClass(ModuleSupport.Access.EXPORT, RegisterMethodsFeature.class, false, + "jdk.internal.vm.ci", + "jdk.vm.ci.code"); + ModuleSupport.accessPackagesToClass(ModuleSupport.Access.EXPORT, RegisterMethodsFeature.class, false, + "jdk.graal.compiler", + "jdk.graal.compiler.api.directives", "jdk.graal.compiler.word"); + } + + @Override + public List> getRequiredFeatures() { + return List.of(RuntimeCompilationFeature.class); + } + + @Override + public void beforeAnalysis(BeforeAnalysisAccess a) { + BeforeAnalysisAccessImpl config = (BeforeAnalysisAccessImpl) a; + + RuntimeHolder holder = new RuntimeHolder(); + RuntimeClassInitialization.initializeAtBuildTime(RuntimeHolder.class); + ImageSingletons.add(RuntimeHolder.class, holder); + + RuntimeCompilationFeature runtimeCompilationFeature = RuntimeCompilationFeature.singleton(); + runtimeCompilationFeature.initializeRuntimeCompilationForTesting(config); + + holder.testMethod1 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "paramMethod", Integer.class, int.class, String.class, Object.class); + holder.testMethod2 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "noParamMethod"); + holder.testMethod3 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "voidMethod"); + holder.testMethod4 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "primitiveReturnMethod", int.class); + holder.testMethod5 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "arrayMethod", int[].class, String[].class); + holder.testMethod6 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "localsMethod", String.class); + holder.testMethod7 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "staticMethod", Integer.class, int.class, String.class, Object.class); + holder.testMethod8 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "inlineTest", String.class); + holder.testMethod9 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, CStructTests.class, "mixedArguments"); + holder.testMethod10 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, CStructTests.class, "weird"); + holder.testMethod11 = prepareMethodForRuntimeCompilation(config, runtimeCompilationFeature, RuntimeCompilations.class, "cPointerTypes", CCharPointer.class, CCharPointerPointer.class); + } + + private static SubstrateMethod prepareMethodForRuntimeCompilation(BeforeAnalysisAccessImpl config, RuntimeCompilationFeature runtimeCompilationFeature, Class holder, String methodName, + Class... signature) { + RuntimeClassInitialization.initializeAtBuildTime(holder); + try { + return runtimeCompilationFeature.prepareMethodForRuntimeCompilation(holder.getMethod(methodName, signature), config); + } catch (NoSuchMethodException ex) { + throw shouldNotReachHere(ex); + } + } + } + + interface TestFunctionPointer extends CFunctionPointer { + @InvokeJavaFunctionPointer + Integer invoke(Object receiver, Integer arg1, int arg2, String arg3, Object arg4); + + @InvokeJavaFunctionPointer + Object invoke(Object receiver); + + @InvokeJavaFunctionPointer + void invoke(); + + @InvokeJavaFunctionPointer + void invoke(Object receiver, CCharPointer arg1, CCharPointerPointer arg2); + + @InvokeJavaFunctionPointer + int invoke(Object receiver, int arg1); + + @InvokeJavaFunctionPointer + Integer[] invoke(Object receiver, int[] arg1, String[] arg2); + + @InvokeJavaFunctionPointer + float invoke(Object receiver, String arg1); + + @InvokeJavaFunctionPointer + Integer invoke(Integer arg1, int arg2, String arg3, Object arg4); + } + + private static RuntimeHolder getHolder() { + return ImageSingletons.lookup(RuntimeHolder.class); + } + + private static Integer invoke(TestFunctionPointer functionPointer, Object receiver, Integer arg1, int arg2, String arg3, Object arg4) { + return functionPointer.invoke(receiver, arg1, arg2, arg3, arg4); + } + + private static void invoke(TestFunctionPointer functionPointer) { + functionPointer.invoke(); + } + + private static void invoke(TestFunctionPointer functionPointer, Object receiver, CCharPointer arg1, CCharPointerPointer arg2) { + functionPointer.invoke(receiver, arg1, arg2); + } + + private static Object invoke(TestFunctionPointer functionPointer, Object receiver) { + return functionPointer.invoke(receiver); + } + + private static int invoke(TestFunctionPointer functionPointer, Object receiver, int arg1) { + return functionPointer.invoke(receiver, arg1); + } + + private static Integer[] invoke(TestFunctionPointer functionPointer, Object receiver, int[] arg1, String[] arg2) { + return functionPointer.invoke(receiver, arg1, arg2); + } + + private static float invoke(TestFunctionPointer functionPointer, Object receiver, String arg1) { + return functionPointer.invoke(receiver, arg1); + } + + private static Integer invoke(TestFunctionPointer functionPointer, Integer arg1, int arg2, String arg3, Object arg4) { + return functionPointer.invoke(arg1, arg2, arg3, arg4); + } + + private static TestFunctionPointer getFunctionPointer(InstalledCode installedCode) { + return WordFactory.pointer(installedCode.getEntryPoint()); + } + + @SuppressWarnings("unused") + public static void testParams() { + InstalledCode installedCode = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod1); + InstalledCode installedCodeStatic = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod7); + + RuntimeCompilations x = new RuntimeCompilations(11); + Integer param1 = 42; + int param2 = 27; + String param3 = "test"; + Object param4 = new ArrayList<>(); + + Integer result = invoke(getFunctionPointer(installedCode), x, param1, param2, param3, param4); + Integer resultStatic = invoke(getFunctionPointer(installedCodeStatic), param1, param2, param3, param4); + + installedCode.invalidate(); + installedCodeStatic.invalidate(); + } + + @SuppressWarnings("unused") + public static void testNoParam() { + InstalledCode installedCode = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod2); + + RuntimeCompilations x = new RuntimeCompilations(11); + + Integer result = (Integer) invoke(getFunctionPointer(installedCode), x); + + installedCode.invalidate(); + } + + public static void testVoid() { + InstalledCode installedCode = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod3); + + RuntimeCompilations x = new RuntimeCompilations(11); + + invoke(getFunctionPointer(installedCode), x); + + installedCode.invalidate(); + } + + @SuppressWarnings("unused") + public static void testPrimitiveReturn() { + InstalledCode installedCode = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod4); + + RuntimeCompilations x = new RuntimeCompilations(11); + int param1 = 42; + + int result = invoke(getFunctionPointer(installedCode), x, param1); + + installedCode.invalidate(); + } + + @SuppressWarnings("unused") + public static void testArray() { + InstalledCode installedCode = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod5); + + RuntimeCompilations x = new RuntimeCompilations(11); + int[] param1 = new int[]{1, 2, 3, 4}; + String[] param2 = new String[]{"this", "is", "an", "array"}; + + Integer[] result = invoke(getFunctionPointer(installedCode), x, param1, param2); + + installedCode.invalidate(); + } + + @SuppressWarnings("unused") + public static void testLocals() { + InstalledCode installedCode = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod6); + + RuntimeCompilations x = new RuntimeCompilations(11); + String param1 = "test"; + + float result = invoke(getFunctionPointer(installedCode), x, param1); + + installedCode.invalidate(); + } + + public static void testInlining() { + InstalledCode installedCode = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod8); + + RuntimeCompilations x = new RuntimeCompilations(11); + String param1 = "test"; + + invoke(getFunctionPointer(installedCode), x, param1); + + installedCode.invalidate(); + } + + public static void testCStructs() { + InstalledCode installedCode1 = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod9); + InstalledCode installedCode2 = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod10); + + invoke(getFunctionPointer(installedCode1)); + invoke(getFunctionPointer(installedCode2)); + + installedCode1.invalidate(); + installedCode2.invalidate(); + } + + public static void testCPointer() { + InstalledCode installedCode = SubstrateGraalUtils.compileAndInstall(getHolder().testMethod11); + + RuntimeCompilations x = new RuntimeCompilations(11); + CCharPointer param1 = CTypeConversion.toCString("test").get(); + CCharPointerPointer param2 = UnmanagedMemory.malloc(SizeOf.get(CCharPointer.class)); + param2.write(param1); + + invoke(getFunctionPointer(installedCode), x, param1, param2); + + installedCode.invalidate(); + UnmanagedMemory.free(param2); + } + + @SuppressWarnings("unused") + public static void testAOTCompiled() { + + RuntimeCompilations x = new RuntimeCompilations(11); + Integer param1 = 42; + int param2 = 27; + String param3 = "test"; + Object param4 = new ArrayList<>(); + + Integer result = x.paramMethod(param1, param2, param3, param4); + Integer result2 = RuntimeCompilations.staticMethod(param1, param2, param3, param4); + Integer result3 = x.noParamMethod(); + } + + public static void main(String[] args) { + /* Run startup hooks to, e.g., install the segfault handler so that we get crash reports. */ + VMRuntime.initialize(); + + // aot compiled code for comparing generated debug infos + testAOTCompiled(); + + // use runtime compiled code + testParams(); + testNoParam(); + testVoid(); + testPrimitiveReturn(); + testArray(); + testLocals(); + testInlining(); + testCStructs(); + testCPointer(); + } +} diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/gdb_helper.py b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/gdb_helper.py index 3754c24380c9..3666c1f1050a 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/gdb_helper.py +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/gdb_helper.py @@ -32,7 +32,7 @@ import gdb -logfile = os.environ.get('gdbdebughelperstest_logfile', 'debug_helper.log') +logfile = os.environ.get('gdb_logfile', 'debug_helper.log') logging.basicConfig(filename=logfile, format='%(name)s %(levelname)s: %(message)s', level=logging.DEBUG) logger = logging.getLogger('[DebugTest]') @@ -100,7 +100,7 @@ def gdb_advanced_print(var: str, output_format: str = None) -> str: def gdb_set_breakpoint(location: str) -> None: logger.info(f"Setting breakpoint at: {location}") - gdb_execute(f"break {location}") + gdb.Breakpoint(location) def gdb_set_param(name: str, value: str) -> None: diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_cinterface.py b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_cinterface.py index e0db933104e7..0d6282593a82 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_cinterface.py +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_cinterface.py @@ -53,6 +53,10 @@ def test_auto_load(self): self.assertIn("SubstrateVM", exec_string, 'pretty-printer was not loaded') # assume that there are no other pretty-printers were attached to an objfile self.assertIn("objfile", exec_string, 'pretty-printer was not attached to an objfile') + # check frame filter + exec_string = gdb_execute('info frame-filter') + self.assertIn('libcinterfacetutorial.so.debug frame-filters:', exec_string) + self.assertIn('SubstrateVM FrameFilter', exec_string) def test_manual_load(self): backup_auto_load_param = gdb_get_param("auto-load python-scripts") @@ -65,6 +69,10 @@ def test_manual_load(self): exec_string = gdb_execute('info pretty-printer') self.assertIn("objfile", exec_string) # check if any objfile has a pretty-printer self.assertIn("SubstrateVM", exec_string) + # check frame filter + exec_string = gdb_execute('info frame-filter') + self.assertIn('libcinterfacetutorial.so.debug frame-filters:', exec_string) + self.assertIn('SubstrateVM FrameFilter', exec_string) finally: # make sure auto-loading is re-enabled for other tests gdb_set_param("auto-load python-scripts", backup_auto_load_param) @@ -80,10 +88,16 @@ def test_manual_load_without_executable(self): def test_auto_reload(self): gdb_start() - gdb_start() # all loaded shared libraries get freed (their pretty printers are removed) and newly attached + # all loaded shared libraries get freed and newly attached + # pretty printers, frame filters and frame unwinders should also be reattached + gdb_start() exec_string = gdb_execute('info pretty-printer') self.assertIn("SubstrateVM", exec_string, 'pretty-printer was not loaded') self.assertIn("objfile", exec_string, 'pretty-printer was not attached to an objfile') + # check frame filter + exec_string = gdb_execute('info frame-filter') + self.assertIn('libcinterfacetutorial.so.debug frame-filters:', exec_string) + self.assertIn('SubstrateVM FrameFilter', exec_string) class TestCInterface(unittest.TestCase): @@ -114,7 +128,7 @@ def test_print_from_java_shared_libray(self): exec_string = gdb_output("data") self.assertTrue(exec_string.startswith('my_data = {'), f"GDB output: '{exec_string}'") self.assertIn('f_primitive = 42', exec_string) - self.assertIn('f_array = int32_t [4] = {...}', exec_string) + self.assertIn('f_array = int [4] = {...}', exec_string) self.assertRegex(exec_string, f'f_cstr = 0x{hex_rexp.pattern} "Hello World"') self.assertRegex(exec_string, f'f_java_object_handle = 0x{hex_rexp.pattern}') self.assertRegex(exec_string, f'f_print_function = 0x{hex_rexp.pattern} ') diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_class_loader.py b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_class_loader.py index 38d06d71d756..577763f9bf8e 100644 --- a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_class_loader.py +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_class_loader.py @@ -72,6 +72,7 @@ def setUp(self): self.maxDiff = None set_up_test() set_up_gdb_debughelpers() + self.svm_util = SVMUtil() def tearDown(self): gdb_delete_breakpoints() @@ -82,8 +83,8 @@ def test_get_classloader_namespace(self): gdb_run() this = gdb.parse_and_eval('this') # type = com.oracle.svm.test.missing.classes.TestClass -> testClassLoader field = gdb.parse_and_eval('this.instanceField') # instanceField is null - self.assertRegex(SVMUtil.get_classloader_namespace(this), f'testClassLoader_{hex_rexp.pattern}') - self.assertEqual(SVMUtil.get_classloader_namespace(field), "") + self.assertRegex(self.svm_util.get_classloader_namespace(this), f'testClassLoader_{hex_rexp.pattern}') + self.assertEqual(self.svm_util.get_classloader_namespace(field), "") # redirect unittest output to terminal diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_runtime_compilation.py b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_runtime_compilation.py new file mode 100644 index 000000000000..9c37bf21dc83 --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_runtime_compilation.py @@ -0,0 +1,347 @@ +# +# Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# + +import os +import sys +import unittest + +import gdb + +# add test directory to path to allow import of gdb_helper.py +sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)))) + +from gdb_helper import * + + +# this just tests the jit compilation interface, not the generated debug info +# however still requires symbols to be available for setting a breakpoint +class TestJITCompilationInterface(unittest.TestCase): + @classmethod + def setUp(cls): + set_up_test() + gdb_delete_breakpoints() + gdb_start() + + @classmethod + def tearDown(cls): + gdb_delete_breakpoints() + gdb_kill() + + # this test requires gdb to automatically add a breakpoint when a runtime compilation occurs + # if a breakpoint for the compiled method existed before + def test_update_breakpoint(self): + # set breakpoint in runtime compiled function and stop there + # store initial breakpoint info for comparison + gdb_set_breakpoint("com.oracle.svm.test.debug.helper.RuntimeCompilations::inlineTest") + breakpoint_info_before = gdb_execute('info breakpoints') + gdb_continue() + + # get current breakpoint info and do checks + breakpoint_info_after = gdb_execute('info breakpoints') + # check if we got more breakpoints than before + self.assertGreater(len(breakpoint_info_after), len(breakpoint_info_before)) + # check if old breakpoints still exist, code address must be the same + # split at code address as multi-breakpoints are printed very different to single breakpoints + self.assertIn(breakpoint_info_before.split('0x')[-1], breakpoint_info_after) + # check if exactly one new correct breakpoint was added + # new breakpoint address is always added after + self.assertEqual(breakpoint_info_after.split(breakpoint_info_before.split('0x')[-1])[-1].count('com.oracle.svm.test.debug.helper.RuntimeCompilations::inlineTest'), 1) + + # run until the runtime compilation is invalidated and check if the breakpoint is removed + gdb_set_breakpoint('com.oracle.svm.graal.meta.SubstrateInstalledCodeImpl::invalidate') + gdb_continue() # run until invalidation + gdb_finish() # finish invalidation - this should trigger an unregister call to gdb + breakpoint_info_after_invalidation = gdb_execute('info breakpoints') + # check if additional breakpoint is cleared after invalidate + # breakpoint info is still printed as multi-breakpoint, thus we check if exactly one valid breakpoint is remaining + self.assertEqual(breakpoint_info_after_invalidation.count('com.oracle.svm.test.debug.helper.RuntimeCompilations::inlineTest'), 1) + # breakpoint info must change after invalidation + self.assertNotEqual(breakpoint_info_after, breakpoint_info_after_invalidation) + + # this test requires gdb to first load a new objfile at runtime and then remove it as the compilation is invalidated + def test_load_objfile(self): + # sanity check, we should not have in-memory objfiles at this point (to avoid false-positives later) + objfiles = gdb.objfiles() + self.assertFalse(any([o.filename.startswith('') + self.assertEqual(gdb_output('param3'), '"test"') + self.assertEqual(gdb_output('param4'), 'java.util.ArrayList(0)') + this = gdb_output('this') + self.assertTrue(this.startswith('com.oracle.svm.test.debug.helper.RuntimeCompilations = {')) + self.assertIn('a = 42', this) + self.assertIn('b = 27', this) + self.assertIn('c = null', this) + self.assertIn('d = null', this) + # step 3 set c to param3 + gdb_continue() + gdb_finish() + self.assertEqual(gdb_output('param1'), '42') + self.assertEqual(gdb_output('param2'), '') + self.assertEqual(gdb_output('param3'), '') + self.assertEqual(gdb_output('param4'), 'java.util.ArrayList(0)') + this = gdb_output('this') + self.assertTrue(this.startswith('com.oracle.svm.test.debug.helper.RuntimeCompilations = {')) + self.assertIn('a = 42', this) + self.assertIn('b = 27', this) + self.assertIn('c = "test"', this) + self.assertIn('d = null', this) + # step 4 set d to param4 (pin of param1 ends here) + gdb_continue() + gdb_finish() + self.assertEqual(gdb_output('param1'), '') + self.assertEqual(gdb_output('param2'), '') + self.assertEqual(gdb_output('param3'), '') + self.assertEqual(gdb_output('param4'), '') + this = gdb_output('this') + self.assertTrue(this.startswith('com.oracle.svm.test.debug.helper.RuntimeCompilations = {')) + self.assertIn('a = 42', this) + self.assertIn('b = 27', this) + self.assertIn('c = "test"', this) + self.assertIn('d = java.util.ArrayList(0)', this) + + # compares params and param types of AOT and JIT compiled method + def test_compare_AOT_to_JIT(self): + gdb_set_breakpoint("com.oracle.svm.test.debug.helper.RuntimeCompilations::paramMethod") + # first stop for the AOT compiled variant + gdb_continue() + this = gdb_output('this') + this_t = gdb_print_type('this') + param1 = gdb_output('param1') + param1_t = gdb_print_type('param1') + param2 = gdb_output('param2') + param2_t = gdb_print_type('param2') + param3 = gdb_output('param3') + param3_t = gdb_print_type('param3') + param4 = gdb_output('param4') + param4_t = gdb_print_type('param4') + + # now stop for runtime compiled variant and check if equal + gdb_continue() + self.assertEqual(gdb_output('this'), this) + self.assertEqual(gdb_print_type('this'), this_t) + self.assertEqual(gdb_output('param1'), param1) + self.assertEqual(gdb_print_type('param1'), param1_t) + self.assertEqual(gdb_output('param2'), param2) + self.assertEqual(gdb_print_type('param2'), param2_t) + self.assertEqual(gdb_output('param3'), param3) + self.assertEqual(gdb_print_type('param3'), param3_t) + self.assertEqual(gdb_output('param4'), param4) + self.assertEqual(gdb_print_type('param4'), param4_t) + + # checks run-time debug info for c types + def test_c_types_1(self): + gdb_set_breakpoint("com.oracle.svm.test.debug.CStructTests::weird") + # first stop for the AOT compiled variant + gdb_continue() + gdb_next() + + # check if contents are correct + wd = gdb_output('wd') + self.assertIn('f_short = 42', wd) + self.assertIn('f_int = 43', wd) + self.assertIn('f_long = 44', wd) + self.assertIn('f_float = 4.5', wd) + self.assertIn('f_double = 4.59999', wd) + self.assertIn('a_int = int [8] = {', wd) + self.assertIn('a_char = char [12] = {', wd) + + # check if opaque type resolution works + # the full type unit is in the AOT debug info, the run-time debug contains an opaque type (resolve by type name) + # the actual type name is the Java class name (the class annotated with @CStruct) as typedef for the c type + # -> typedefs are resolved by gdb automatically + wd_t = gdb_print_type('wd') + self.assertIn('type = struct weird {', wd_t) + self.assertIn('short f_short;', wd_t) + self.assertIn('int f_int;', wd_t) + self.assertIn('long f_long;', wd_t) + self.assertIn('float f_float;', wd_t) + self.assertIn('double f_double;', wd_t) + self.assertIn('int a_int[8];', wd_t) + self.assertIn('char a_char[12];', wd_t) + + def test_c_types_2(self): + gdb_set_breakpoint("com.oracle.svm.test.debug.CStructTests::mixedArguments") + # first stop for the AOT compiled variant + gdb_continue() + gdb_next() + gdb_next() + + # check if contents are correct + ss1 = gdb_output('ss1') + self.assertIn('first = 1', ss1) + self.assertIn('second = 2', ss1) + ss2 = gdb_output('ss2') + self.assertIn('alpha = 99', ss2) + self.assertIn('beta = 100', ss2) + + # check if opaque type resolution works + # the full type unit is in the AOT debug info, the run-time debug contains an opaque type (resolve by type name) + # the actual type name is the Java class name (the class annotated with @CStruct) as typedef for the c type + # -> typedefs are resolved by gdb automatically + ss1_t = gdb_print_type('ss1') + self.assertIn('type = struct simple_struct {', ss1_t) + self.assertIn('int first;', ss1_t) + self.assertIn('int second;', ss1_t) + ss2_t = gdb_print_type('ss2') + self.assertIn('type = struct simple_struct2 {', ss2_t) + self.assertIn('byte alpha;', ss2_t) + self.assertIn('long beta;', ss2_t) + + def test_c_types_3(self): + gdb_set_breakpoint("com.oracle.svm.test.debug.helper.RuntimeCompilations::cPointerTypes") + # first stop for the AOT compiled variant + gdb_continue() + + # check if contents are correct + c1 = gdb_output('charPtr') + self.assertRegex(c1, f'\\(org.graalvm.nativeimage.c.type.CCharPointer\\) 0x{hex_rexp.pattern} "test"') + c2_p = gdb_output('charPtrPtr') + self.assertRegex(c2_p, f'\\(org.graalvm.nativeimage.c.type.CCharPointerPointer\\) 0x{hex_rexp.pattern}') + c2 = gdb_output('*charPtrPtr') + self.assertEqual(c1, c2) + + # check if opaque type resolution works + # the full type unit is in the AOT debug info, the run-time debug contains an opaque type (resolve by type name) + # the actual type name is the Java class name (the class annotated with @CStruct) as typedef for the c type + # -> typedefs are resolved by gdb automatically + c1_t = gdb_print_type('charPtr') + self.assertIn('type = char *', c1_t) + c2_p_t = gdb_print_type('charPtrPtr') + self.assertIn('type = char **', c2_p_t) + + # check if dereferencing resolves to the correct value + self.assertEqual(gdb.parse_and_eval('charPtr'), gdb.parse_and_eval('*charPtrPtr')) + + +# redirect unittest output to terminal +result = unittest.main(testRunner=unittest.TextTestRunner(stream=sys.__stdout__), exit=False) +# close gdb +gdb_quit(0 if result.result.wasSuccessful() else 1) diff --git a/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_runtime_deopt.py b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_runtime_deopt.py new file mode 100644 index 000000000000..ec16345963d8 --- /dev/null +++ b/substratevm/src/com.oracle.svm.test/src/com/oracle/svm/test/debug/helper/test_runtime_deopt.py @@ -0,0 +1,127 @@ +# +# Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# This code is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 only, as +# published by the Free Software Foundation. Oracle designates this +# particular file as subject to the "Classpath" exception as provided +# by Oracle in the LICENSE file that accompanied this code. +# +# This code is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +# version 2 for more details (a copy is included in the LICENSE file that +# accompanied this code). +# +# You should have received a copy of the GNU General Public License version +# 2 along with this work; if not, write to the Free Software Foundation, +# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +# or visit www.oracle.com if you need additional information or have any +# questions. +# + +import os +import sys +import unittest + +import gdb + +# add test directory to path to allow import of gdb_helper.py +sys.path.insert(0, os.path.join(os.path.dirname(os.path.realpath(__file__)))) + +from gdb_helper import * + + +# requires the gdb patch to be available +class TestRuntimeDeopt(unittest.TestCase): + @classmethod + def setUp(cls): + cls.maxDiff = None + set_up_test() + gdb_start() + + @classmethod + def tearDown(cls): + gdb_delete_breakpoints() + gdb_kill() + + def test_frame_unwinder_registration(self): + # run to a method where the frame unwinder is registered + gdb_set_breakpoint('com.oracle.truffle.runtime.OptimizedCallTarget::profiledPERoot') + gdb_continue() + set_up_gdb_debughelpers() # setup debughelpers after we know it exists + # check frame unwinder + unwinder_info = gdb_execute('info unwinder') + self.assertIn('libjsvm.so.debug:', unwinder_info) + self.assertIn('SubstrateVM FrameUnwinder', unwinder_info) + + # for shared libraries, the frame unwinder is removed when the shared library is unloaded + # when it is loaded again, the gdb script is not run again + # the gdb-debughelpers should still be able to reload the frame unwinder + def test_frame_unwinder_reload(self): + # run to a method where the frame unwinder is registered + gdb_set_breakpoint('com.oracle.truffle.runtime.OptimizedCallTarget::profiledPERoot') + gdb_continue() + set_up_gdb_debughelpers() # setup debughelpers after we know it exists + + # stops previous execution and reloads the shared library + # gdb-debughelpers should then reload the frame unwinder for the shared library + gdb_run() + # check frame unwinder + unwinder_info = gdb_execute('info unwinder') + self.assertIn('libjsvm.so.debug:', unwinder_info) + self.assertIn('SubstrateVM FrameUnwinder', unwinder_info) + + def test_backtrace_with_deopt(self): + # run until method is deoptimized + gdb_set_breakpoint("com.oracle.svm.core.deopt.Deoptimizer::invalidateMethodOfFrame") + gdb_continue() + gdb_finish() + + # check backtrace + backtrace = gdb_execute('backtrace 5') + # check if eager deopt frame + if 'EAGER DEOPT FRAME' in backtrace: + self.assertIn('in [EAGER DEOPT FRAME]', backtrace) + self.assertIn('deoptFrameValues=2', backtrace) + + # check if values are printed correctly and backtrace is not corrupted + if 'SubstrateEnterpriseOptimizedCallTarget' in backtrace: + self.assertIn('SubstrateEnterpriseOptimizedCallTarget = {...}, __1=java.lang.Object[5] = {...}', backtrace) + self.assertIn('SubstrateEnterpriseOptimizedCallTarget::doInvoke', backtrace) + else: + self.assertIn('SubstrateOptimizedCallTarget = {...}, __1=java.lang.Object[5] = {...})', backtrace) + self.assertIn('SubstrateOptimizedCallTargetInstalledCode::doInvoke', backtrace) + + self.assertNotIn('??', backtrace) + self.assertNotIn('Unknown Frame at', backtrace) + else: + # must be lazy deopt frame + # we can't be sure it is handled properly, but at least it should show up as lazy deopt frame in the backtrace + self.assertIn('[LAZY DEOPT FRAME] at', backtrace) + + # the js deopt test uses the jsvm-library + # so the debugging symbols come from the js shared library + def test_opaque_types_with_shared_library(self): + # stop at a method where we know that the runtime compiled frame is in the backtrace + gdb_set_breakpoint("com.oracle.svm.core.deopt.Deoptimizer::invalidateMethodOfFrame") + gdb_continue() + + # check backtrace + backtrace = gdb_execute('backtrace 5') + if 'SubstrateEnterpriseOptimizedCallTarget' in backtrace: + self.assertIn('SubstrateEnterpriseOptimizedCallTarget::add_I_AAIIZ', backtrace) + self.assertNotIn(', originalArguments=)', backtrace) + self.assertNotIn('this=