# # Copyright (c) 2023, 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. # from typing import Iterable import sys import os import re import gdb import gdb.types import gdb.printing import gdb.unwinder from gdb.FrameDecorator import FrameDecorator if sys.version_info.major < 3: pyversion = '.'.join(str(v) for v in sys.version_info[:3]) message = ( 'Cannot load SubstrateVM debugging assistance for GDB from ' + os.path.basename(__file__) + ': it requires at least Python 3.x. You are running GDB with Python ' + pyversion + ' from ' + sys.executable + '.' ) raise AssertionError(message) if int(gdb.VERSION.split('.')[0]) < 14: message = ( 'Cannot load SubstrateVM debugging assistance for GDB from ' + os.path.basename(__file__) + ': it requires at least GDB 14.x. You are running GDB ' + gdb.VERSION + '.' ) raise AssertionError(message) # check for a symbol that exists only in native image debug info if gdb.lookup_global_symbol("com.oracle.svm.core.Isolates") is None: message = ( 'Cannot load SubstrateVM debugging assistance without a loaded java native image or native shared library,' + 'the script requires java types for initialization.' ) raise AssertionError(message) def trace(msg: str) -> None: if svm_debug_tracing.tracefile: svm_debug_tracing.tracefile.write(f'trace: {msg}\n'.encode(encoding='utf-8', errors='strict')) 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() except exceptions or Exception: return failure() if callable(failure) else failure class Function: """A more complete representation of gdb function symbols.""" def __init__(self, static: bool, name: str, gdb_sym: gdb.Symbol): self.static = static self.name = name self.sym = gdb_sym class SVMUtil: 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 parents = dict() selfref_cycles = set() hlreps = dict() deopt_stub_adr = 0 @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 @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())) @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}') return result @classmethod def get_compressed_type(cls, t: gdb.Type) -> gdb.Type: 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 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 trace(f' - get_compressed_type({t}) = {type_name}') return gdb.lookup_type(type_name) @classmethod def get_compressed_oop(cls, 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): return int(obj) obj_adr = 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) compressed_oop = obj_adr if cls.use_heap_base: compressed_oop -= int(SVMUtil.get_heap_base()) assert compression_shift >= 0 compressed_oop = compressed_oop >> compression_shift if is_hub and num_reserved_bits != 0: assert compression_shift >= 0 compressed_oop = compressed_oop << compression_shift assert num_alignment_bits >= 0 compressed_oop = compressed_oop >> num_alignment_bits assert num_reserved_bits >= 0 compressed_oop = compressed_oop << num_reserved_bits 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))})' else: result = f' @({hex(adr(obj))})' trace(f' - adr_str({hex(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: 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}') return result @classmethod def add_selfref(cls, 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): return child child_adr = adr(child) parent_adr = 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): trace(f' ') cls.selfref_cycles.add(child_adr) else: trace(f' {hex(parent_adr)}>') if child_adr in cls.parents: cls.parents[child_adr].append(parent_adr) else: cls.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): return "" trace(f' - get_java_string({hex(adr(obj))})') coder = cls.get_int_field(obj, 'coder', None) if coder is None: codec = 'utf-16' # Java 8 has a char[] with utf-16 bytes_per_char = 2 else: trace(f' - get_java_string: coder = {coder}') # From Java 9 on, value is byte[] with latin_1 or utf-16_le codec = { 0: 'latin_1', 1: 'utf-16_le', }.get(coder) bytes_per_char = 1 value = cls.get_obj_field(obj, 'value') if cls.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: return "" string_data = bytearray() for index in range(min(svm_print_string_limit.value, value_length) if gdb_output_string and svm_print_string_limit.value >= 0 else value_length): mask = (1 << 8 * bytes_per_char) - 1 code_unit = int(value_content[index] & mask) code_unit_as_bytes = code_unit.to_bytes(bytes_per_char, byteorder='little') string_data.extend(code_unit_as_bytes) result = string_data.decode(codec).replace("\x00", r"\0") if gdb_output_string and 0 < svm_print_string_limit.value < value_length: result += "..." trace(f' - get_java_string({hex(adr(obj))}) = {result}') return result @classmethod def get_obj_field(cls, obj: gdb.Value, field_name: str, default: gdb.Value = null) -> gdb.Value: try: return obj[field_name] except gdb.error: return 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) 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: try: hub = cls.get_obj_field(obj, cls.hub_field_name) if cls.is_null(hub): return "" hub_companion = cls.get_obj_field(hub, 'companion') if cls.is_null(hub_companion): return "" loader = cls.get_obj_field(hub_companion, 'classLoader') if cls.is_null(loader): return "" loader = cls.cast_to(loader, cls.classloader_type) loader_name = cls.get_obj_field(loader, 'nameAndId') if cls.is_null(loader_name): return "" loader_namespace = cls.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 = 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) if static_type == cls.hub_type: return cls.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) hub = cls.get_obj_field(obj, cls.hub_field_name) if cls.is_null(hub): return static_type name_field = cls.get_obj_field(hub, 'name') if cls.is_null(name_field): return static_type rtt_name = cls.get_java_string(name_field) if rtt_name.startswith('['): array_dimension = rtt_name.count('[') if array_dimension > 0: rtt_name = rtt_name[array_dimension:] if rtt_name[0] == 'L': classname_end = rtt_name.find(';') rtt_name = rtt_name[1:classname_end] else: rtt_name = { 'Z': 'boolean', 'B': 'byte', 'C': 'char', 'D': 'double', 'F': 'float', 'I': 'int', 'J': 'long', 'S': 'short', }.get(rtt_name, rtt_name) for _ in range(array_dimension): rtt_name += '[]' loader_namespace = cls.get_classloader_namespace(obj) if loader_namespace != "": try: # try to apply loader namespace rtt = gdb.lookup_type(loader_namespace + '::' + rtt_name) except gdb.error: rtt = gdb.lookup_type(rtt_name) # found a loader namespace that is ignored (e.g. 'app') 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) trace(f' - get_rtt({hex(adr(obj))}) = {rtt_name}') return rtt @classmethod def cast_to(cls, 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) else: obj_oop = adr(obj) trace(f' - cast_to({hex(adr(obj))}, {t})') if t.code != gdb.TYPE_CODE_PTR: t = t.pointer() trace(f' - cast_to({hex(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}') return result @classmethod def is_primitive_wrapper(cls, t: gdb.Type) -> bool: result = t in cls.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) @classmethod def find_shared_types(cls, type_list: list, t: gdb.Type) -> list: # list[gdb.Type] if len(type_list) == 0: while t != cls.object_type: type_list += [t] t = cls.get_base_class(t) return type_list else: while t != cls.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 @classmethod def is_java_type(cls, t: gdb.Type) -> bool: t = cls.get_uncompressed_type(cls.get_basic_type(t)) # Check for existing ".class" symbol (which exists for every java type in a native image) # a java class is represented by a struct, interfaces are represented by a union # only structs contain a "hub" field, thus just checking for a hub field does not work for interfaces result = ((t.code == gdb.TYPE_CODE_UNION and gdb.lookup_global_symbol(t.name + '.class', gdb.SYMBOL_VAR_DOMAIN) is not None) or (t.code == gdb.TYPE_CODE_STRUCT and gdb.types.has_field(t, SVMUtil.hub_field_name))) trace(f' - is_java_obj({t}) = {result}') return result class SVMPPString: def __init__(self, obj: gdb.Value, java: bool = True): trace(f' - __init__({hex(adr(obj))})') self.__obj = obj self.__java = java def to_string(self) -> str: trace(' - to_string') if self.__java: try: value = '"' + SVMUtil.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) 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})') 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.__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 if not self.__skip_children: SVMUtil.current_print_depth += 1 def display_hint(self) -> str: trace(' - display_hint = array') return 'array' 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)) 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) trace(f' - to_string = {value}') return value def __iter__(self): trace(' - __iter__') if self.__array is not None: for i in range(self.__length): yield self.__array[i] def children(self) -> Iterable[object]: trace(' - children') if self.__skip_children: return for index, elem in enumerate(self): # apply custom limit only for java arrays if self.__java_array and 0 <= svm_print_element_limit.value <= index: yield str(index), '...' return trace(f' - children[{index}]') yield str(index), SVMUtil.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))})') 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 if not self.__skip_children: SVMUtil.current_print_depth += 1 def __getitem__(self, key: str) -> gdb.Value: trace(f' - __getitem__({key})') item = SVMUtil.get_obj_field(self.__obj, key, None) if item is None: return None pp_item = gdb.default_visualizer(item) return item if pp_item is None else pp_item 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 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) trace(f' - to_string = {result}') return result except gdb.error as ex: trace(f" - to_string error - SVMPPClass: {ex}") return 'object' def children(self) -> Iterable[object]: trace(' - children (class field iterator)') 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] 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]) SVMUtil.current_print_depth -= 1 class SVMPPEnum: def __init__(self, obj: gdb.Value): trace(f' - __init__({hex(adr(obj))})') self.__obj = obj self.__name = SVMUtil.get_obj_field(self.__obj, 'name', "") self.__ordinal = SVMUtil.get_int_field(self.__obj, 'ordinal', None) def to_string(self) -> str: result = SVMUtil.get_java_string(self.__name) + f"({self.__ordinal})" if svm_print_address.with_adr: result += SVMUtil.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))})') self.__obj = obj self.__value = SVMUtil.get_obj_field(self.__obj, 'value', obj.type.name) def to_string(self) -> str: result = str(self.__value) if svm_print_address.with_adr: result += SVMUtil.adr_str(self.__obj) trace(f' - to_string = {result}') return result class SVMPPConst: def __init__(self, val: str): trace(' - __init__') self.__val = val def to_string(self) -> str: result = "null" if self.__val is None else self.__val trace(f' - to_string = {result}') return result class SVMPrettyPrinter(gdb.printing.PrettyPrinter): def __init__(self): super().__init__(SVMUtil.pretty_printer_name) def __call__(self, obj: gdb.Value): trace(f' - __call__({obj.type} @ {hex(adr(obj))})') if not SVMUtil.is_primitive(obj.type) and SVMUtil.is_java_type(obj.type): # Filter out references to the null literal if SVMUtil.is_null(obj): return SVMPPConst(None) rtt = SVMUtil.get_rtt(obj) uncompressed_rtt = SVMUtil.get_uncompressed_type(rtt) obj = SVMUtil.cast_to(obj, rtt) # filter for primitive wrappers if SVMUtil.is_primitive_wrapper(uncompressed_rtt): return SVMPPBoxedPrimitive(obj) # filter for strings if uncompressed_rtt == SVMUtil.string_type: return SVMPPString(obj) # filter for arrays if uncompressed_rtt.name.endswith("[]"): return SVMPPArray(obj) # filter for enum values if SVMUtil.is_enum_type(uncompressed_rtt): return SVMPPEnum(obj) # Any other Class ... if svm_use_hlrep.value: pp = make_high_level_object(obj, uncompressed_rtt.name) else: pp = SVMPPClass(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): return SVMPPConst(None) return self.__call__(obj.dereference()) elif obj.type.code == gdb.TYPE_CODE_ARRAY: return SVMPPArray(obj, False) elif obj.type.code == gdb.TYPE_CODE_TYPEDEF: # try to expand foreign c structs try: obj = obj.dereference() return self.__call__(obj) except gdb.error as err: return None elif obj.type.code == gdb.TYPE_CODE_STRUCT: return SVMPPClass(obj, False) elif SVMUtil.is_primitive(obj.type): if obj.type.name == "char" and obj.type.sizeof == 2: return SVMPPConst(repr(chr(obj))) elif obj.type.name == "byte": return SVMPPConst(str(int(obj))) else: return None else: return None def HLRep(original_class): try: SVMUtil.hlreps[original_class.target_type] = original_class except Exception as ex: trace(f'<@HLRep registration exception: {ex}>') return original_class @HLRep 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): 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 = None self.__obj = obj self.__skip_children = SVMUtil.is_selfref(obj) or 0 <= svm_print_depth_limit.value <= SVMUtil.current_print_depth if not self.__skip_children: SVMUtil.current_print_depth += 1 def to_string(self) -> str: trace(' - to_string') res = 'java.util.ArrayList' if svm_infer_generics.value != 0: elem_type = self.infer_generic_types() if elem_type is not None: res += f'<{elem_type}>' res += f'({self.__size})' if self.__skip_children: res += ' = {...}' if svm_print_address.with_adr: res += SVMUtil.adr_str(self.__obj) trace(f' - to_string = {res}') return res 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): break return None if len(elem_type) == 0 else SVMUtil.get_unqualified_type_name(elem_type[0].name) def display_hint(self) -> str: trace(' - display_hint = array') return 'array' def __iter__(self) -> gdb.Value: trace(' - __iter__') if self.__data is not None: for i in range(self.__size): yield self.__data[i] def children(self) -> Iterable[object]: trace(f' - children({self.__obj.type} @ {hex(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) SVMUtil.current_print_depth -= 1 @HLRep class HashMap: target_type = 'java.util.HashMap' def __init__(self, obj: gdb.Value): trace(f' - __init__({obj.type} @ {hex(adr(obj))})') self.__size = SVMUtil.get_int_field(obj, 'size') table = SVMUtil.get_obj_field(obj, 'table') if SVMUtil.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 = None self.__table_len = SVMUtil.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 if not self.__skip_children: SVMUtil.current_print_depth += 1 def to_string(self) -> str: trace(' - to_string') res = 'java.util.HashMap' if svm_infer_generics.value != 0: key_type, value_type = self.infer_generic_types() res += f"<{key_type}, {value_type}>" res += f'({self.__size})' if self.__skip_children: res += ' = {...}' if svm_print_address.with_adr: res += SVMUtil.adr_str(self.__obj) trace(f' - to_string = {res}') return res def infer_generic_types(self) -> tuple: # (str, str): key_type: list = [] # list[gdb.Type] value_type: list = [] # list[gdb.Type] for i, kv in enumerate(self, 1): key, value = kv # if len(*_type) = 1 we could just infer the type java.lang.Object, ignore null values if not SVMUtil.is_null(key) and (len(key_type) == 0 or key_type[0] != SVMUtil.object_type): key_type = SVMUtil.find_shared_types(key_type, SVMUtil.get_rtt(key)) if not SVMUtil.is_null(value) and (len(value_type) == 0 or value_type[0] != SVMUtil.object_type): value_type = SVMUtil.find_shared_types(value_type, SVMUtil.get_rtt(value)) if (0 <= svm_infer_generics.value <= i) or (len(key_type) > 0 and key_type[0] == SVMUtil.object_type and len(value_type) > 0 and value_type[0] == SVMUtil.object_type): break key_type_name = '?' if len(key_type) == 0 else SVMUtil.get_unqualified_type_name(key_type[0].name) value_type_name = '?' if len(value_type) == 0 else SVMUtil.get_unqualified_type_name(value_type[0].name) return key_type_name, value_type_name def display_hint(self) -> str: trace(' - display_hint = map') return "map" 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') yield key, value obj = SVMUtil.get_obj_field(obj, 'next') def children(self) -> Iterable[object]: trace(f' - children({self.__obj.type} @ {hex(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) SVMUtil.current_print_depth -= 1 def make_high_level_object(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) except Exception as ex: trace(f' exception: {ex}') return SVMPPClass(obj) class SVMPrintParam(gdb.Parameter): """Use this command to enable/disable SVM pretty printing.""" set_doc = "Enable/Disable SVM pretty printer." show_doc = "Show if SVM pretty printer are enabled/disabled." def __init__(self, initial: bool = True): super().__init__('svm-print', gdb.COMMAND_DATA, gdb.PARAM_BOOLEAN) self.value = initial # default enabled def get_set_string(self): return SVMUtil.execout(f"{'enable' if self.value else 'disable'} pretty-printer .* {SVMUtil.pretty_printer_name}") class SVMPrintStringLimit(gdb.Parameter): """Use this command to limit the number of characters in a string shown during pretty printing. Does only limit java strings. To limit c strings use 'set print characters'.""" set_doc = "Set character limit for java strings." show_doc = "Show character limit for java strings." def __init__(self, initial: int = 200): super().__init__('svm-print-string-limit', gdb.COMMAND_DATA, gdb.PARAM_ZUINTEGER_UNLIMITED) self.value = initial class SVMPrintElementLimit(gdb.Parameter): """Use this command to limit the number of elements in an array/collection shown during SVM pretty printing. Does only limit java arrays and java some java collections. To limit other arrays use 'set print elements'. However, 'print elements' also limits the amount of elements for java arrays and java collections. If GDBs element limit is below the SVM element limit, printing will be limited by gdb.""" set_doc = "Set element limit for arrays and collections." show_doc = "Show element limit for array and collections." def __init__(self, initial: int = 10): super().__init__('svm-print-element-limit', gdb.COMMAND_DATA, gdb.PARAM_ZUINTEGER_UNLIMITED) self.value = initial def get_set_string(self): gdb_limit = gdb.parameter("print elements") if gdb_limit >= 0 and (self.value > gdb_limit or self.value == -1): return f"""The number of elements printed will be limited by GDBs 'print elements' which is {gdb_limit}. To increase this limit use 'set print elements '""" else: return "" class SVMPrintFieldLimit(gdb.Parameter): """Use this command to limit the number of fields in a java object shown during SVM pretty printing. Does only limit java objects. To limit other objects use 'set print elements'. However, 'print elements' also limits the amount of fields for java objects. If GDBs element limit is below the field limit, field printing will be limited by gdb.""" set_doc = "Set field limit for objects." show_doc = "Show field limit for objects." def __init__(self, initial: int = 50): super().__init__('svm-print-field-limit', gdb.COMMAND_DATA, gdb.PARAM_ZUINTEGER_UNLIMITED) self.value = initial def get_set_string(self): gdb_limit = gdb.parameter("print elements") if gdb_limit >= 0 and (self.value > gdb_limit or self.value == -1): return f"""The number of fields printed will be limited by GDBs 'print elements' which is {gdb_limit}. To increase this limit use 'set print elements '""" else: return "" class SVMPrintDepthLimit(gdb.Parameter): """Use this command to limit the depth at which objects are printed by the SVM pretty printer. Does only affect objects that are handled by the SVM pretty printer, similar to selfref checks. However, 'print max-depth' also limits the svm print depth. If GDBs max-depth limit is below the svm depth limit, printing will be limited by gdb.""" set_doc = "Set depth limit for svm objects." show_doc = "Show depth limit for svm objects." def __init__(self, initial: int = 1): super().__init__('svm-print-depth-limit', gdb.COMMAND_DATA, gdb.PARAM_ZUINTEGER_UNLIMITED) self.value = initial def get_set_string(self): gdb_limit = gdb.parameter("print max-depth") if gdb_limit >= 0 and (self.value > gdb_limit or self.value == -1): return f"""The print depth will be limited by GDBs 'print max-depth' which is {gdb_limit}. To increase this limit use 'set print max-depth '""" else: return "" class SVMUseHLRepParam(gdb.Parameter): """Use this command to enable/disable SVM high level representations. Supported high level representations: ArrayList, HashMap""" set_doc = "Enable/Disable pretty printing of high level representations." show_doc = "Show if SVM pretty printer are enabled/disabled." def __init__(self, initial: bool = True): super().__init__('svm-use-hlrep', gdb.COMMAND_DATA, gdb.PARAM_BOOLEAN) self.value = initial class SVMInferGenericsParam(gdb.Parameter): """Use this command to set the limit of elements used to infer types for collections with generic type parameters. (0 for no Inference, -1 for Inference over all elements)""" set_doc = "Set limit of elements used for inferring generic type parameters." show_doc = "Show limit of elements used for inferring generic type parameters." def __init__(self, initial: int = 10): super().__init__('svm-infer-generics', gdb.COMMAND_DATA, gdb.PARAM_ZUINTEGER_UNLIMITED) self.value = initial class SVMPrintAddressParam(gdb.Parameter): """Use this command to enable/disable additionally printing the addresses.""" set_doc = "Set additional printing of addresses." show_doc = "Show additional printing of addresses." def __init__(self, initial: str = 'disable'): super().__init__('svm-print-address', gdb.COMMAND_DATA, gdb.PARAM_ENUM, ['on', 'enable', 'absolute', 'disable', 'off']) self.with_adr = False self.absolute_adr = False self.value = initial self.set_flags() def set_flags(self): if self.value == 'disable' or self.value == 'off': self.with_adr = False elif self.value == 'absolute': self.absolute_adr = True self.with_adr = True else: self.absolute_adr = False self.with_adr = True def get_set_string(self): self.set_flags() return "" class SVMCheckSelfrefParam(gdb.Parameter): """Use this command to enable/disable cycle detection for pretty printing.""" set_doc = "Set selfref check." show_doc = "Show selfref check." def __init__(self, initial: bool = True): super().__init__('svm-selfref-check', gdb.COMMAND_DATA, gdb.PARAM_BOOLEAN) self.value = initial def get_set_string(self): # make sure selfrefs are cleared after changing this setting (to avoid unexpected behavior) SVMUtil.selfref_cycles.clear() return "" class SVMPrintStaticFieldsParam(gdb.Parameter): """Use this command to enable/disable printing of static field members.""" set_doc = "Set print static fields." show_doc = "Show print static fields." def __init__(self, initial: bool = False): super().__init__('svm-print-static-fields', gdb.COMMAND_DATA, gdb.PARAM_BOOLEAN) self.value = initial class SVMCompleteStaticVariablesParam(gdb.Parameter): """Use this command to enable/disable printing of static field members.""" set_doc = "Set complete static variables." show_doc = "Show complete static variables." def __init__(self, initial: bool = False): super().__init__('svm-complete-static-variables', gdb.COMMAND_DATA, gdb.PARAM_BOOLEAN) self.value = initial class SVMDebugTraceParam(gdb.Parameter): """Use this command to enable/disable debug tracing for gdb-debughelpers.py. Appends debug logs to the file 'gdb-debughelpers.trace.out' in the current working directory (creates the file if it does not exist).""" set_doc = "Set debug tracing." show_doc = "Show debug tracing." def __init__(self, initial: bool = False): super().__init__('svm-debug-tracing', gdb.COMMAND_SUPPORT, gdb.PARAM_BOOLEAN) self.value = initial self.tracefile = open('gdb-debughelpers.trace.out', 'ab', 0) if initial else None def get_set_string(self): if self.value and self.tracefile is None: self.tracefile = open('gdb-debughelpers.trace.out', 'ab', 0) elif not self.value and self.tracefile is not None: self.tracefile.close() self.tracefile = None return "" def load_param(name: str, param_class): try: return param_class(globals()[name].value) except (KeyError, AttributeError): return param_class() svm_print = load_param('svm_print', SVMPrintParam) svm_print_string_limit = load_param('svm_print_string_limit', SVMPrintStringLimit) svm_print_element_limit = load_param('svm_print_element_limit', SVMPrintElementLimit) svm_print_field_limit = load_param('svm_print_field_limit', SVMPrintFieldLimit) svm_print_depth_limit = load_param('svm_print_depth_limit', SVMPrintDepthLimit) svm_use_hlrep = load_param('svm_use_hlrep', SVMUseHLRepParam) svm_infer_generics = load_param('svm_infer_generics', SVMInferGenericsParam) svm_print_address = load_param('svm_print_address', SVMPrintAddressParam) svm_check_selfref = load_param('svm_check_selfref', SVMCheckSelfrefParam) svm_print_static_fields = load_param('svm_print_static_fields', SVMPrintStaticFieldsParam) svm_complete_static_variables = load_param('svm_complete_static_variables', SVMCompleteStaticVariablesParam) svm_debug_tracing = load_param('svm_debug_tracing', SVMDebugTraceParam) class SVMCommandDebugPrettyPrinting(gdb.Command): """Use this command to start debugging pretty printing.""" def __init__(self): super().__init__('pdb', gdb.COMMAND_DATA) def complete(self, text: str, word: str) -> int: # list[str] | int: return gdb.COMPLETE_EXPRESSION def invoke(self, arg: str, from_tty: bool) -> None: trace(f' - invoke({arg})') command = "gdb.execute('print {}')".format(arg.replace("'", "\\'")) import pdb pdb.run(command) SVMCommandDebugPrettyPrinting() class SVMCommandPrint(gdb.Command): """Use this command for printing with awareness for java values. This command shadows the alias 'p' for GDBs built-in print command if SVM pretty printing is enabled. If the expression contains a java value, it is evaluated as such, otherwise GDBs default print command is used""" class Token: def __init__(self, kind: str = "", val: str = "", start: int = 0, end: int = 0): self.kind = kind self.val = val self.start = start self.end = end class AutoComplete(RuntimeError): def __init__(self, complete): # complete: list[str] | int self.complete = complete cache = dict() scanner = None expr = "" t: Token = Token() la: Token = Token() sym: str = "" def __init__(self): super().__init__('p', gdb.COMMAND_DATA) @staticmethod def cast_to_rtt(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) 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) return obj, f"(('{rtt.name}' *)({obj_oop}))" # Define the token specifications token_specification = [ ('IDENT', r'\$?[a-zA-Z_][a-zA-Z0-9_]*'), # identifier (convenience variables may contain $) ('QIDENT', r"'\$?[a-zA-Z_][a-zA-Z0-9_.:]*'"), # quoted identifier ('FA', r'\.'), # field access ('LBRACK', r'\['), # opening bracket ('RBRACK', r'\]'), # closing bracket ('LPAREN', r'\('), # opening parentheses ('RPAREN', r'\)'), # closing parentheses ('COMMA', r','), # comma ('SKIP', r'\s+'), # skip over whitespaces ('OTHER', r'.'), # any other character, will be handled by gdb ] token_regex = '|'.join(f'(?P<{kind}>{regex})' for (kind, regex) in token_specification) def tokenize(self, expr: str) -> Iterable[Token]: for match in re.finditer(self.token_regex, expr): kind = match.lastgroup val = match.group() if kind == 'SKIP': # skip whitespaces continue yield self.Token(kind, val, match.start(), match.end()) def setup_scanner(self, expr: str) -> None: self.scanner = iter(self.tokenize(expr)) self.t = self.Token() self.la = self.Token() self.sym = "" self.expr = expr def scan(self): self.t = self.la self.la = next(self.scanner, self.Token()) self.sym = self.la.kind def check(self, expected: str): if self.sym == expected: self.scan() else: raise RuntimeError(f"{expected} expected after {self.expr[:self.t.end]} but got {self.sym}") def parse(self, completion: bool = False) -> str: self.scan() if self.sym == "" and completion: raise self.AutoComplete(gdb.COMPLETE_EXPRESSION) expr = self.expression(completion) self.check("") return expr def expression(self, completion: bool = False) -> str: expr = "" while self.sym != "": if self.sym == "IDENT": expr += self.object(completion) else: # ignore everything that does not start with an identifier self.scan() expr += self.t.val return expr def object(self, completion: bool = False) -> str: self.scan() if self.sym == "" and completion: raise self.AutoComplete(gdb.COMPLETE_EXPRESSION) obj_str = self.t.val if obj_str in self.cache: obj, obj_str = self.cache[obj_str] else: try: obj = gdb.parse_and_eval(obj_str) except gdb.error: # 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) self.cache[base_obj_str] = (obj, obj_str) while self.sym == "FA" or self.sym == "LPAREN" or self.sym == "LBRACK": if self.sym == "FA": self.scan() if not completion: self.check("IDENT") else: # handle auto-completion after field access fields = SVMUtil.get_all_fields(obj.type, svm_complete_static_variables.value) funcs = SVMUtil.get_all_member_functions(obj.type, svm_complete_static_variables.value, False) field_names = set(f.name for f in fields) func_names = set(f.name for f in funcs) complete_set = field_names.union(func_names) if self.sym == "": raise self.AutoComplete(list(complete_set)) self.check("IDENT") if self.sym == "": raise self.AutoComplete([c for c in complete_set if c.startswith(self.t.val)]) 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) self.cache[base_obj_str] = (obj, obj_str) elif self.sym == "LPAREN": if obj.type.code != gdb.TYPE_CODE_METHOD: raise RuntimeError(f"Method object expected at: {self.expr[:self.t.end]}") self.scan() param_str = self.params(completion) self.check("RPAREN") this, _, func_name = obj_str.rpartition('.') if this in self.cache: this_obj, this = self.cache[this] else: this_obj = gdb.parse_and_eval(this) if this_obj.type.code == gdb.TYPE_CODE_PTR: obj_str = f"{this}->{func_name}" 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) 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) # gdb.types.get_basic_type strips typedefs and other type modifiers is_pointer = gdb.types.get_basic_type(obj.type).code == gdb.TYPE_CODE_PTR if not (is_array or is_pointer): raise RuntimeError(f"Array object or pointer expected at: {self.expr[:self.t.end]}") self.scan() 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()): index = 0 if i_obj_str == '' else int(i_obj_str) length = SVMUtil.get_int_field(obj, 'len') complete = [] if index < length: complete.append(f'{index}]') if index + 1 < length: complete.append(f'{index + 1}]') if index + 2 < length: complete.append(f'{length - 1}]') raise self.AutoComplete(complete) else: raise self.AutoComplete(gdb.COMPLETE_EXPRESSION) if i_obj_str in self.cache: i_obj, i_obj_str = self.cache[i_obj_str] else: i_obj = gdb.parse_and_eval(i_obj_str) self.check('RBRACK') if is_array and SVMUtil.is_java_type(obj.type): obj_str += ".data" obj = SVMUtil.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') 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) self.cache[base_obj_str] = (obj, obj_str) else: # let gdb figure out what to do obj_str += f"[{i_obj_str}]" if obj_str in self.cache: obj, obj_str = self.cache[obj_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) self.cache[base_obj_str] = (obj, obj_str) if isinstance(gdb.default_visualizer(obj), SVMPPBoxedPrimitive): obj_str += ".value" return obj_str def params(self, completion: bool = False) -> str: param_str = "" while self.sym != "RPAREN" and self.sym != "": obj_str = "" while self.sym != "RPAREN" and self.sym != "COMMA" and self.sym != "": if self.sym == "IDENT": obj_str += self.object(completion) else: self.scan() 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): # uncompress compressed java params obj_str = f"(('{SVMUtil.get_uncompressed_type(SVMUtil.get_basic_type(obj.type)).name}' *)({adr(obj)}))" param_str += obj_str if self.sym == "COMMA": self.scan() param_str += self.t.val if self.sym == "" and completion: # handle autocompletion for params if self.t.kind == "LPAREN" or self.t.kind == "COMMA": # no open object access raise self.AutoComplete(gdb.COMPLETE_EXPRESSION) else: raise self.AutoComplete(gdb.COMPLETE_NONE) return param_str def array_index(self, completion: bool = False) -> str: i_obj_str = "" while self.sym != "RBRACK" and self.sym != "": if self.sym == "IDENT": i_obj_str += self.object(completion) else: self.scan() i_obj_str += self.t.val return i_obj_str def complete(self, text: str, word: str): # -> list[str] | int: if not svm_print.value: return gdb.COMPLETE_EXPRESSION self.setup_scanner(text) try: self.parse(completion=True) except self.AutoComplete as ac: trace(f" - complete({text}, {word}) -- autocomplete result: {ac.complete}") return ac.complete trace(f" - complete({text}, {word}) -- no completion possible") return gdb.COMPLETE_NONE def invoke(self, arg: str, from_tty: bool) -> None: if not svm_print.value: gdb.execute(f"print {arg}") return output_format = "" if arg.startswith('/'): output_format, _, arg = arg.partition(' ') self.setup_scanner(arg) expr = self.parse() trace(f" - invoke({arg}) -- parsed arg: {expr}") # handle print call as if it was a new prompt SVMUtil.prompt_hook() # let gdb evaluate the modified expression gdb.execute(f"print{output_format} {expr}", False, False) SVMCommandPrint() class SVMCommandBreak(gdb.Command): def __init__(self): super().__init__('b', gdb.COMMAND_BREAKPOINTS, gdb.COMPLETE_LOCATION) def invoke(self, arg: str, from_tty: bool) -> None: args = gdb.string_to_argv(arg) + [''] # add an empty arg to avoid IndexError if arg is empty # first argument can either be line number, symbol name, empty, or if condition # # -> :: to make breakpoints work with IntelliJ function name notation args[0] = args[0].replace('#', '::') # let gdb execute the full break command with the updated symbol name gdb.execute(f"break {''.join(args)}", False, False) trace(f" - invoke({arg}) -- invoked: break {''.join(args)}") SVMCommandBreak() class SVMFrameUnwinder(gdb.unwinder.Unwinder): AMD64_RBP = 6 AMD64_RSP = 7 AMD64_RIP = 16 def __init__(self): super().__init__('SubstrateVM FrameUnwinder') self.stack_type = gdb.lookup_type('long') self.deopt_frame_type = gdb.lookup_type('com.oracle.svm.core.deopt.DeoptimizedFrame') def __call__(self, pending_frame): 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 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'] # 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)) return unwind_info except Exception as e: print(e) # Fallback to default frame unwinding via debug_frame (dwarf) return None class SVMFrameFilter(): def __init__(self): self.name = "SubstrateVM FrameFilter" self.priority = 100 self.enabled = True 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) else: yield SVMFrame(frame) class SVMFrame(FrameDecorator): def function(self): frame = self.inferior_frame() if not frame.name(): return 'Unknown Frame at ' + hex(int(frame.read_register('sp'))) func_name = str(frame.name().split('(')[0]) if frame.type() == gdb.INLINE_FRAME: func_name = '<-- ' + func_name filename = self.filename() if filename: line = self.line() if line is None: line = 0 eclipse_filename = '(' + os.path.basename(filename) + ':' + str(line) + ')' else: eclipse_filename = '' return func_name + eclipse_filename class SVMFrameDeopt(SVMFrame): def function(self): return '[DEOPT FRAMES ...]' def frame_args(self): return None def frame_locals(self): return None try: svminitfile = os.path.expandvars('${SVMGDBINITFILE}') exec(open(svminitfile).read()) trace(f'successfully processed svminitfile: {svminitfile}') except Exception as e: trace(f'') 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 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) # 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) def free_objectfile(free_objfile_event): objfile = free_objfile_event.objfile if any(pp.name == SVMUtil.pretty_printer_name for pp in objfile.pretty_printers): SVMUtil.pretty_print_objfiles.add(objfile.filename) gdb.events.new_objfile.connect(new_objectfile) gdb.events.free_objfile.connect(free_objectfile) except Exception as e: print(f'')