Files
IMR6/libraries/linux/graalvm/lib/svm/debug/gdb-debughelpers.py
T

1918 lines
81 KiB
Python

#
# 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 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:
# class fields
pretty_printer_name = "SubstrateVM"
hub_field_name = "hub"
compressed_ref_prefix = '_z_.'
pretty_print_objfiles = set()
current_print_depth = 0
parents = dict()
selfref_cycles = set()
hlreps = dict()
# 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'<SVMUtil> - get_unqualified_type_name({qualified_type_name}) = {result}')
return result
@staticmethod
def get_symbol_adr(symbol: str) -> int:
trace(f'<SVMUtil> - get_symbol_adr({symbol})')
return gdb.parse_and_eval(symbol).address
@staticmethod
def execout(cmd: str) -> str:
trace(f'<SVMUtil> - execout({cmd})')
return gdb.execute(cmd, False, True)
@staticmethod
def get_basic_type(t: gdb.Type) -> gdb.Type:
trace(f'<SVMUtil> - get_basic_type({t})')
while t.code == gdb.TYPE_CODE_PTR:
t = t.target()
return t
# class methods
@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
# 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'<SVMUtil> - 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 is_primitive(cls, t: gdb.Type) -> bool:
result = cls.get_basic_type(t).is_scalar
trace(f'<SVMUtil> - is_primitive({t}) = {result}')
return result
@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 = 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'<SVMUtil> - get_all_member_function_names({t}) exception: {ex}')
return syms
# 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
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'<SVMUtil> - 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 <loader_name>::_z_.<type_name>
result = type_name.startswith(self.compressed_ref_prefix) or ('::' + self.compressed_ref_prefix) in type_name
trace(f'<SVMUtil> - 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 self.is_compressed(obj.type):
return int(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 = 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 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:
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
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(self.get_adr(obj))})'
trace(f'<SVMUtil> - adr_str({hex(self.get_adr(obj))}) = {result}')
return result
def is_selfref(self, obj: gdb.Value) -> bool:
result = (svm_check_selfref.value and
not SVMUtil.is_primitive(obj.type) and
self.get_adr(obj) in SVMUtil.selfref_cycles)
trace(f'<SVMUtil> - is_selfref({hex(self.get_adr(obj))}) = {result}')
return result
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 self.is_null(child)) or SVMUtil.is_primitive(child.type):
return child
child_adr = self.get_adr(child)
parent_adr = self.get_adr(parent)
trace(f'<SVMUtil> - add_selfref(parent={hex(parent_adr)}, child={hex(child_adr)})')
if svm_check_selfref.value and SVMUtil.is_reachable(child_adr, parent_adr):
trace(f' <add selfref {child_adr}>')
SVMUtil.selfref_cycles.add(child_adr)
else:
trace(f' <add {hex(child_adr)} --> {hex(parent_adr)}>')
if child_adr in SVMUtil.parents:
SVMUtil.parents[child_adr].append(parent_adr)
else:
SVMUtil.parents[child_adr] = [parent_adr]
return child
def get_java_string(self, obj: gdb.Value, gdb_output_string: bool = False) -> str:
if self.is_null(obj):
return ""
trace(f'<SVMUtil> - 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
else:
trace(f'<SVMUtil> - 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 = self.get_obj_field(obj, 'value')
if self.is_null(value):
return ""
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()
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'<SVMUtil> - get_java_string({hex(self.get_adr(obj))}) = {result}')
return result
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 self.null if default is None else default
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
def get_classloader_namespace(self, obj: gdb.Value) -> str:
try:
hub = self.get_hub_field(obj)
if self.is_null(hub):
return ""
hub_companion = self.get_obj_field(hub, 'companion')
if self.is_null(hub_companion):
return ""
loader = self.get_obj_field(hub_companion, 'classLoader')
if self.is_null(loader):
return ""
loader = self.cast_to(loader, self.classloader_type)
loader_name = self.get_obj_field(loader, 'nameAndId')
if self.is_null(loader_name):
return ""
loader_namespace = self.get_java_string(loader_name)
trace(f'<SVMUtil> - 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 = 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 ""
def get_rtt(self, obj: gdb.Value) -> gdb.Type:
static_type = SVMUtil.get_basic_type(obj.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 self.get_uncompressed_type(SVMUtil.get_basic_type(obj.type)).code == gdb.TYPE_CODE_UNION:
obj = self.cast_to(obj, self.object_type)
hub = self.get_hub_field(obj)
if self.is_null(hub):
return static_type
name_field = self.get_obj_field(hub, 'name')
if self.is_null(name_field):
return static_type
rtt_name = self.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 = self.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 self.is_compressed(obj.type) and not self.is_compressed(rtt):
rtt = self.get_compressed_type(rtt)
trace(f'<SVMUtil> - get_rtt({hex(self.get_adr(obj))}) = {rtt_name}')
return rtt
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 self.is_compressed(t):
obj_oop = self.get_compressed_oop(obj)
else:
obj_oop = self.get_adr(obj)
trace(f'<SVMUtil> - cast_to({hex(self.get_adr(obj))}, {t})')
if t.code != gdb.TYPE_CODE_PTR:
t = t.pointer()
trace(f'<SVMUtil> - 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)
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'<SVMUtil> - get_uncompressed_type({t}) = {result}')
return result
def is_primitive_wrapper(self, t: gdb.Type) -> bool:
result = t in self.wrapper_types
trace(f'<SVMUtil> - is_primitive_wrapper({t}) = {result}')
return result
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)
def find_shared_types(self, type_list: list, t: gdb.Type) -> list: # list[gdb.Type]
if len(type_list) == 0:
# fill type list -> java.lang.Object will be last element
while t != self.object_type:
type_list += [t]
t = self.get_base_class(t)
return type_list
else:
# 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 = self.get_base_class(t)
# if nothing matches return the java.lang.Object
return [self.object_type]
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
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'<SVMUtil> - 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'<SVMUtil> - could not find compressed type "{type_name}" using uncompressed type')
except gdb.error as ex:
trace(ex)
result_type = t
trace(f'<SVMUtil> - get_compressed_type({t}) = {t.name}')
return result_type
class SVMPPString:
def __init__(self, svm_util: SVMUtil, obj: gdb.Value, java: bool = True):
trace(f'<SVMPPString> - __init__({hex(svm_util.get_adr(obj))})')
self.__obj = obj
self.__java = java
self.__svm_util = svm_util
def to_string(self) -> str:
trace('<SVMPPString> - to_string')
if self.__java:
try:
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 += self.__svm_util.adr_str(self.__obj)
trace(f'<SVMPPString> - to_string = {value}')
return value
class SVMPPArray:
def __init__(self, svm_util: SVMUtil, obj: gdb.Value, java_array: bool = True):
trace(f'<SVMPPArray> - __init__(obj={obj.type} @ {hex(svm_util.get_adr(obj))}, java_array={java_array})')
if java_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 = 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('<SVMPPArray> - display_hint = array')
return 'array'
def to_string(self) -> str:
trace('<SVMPPArray> - to_string')
if self.__java_array:
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 += self.__svm_util.adr_str(self.__obj)
trace(f'<SVMPPArray> - to_string = {value}')
return value
def __iter__(self):
trace('<SVMPPArray> - __iter__')
if self.__array is not None:
for i in range(self.__length):
yield self.__array[i]
def children(self) -> Iterable[object]:
trace('<SVMPPArray> - 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'<SVMPPArray> - children[{index}]')
yield str(index), self.__svm_util.add_selfref(self.__obj, elem)
SVMUtil.current_print_depth -= 1
class SVMPPClass:
def __init__(self, svm_util: SVMUtil, obj: gdb.Value, java_class: bool = True):
trace(f'<SVMPPClass> - __init__({obj.type} @ {hex(svm_util.get_adr(obj))})')
self.__obj = obj
self.__java_class = java_class
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'<SVMPPClass> - __getitem__({key})')
item = self.__svm_util.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('<SVMPPClass> - to_string')
try:
if self.__java_class:
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 += self.__svm_util.adr_str(self.__obj)
trace(f'<SVMPPClass> - to_string = {result}')
return result
except gdb.error as ex:
trace(f"<SVMPPClass> - to_string error - SVMPPClass: {ex}")
return 'object'
def children(self) -> Iterable[object]:
trace('<SVMPPClass> - 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 != self.__svm_util.object_header_type]
for index, f in enumerate(fields):
trace(f'<SVMPPClass> - 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, self.__svm_util.add_selfref(self.__obj, self.__obj[f])
SVMUtil.current_print_depth -= 1
class SVMPPEnum:
def __init__(self, svm_util: SVMUtil, obj: gdb.Value):
trace(f'<SVMPPEnum> - __init__({hex(svm_util.get_adr(obj))})')
self.__obj = obj
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 = self.__svm_util.get_java_string(self.__name) + f"({self.__ordinal})"
if svm_print_address.with_adr:
result += self.__svm_util.adr_str(self.__obj)
trace(f'<SVMPPEnum> - to_string = {result}')
return result
class SVMPPBoxedPrimitive:
def __init__(self, svm_util: SVMUtil, obj: gdb.Value):
trace(f'<SVMPPBoxedPrimitive> - __init__({obj.type} @ {hex(svm_util.get_adr(obj))})')
self.__obj = obj
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 += self.__svm_util.adr_str(self.__obj)
trace(f'<SVMPPBoxedPrimitive> - to_string = {result}')
return result
class SVMPPConst:
def __init__(self, val: str):
trace('<SVMPPConst> - __init__')
self.__val = val
def to_string(self) -> str:
result = "null" if self.__val is None else self.__val
trace(f'<SVMPPConst> - to_string = {result}')
return result
class SVMPrettyPrinter(gdb.printing.PrettyPrinter):
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'<SVMPrettyPrinter> - __call__({obj.type} @ {hex(self.svm_util.get_adr(obj))})')
if not SVMUtil.is_primitive(obj.type) and self.svm_util.is_java_type(obj.type):
# Filter out references to the null literal
if self.svm_util.is_null(obj):
return SVMPPConst(None)
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 self.svm_util.is_primitive_wrapper(uncompressed_rtt):
return SVMPPBoxedPrimitive(self.svm_util, obj)
# filter for strings
if uncompressed_rtt == self.string_type:
return SVMPPString(self.svm_util, obj)
# filter for arrays
if uncompressed_rtt.name.endswith("[]"):
return SVMPPArray(self.svm_util, obj)
# filter for enum values
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(self.svm_util, obj, uncompressed_rtt.name)
else:
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 self.svm_util.is_null(obj):
return SVMPPConst(None)
return self.__call__(obj.dereference())
elif obj.type.code == gdb.TYPE_CODE_ARRAY:
return SVMPPArray(self.svm_util, 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:
return None
elif obj.type.code == gdb.TYPE_CODE_STRUCT:
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)))
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 EspressoSymbol:
target_type = 'com.oracle.svm.espresso.classfile.descriptors.Symbol'
def __init__(self, svm_util: SVMUtil, obj: gdb.Value):
trace(f'<EspressoSymbol> - __init__({obj.type} @ {hex(svm_util.get_adr(obj))})')
value = svm_util.get_obj_field(obj, 'value')
self.__length = svm_util.get_int_field(value, 'len')
self.__array = svm_util.get_obj_field(value, 'data', None)
def to_string(self) -> str:
trace('<EspressoSymbol> - to_string')
byte_list = [self.__array[i] for i in range(self.__length)]
return f'EspressoSymbol({str(bytes(byte_list))})'
@HLRep
class ArrayList:
target_type = 'java.util.ArrayList'
def __init__(self, svm_util: SVMUtil, obj: gdb.Value):
trace(f'<ArrayList> - __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 = 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 = 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('<ArrayList> - 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 += self.__svm_util.adr_str(self.__obj)
trace(f'<ArrayList> - 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 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)
def display_hint(self) -> str:
trace('<ArrayList> - display_hint = array')
return 'array'
def __iter__(self) -> gdb.Value:
trace('<ArrayList> - __iter__')
if self.__data is not None:
for i in range(self.__size):
yield self.__data[i]
def children(self) -> Iterable[object]:
trace(f'<ArrayList> - 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'<ArrayList> - 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
@HLRep
class HashMap:
target_type = 'java.util.HashMap'
def __init__(self, svm_util: SVMUtil, obj: gdb.Value):
trace(f'<HashMap> - __init__({obj.type} @ {hex(svm_util.get_adr(obj))})')
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 = 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 = svm_util.get_int_field(table, '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('<HashMap> - 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 += self.__svm_util.adr_str(self.__obj)
trace(f'<HashMap> - 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('<HashMap> - display_hint = map')
return "map"
def __iter__(self) -> tuple: # (gdb.Value, gdb.Value):
trace('<HashMap> - __iter__')
for i in range(self.__table_len):
obj = self.__data[i]
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 = self.__svm_util.get_obj_field(obj, 'next')
def children(self) -> Iterable[object]:
trace(f'<HashMap> - 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'<HashMap> - 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
@HLRep
class EconomicMapImpl:
target_type = 'org.graalvm.collections.EconomicMapImpl'
def __init__(self, svm_util: SVMUtil, obj: gdb.Value):
trace(f'<EconomicMapImpl> - __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('<EconomicMapImpl> - 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'<EconomicMapImpl> - 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('<EconomicMapImpl> - display_hint = map')
return "map"
def __iter__(self) -> tuple: # (gdb.Value, gdb.Value):
trace('<EconomicMapImpl> - __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'<EconomicMapImpl> - 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'<EconomicMapImpl> - 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(svm_util, obj)
except Exception as ex:
trace(f'<makeHighLevelObject> exception: {ex}')
return SVMPPClass(svm_util, 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 <limit>'"""
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 <limit>'"""
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 <limit>'"""
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'<SVMCommandDebugPrettyPrinting> - 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)
self.svm_util = SVMUtil()
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 = 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 = 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
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.svm_util = SVMUtil()
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 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":
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 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:
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 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)
# 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 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 = self.svm_util.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 self.svm_util.is_java_type(obj.type):
obj_str += ".data"
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 = 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 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
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 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):
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 self.svm_util.is_java_type(obj.type) and self.svm_util.is_compressed(obj.type):
# uncompress compressed java params
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()
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"<SVMCommandPrint> - complete({text}, {word}) -- autocomplete result: {ac.complete}")
return ac.complete
trace(f"<SVMCommandPrint> - 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"<SVMCommandPrint> - 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"<SVMCommandBreak> - invoke({arg}) -- invoked: break {''.join(args)}")
SVMCommandBreak()
class SVMFrameUnwinder(gdb.unwinder.Unwinder):
def __init__(self, svm_util: SVMUtil):
super().__init__('SubstrateVM FrameUnwinder')
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.PendingFrame):
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:
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
caller_sp = sp + int(source_frame_size)
# try to fetch return address directly from stack
caller_pc = gdb.Value(caller_sp - 8).cast(self.svm_util.stack_type.pointer()).dereference()
# Build the unwind info
unwind_info = pending_frame.create_unwind_info(gdb.unwinder.FrameId(caller_sp, caller_pc))
unwind_info.add_saved_register('sp', gdb.Value(caller_sp))
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:
# We only need the original pc for lazy deoptimization -> unwind to original pc with same sp
# Since this is lazy deoptimization we can still use the debug frame info at the current sp
caller_pc = sp.cast(self.svm_util.stack_type.pointer()).dereference()
# build the unwind info
unwind_info = pending_frame.create_unwind_info(gdb.unwinder.FrameId(sp, pc))
unwind_info.add_saved_register('sp', gdb.Value(sp))
unwind_info.add_saved_register('pc', gdb.Value(caller_pc))
return unwind_info
except Exception as ex:
trace(f'<SVMFrameUnwinder> - 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, 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()
lazy_deopt = False
for frame in frame_iter:
frame = frame.inferior_frame()
pc = int(frame.pc())
if pc == self.eager_deopt_stub_adr:
yield SVMFrameEagerDeopt(frame, self.svm_util)
elif pc == self.lazy_deopt_stub_primitive_adr or pc == self.lazy_deopt_stub_object_adr:
lazy_deopt = True
continue # the next frame is the one with the corrected pc
else:
yield SVMFrame(frame, lazy_deopt)
lazy_deopt = False
class SVMFrame(FrameDecorator):
def __init__(self, frame: gdb.Frame, lazy_deopt: bool):
super().__init__(frame)
self.__lazy_deopt = lazy_deopt
def function(self) -> str:
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 = ''
sal = frame.find_sal()
objfile_filename = ''
if sal and sal.symtab and sal.symtab.objfile:
objfile = sal.symtab.objfile
if objfile.owner:
# avoid showing the '.debug' file
objfile = objfile.owner
objfile_filename = ' in ' + os.path.basename(objfile.filename)
prefix = '[LAZY DEOPT FRAME] ' if self.__lazy_deopt else ''
return prefix + func_name + eclipse_filename + objfile_filename
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(FrameDecorator):
def __init__(self, frame: gdb.Frame, svm_util: SVMUtil):
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):
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
try:
svminitfile = os.path.expandvars('${SVMGDBINITFILE}')
exec(open(svminitfile).read())
trace(f'successfully processed svminitfile: {svminitfile}')
except Exception as e:
trace(f'<exception in svminitfile execution: {e}>')
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"):
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 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:
register_objfile(objfile)
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'<exception in gdb-debughelpers initialization: {e}>')