"""GDB support for PHP types This is auto-loaded by GDB if Python Auto-loading is enabled (the default), and the PHP binary is in the auto load safe path: # ~/.config/gdb/gdbinit (not ~/.gdbinit) add-auto-load-safe-path /path/to/php-src See https://sourceware.org/gdb/current/onlinedocs/gdb.html/Python-Auto_002dloading.html See https://sourceware.org/gdb/current/onlinedocs/gdb.html/Auto_002dloading-safe-path.html#Auto_002dloading-safe-path If needed, pretty printers can be by-passed by using the /r flag: (gdb) p /r any_variable Use |set print pretty| to enable multi-line printing and indentation: (gdb) set print pretty on Use |set print max-depth| to control the maximum print depth for nested structures: (gdb) set print max-depth 5 To interactively type Python for development of the printers, use (gdb) python foo = gdb.parse_and_eval('bar') to put the C value 'bar' in the current scope into a Python variable 'foo'. Then you can interact with that variable: (gdb) python print foo['impl_'] """ import gdb import gdb.printing import gdb.types import re import traceback import os pp_set = gdb.printing.RegexpCollectionPrettyPrinter("php") class ZendStringPrettyPrinter(gdb.printing.PrettyPrinter): "Print a zend_string" def __init__(self, val): self.val = val def to_string(self): return '((zend_string*) 0x%x) %s' % (self.val.address, format_zstr(self.val)) def children(self): for field in self.val.type.fields(): if field.name == 'val': yield ('val', format_zstr(self.val)) else: yield (field.name, format_nested(self.val[field.name])) pp_set.add_printer('zend_string', '^_zend_string$', ZendStringPrettyPrinter) class ZendObjectPrettyPrinter(gdb.printing.PrettyPrinter): "Print a zend_object" def __init__(self, val): self.val = val def to_string(self): return 'object(%s)' % format_zstr(self.val['ce']['name']) def children(self): for field in self.val.type.fields(): yield (field.name, format_nested(self.val[field.name])) pp_set.add_printer('zend_object', '^_zend_object$', ZendObjectPrettyPrinter) class ZendArrayPrettyPrinter(gdb.printing.PrettyPrinter): "Print a zend_array" def __init__(self, val): self.val = val def to_string(self): return 'array(%d)' % self.val['nNumOfElements'] def children(self): for field in self.val.type.fields(): if field.name is None: name = '' val = self.val[field] else: name = field.name val = self.val[field.name] yield (name, format_nested(val)) pp_set.add_printer('zend_array', '^_zend_array$', ZendArrayPrettyPrinter) class ZendTypePrettyPrinter(gdb.printing.PrettyPrinter): "Print a zend_type" def __init__(self, val): self.val = val def to_string(self): return '((zend_type*) 0x%x) %s' % (self.val.address, self.format_type(self.val)) def children(self): for field in self.val.type.fields(): yield (field.name, format_nested(self.val[field.name])) def format_type(self, t): type_mask = int(t['type_mask']) type_mask_size = t['type_mask'].type.sizeof * 8 separator = '|' parts = [] meta = [] for bit in range(0, type_mask_size): if type_mask & (1 << bit): type_name = ZendTypeBits.zendTypeName(bit) match type_name: case None: parts.append('(1<<%d)' % bit) case 'list': list = t['ptr'].cast(gdb.lookup_type('zend_type_list').pointer()) num_types = int(list['num_types']) types = list['types'].dereference().cast(gdb.lookup_type('zend_type').array(num_types)) for i in range(0, num_types): str = self.format_type(types[i]) if any((c in set('|&()')) for c in str): str = '(%s)' % str parts.append(str) case 'union' | 'arena': meta.append(type_name) case 'intersection': meta.append(type_name) separator = '&' case 'name': str = t['ptr'].cast(gdb.lookup_type('zend_string').pointer()) parts.append(format_zstr(str)) case _: parts.append(type_name) str = separator.join(parts) if len(meta) > 0: str = '[%s] %s' % (','.join(meta), str) return str pp_set.add_printer('zend_type', '^zend_type$', ZendTypePrettyPrinter) class ZendAstKindPrettyPrinter(gdb.printing.PrettyPrinter): "Print a zend_ast_kind" def __init__(self, val): self.val = val def to_string(self): return self.val.cast(gdb.lookup_type('enum _zend_ast_kind')) pp_set.add_printer('zend_ast_kind', '^zend_ast_kind$', ZendAstKindPrettyPrinter) class ZendAstPrettyPrinter(gdb.printing.PrettyPrinter): """ Print a zend_ast, or one of the specialized structures based on it: zend_ast_decl, zend_ast_list, zend_ast_op_array, zend_ast_zval, or zend_ast_znode """ def __init__(self, val): self.val = val def to_string(self): return '((%s*)0x%x)' % (str(self.cast().type), self.val.address) def children(self): val = self.cast() for field in val.type.fields(): if field.name == 'child': children = val[field.name] num_children = self.num_children() ptr_type = gdb.lookup_type('zend_ast').pointer().pointer() children = children.cast(ptr_type) for i in range(0, num_children): c = children[i] if int(c) != 0: c = c.dereference() yield ('child[%d]' % i, c) elif field.name == 'val': yield (field.name, ZvalPrettyPrinter(val[field.name]).to_string()) else: yield (field.name, format_nested(val[field.name])) def is_special(self): special_shift = 6 # ZEND_AST_SPECIAL_SHIFT kind = self.val['kind'] return (kind >> special_shift) & 1 def is_decl(self): return self.is_special() and int(self.val['kind']) >= enum_value('ZEND_AST_FUNC_DECL') def is_list(self): list_shift = 7 # ZEND_AST_IS_LIST_SHIFT kind = self.val['kind'] return (kind >> list_shift) & 1 def cast(self): kind = int(self.val['kind']) if kind == enum_value('ZEND_AST_ZVAL') or kind == enum_value('ZEND_AST_CONSTANT'): return self.val.cast(gdb.lookup_type('zend_ast_zval')) if kind == enum_value('ZEND_AST_OP_ARRAY'): return self.val.cast(gdb.lookup_type('zend_ast_op_array')) if kind == enum_value('ZEND_AST_CALLABLE_CONVERT'): return self.val.cast(gdb.lookup_type('zend_ast_fcc')) if kind == enum_value('ZEND_AST_ZNODE'): return self.val.cast(gdb.lookup_type('zend_ast_znode')) if self.is_decl(): return self.val.cast(gdb.lookup_type('zend_ast_decl')) if self.is_list(): return self.val.cast(gdb.lookup_type('zend_ast_list')) return self.val def num_children(self): if self.is_decl(): decl_type = gdb.lookup_type('zend_ast_decl') child_type = decl_type['child'].type return array_size(child_type) if self.is_special(): return 0 elif self.is_list(): return int(self.cast()['children']) else: num_children_shift = 8 # ZEND_AST_NUM_CHILDREN_SHIFT kind = self.val['kind'] return kind >> num_children_shift pp_set.add_printer('zend_ast', '^_zend_ast$', ZendAstPrettyPrinter) pp_set.add_printer('zend_ast_decl', '^_zend_ast_decl$', ZendAstPrettyPrinter) pp_set.add_printer('zend_ast_list', '^_zend_ast_list$', ZendAstPrettyPrinter) pp_set.add_printer('zend_ast_op_array', '^_zend_ast_op_array$', ZendAstPrettyPrinter) pp_set.add_printer('zend_ast_fcc', '^_zend_ast_fcc$', ZendAstPrettyPrinter) pp_set.add_printer('zend_ast_zval', '^_zend_ast_zval$', ZendAstPrettyPrinter) pp_set.add_printer('zend_ast_znode', '^_zend_ast_znode$', ZendAstPrettyPrinter) class ZvalPrettyPrinter(gdb.printing.PrettyPrinter): "Print a zval" def __init__(self, val): self.val = val def to_string(self): return '((zval*) 0x%x) %s' % (self.val.address, self.value_to_string()) def value_to_string(self): t = int(self.val['u1']['v']['type']) if t == ZendTypeBits.bit('undef'): return 'undef' elif t == ZendTypeBits.bit('null'): return 'null' elif t == ZendTypeBits.bit('false'): return 'false' elif t == ZendTypeBits.bit('true'): return 'true' elif t == ZendTypeBits.bit('long'): return str(self.val['value']['lval']) elif t == ZendTypeBits.bit('double'): return str(self.val['value']['dval']) elif t == ZendTypeBits.bit('string'): return format_zstr(self.val['value']['str']) elif t == ZendTypeBits.bit('array'): return 'array' elif t == ZendTypeBits.bit('object'): return 'object(%s)' % format_zstr(self.val['value']['obj']['ce']['name']) elif t == ZendTypeBits.bit('resource'): return 'resource' elif t == ZendTypeBits.bit('reference'): return 'reference' elif t == ZendTypeBits.bit('constant_ast'): return 'constant_ast' elif t == ZendTypeBits.bit('indirect'): value = self.val['value']['zv'] valuestr = ZvalPrettyPrinter(value).to_string() return 'indirect: ((zval*) 0x%x) %s' % (int(value), valuestr) elif t == ZendTypeBits.bit('ptr'): value = int(self.val['value']['ptr']) return 'ptr: ((void*) 0x%x)' % (value) elif t == ZendTypeBits.bit('alias_ptr'): value = int(self.val['value']['ptr']) return 'alias_ptr: ((void*) 0x%x)' % (value) else: return 'zval of type %d' % int(self.val['u1']['v']['type']) def children(self): for field in self.val.type.fields(): if field.name == 'value': value = self.val['value'] sub_field = 'ptr' t = int(self.val['u1']['v']['type']) if t == ZendTypeBits.bit('undef'): sub_field = 'lval' elif t == ZendTypeBits.bit('null'): sub_field = 'lval' elif t == ZendTypeBits.bit('false'): sub_field = 'lval' elif t == ZendTypeBits.bit('true'): sub_field = 'lval' elif t == ZendTypeBits.bit('long'): sub_field = 'lval' elif t == ZendTypeBits.bit('double'): sub_field = 'dval' elif t == ZendTypeBits.bit('string'): sub_field = 'str' elif t == ZendTypeBits.bit('array'): sub_field = 'arr' elif t == ZendTypeBits.bit('object'): sub_field = 'obj' elif t == ZendTypeBits.bit('resource'): sub_field = 'res' elif t == ZendTypeBits.bit('reference'): sub_field = 'ref' elif t == ZendTypeBits.bit('constant_ast'): sub_field = 'ast' elif t == ZendTypeBits.bit('indirect'): sub_field = 'zv' value = value[sub_field] if sub_field != 'ptr' and value.type.code == gdb.TYPE_CODE_PTR: value = value.dereference() yield ('%s.%s' % (field.name, sub_field), value) elif field.name == 'u2': yield ('u2', self.val[field.name]['extra']) else: yield (field.name, format_nested(self.val[field.name])) pp_set.add_printer('zval', '^_zval_struct$', ZvalPrettyPrinter) class ZendClassEntryPrettyPrinter(gdb.printing.PrettyPrinter): "Print a zend_class_entry" def __init__(self, val): self.val = val def to_string(self): return '((zend_class_entry*) 0x%x) %s' % (self.val.address, format_zstr(self.val['name'])) def children(self): for field in self.val.type.fields(): if field.name is not None: if field.name == 'ce_flags': flags = self.val[field.name] yield (field.name, '%d (%s)' % (flags, ZendAccFlags.format_ce_flags(flags))) else: yield (field.name, format_nested(self.val[field.name])) else: # Don't break on the union fields. Unfortunately, pretty # printers done in python cannot match the default formatting of # C anonymous fields, which omit the name entirely, see # binutils-gdb/gdb/cp-valprint.c#248 (as of commit # b6532accdd8e24329cc69bb58bc2883796008776) yield ('', self.val[field]) pp_set.add_printer('zend_class_entry', '^_zend_class_entry$', ZendClassEntryPrettyPrinter) class ZendClassConstantPrettyPrinter(gdb.printing.PrettyPrinter): "Print a zend_class_constant" def __init__(self, val): self.val = val def children(self): for field in self.val.type.fields(): if field.name == 'value': flags = self.val[field.name]['u2']['constant_flags'] yield ('value.u2.constant_flags', '%d (%s)' % (flags, ZendAccFlags.format_const_flags(flags))) yield (field.name, self.val[field.name]) else: yield (field.name, format_nested(self.val[field.name])) pp_set.add_printer('zend_class_constant', '^_zend_class_constant$', ZendClassConstantPrettyPrinter) class ZendPropertyInfoPrettyPrinter(gdb.printing.PrettyPrinter): "Print a zend_property_info" def __init__(self, val): self.val = val def children(self): for field in self.val.type.fields(): if field.name == 'flags': flags = self.val[field.name] yield ('flags', '%d (%s)' % (flags, ZendAccFlags.format_prop_flags(flags))) else: yield (field.name, format_nested(self.val[field.name])) pp_set.add_printer('zend_property_info', '^_zend_property_info$', ZendPropertyInfoPrettyPrinter) class ZendFunctionPrettyPrinter(gdb.printing.PrettyPrinter): "Print a zend_function" def __init__(self, val): self.val = val def to_string(self): match int(self.val['type']): case ZendFnTypes.ZEND_INTERNAL_FUNCTION: typestr = 'internal' case ZendFnTypes.ZEND_USER_FUNCTION: typestr = 'user' case ZendFnTypes.ZEND_EVAL_CODE: typestr = 'eval' case _: typestr = '???' if self.val['common']['function_name']: namestr = format_zstr(self.val['common']['function_name']) else: namestr = '{main}' if self.val['common']['scope']: str = '%s method %s::%s' % (typestr, format_zstr(self.val['common']['scope']['name']), namestr) else: str = '%s function %s' % (typestr, namestr) if int(self.val['type']) == ZendFnTypes.ZEND_USER_FUNCTION or int(self.val['type']) == ZendFnTypes.ZEND_EVAL_CODE: str = '%s %s:%d' % (str, format_zstr(self.val['op_array']['filename']), int(self.val['op_array']['line_start'])) return '((zend_function*) 0x%x) %s' % (self.val.address, str) def children(self): for field in self.val.type.fields(): if field.name == 'common' or field.name == 'type' or field.name == 'quick_arg_flags': # Redundant with op_array / internal_function continue elif field.name == 'op_array': if int(self.val['type']) == ZendFnTypes.ZEND_USER_FUNCTION or int(self.val['type']) == ZendFnTypes.ZEND_EVAL_CODE: yield (field.name, self.val[field.name]) elif field.name == 'internal_function': if int(self.val['type']) == ZendFnTypes.ZEND_INTERNAL_FUNCTION: yield (field.name, self.val[field.name]) else: yield (field.name, format_nested(self.val[field.name])) pp_set.add_printer('zend_function', '^_zend_function$', ZendFunctionPrettyPrinter) class ZendOpArrayPrettyPrinter(gdb.printing.PrettyPrinter): "Print a zend_op_array" def __init__(self, val): self.val = val def to_string(self): if self.val['function_name']: namestr = format_zstr(self.val['function_name']) else: namestr = '{main}' if self.val['scope']: str = 'method %s::%s' % (format_zstr(self.val['scope']['name']), namestr) else: str = 'function %s' % (namestr) str = '%s %s:%d' % (str, format_zstr(self.val['filename']), int(self.val['line_start'])) return '((zend_op_array*) 0x%x) %s' % (self.val.address, str) def children(self): for field in self.val.type.fields(): if field.name == 'fn_flags': value = self.val[field.name] yield (field.name, '%d (%s)' % (value, ZendAccFlags.format_fn_flags(value))) else: yield (field.name, format_nested(self.val[field.name])) pp_set.add_printer('zend_op_array', '^_zend_op_array$', ZendOpArrayPrettyPrinter) class ZendOpPrettyPrinter(gdb.printing.PrettyPrinter): "Print a zend_op" def __init__(self, val): self.val = val def children(self): for field in self.val.type.fields(): if field.name == 'opcode': opcode = int(self.val[field.name]) yield (field.name, '%d (%s)' % (opcode, ZendOpcodes.name(opcode))) else: yield (field.name, format_nested(self.val[field.name])) pp_set.add_printer('zend_op', '^_zend_op$', ZendOpPrettyPrinter) class ZendInternalFunctionPrettyPrinter(gdb.printing.PrettyPrinter): "Print a zend_internal_function" def __init__(self, val): self.val = val def to_string(self): if self.val['function_name']: namestr = format_zstr(self.val['function_name']) else: namestr = '???' if self.val['scope']: str = 'method %s::%s' % (format_zstr(self.val['scope']['name']), namestr) else: str = 'function %s' % (namestr) return '((zend_internal_function*) 0x%x) %s' % (self.val.address, str) def children(self): for field in self.val.type.fields(): if field.name == 'fn_flags': yield ('fn_flags', ('%d (%s)' % (self.val[field.name], ZendAccFlags.format_fn_flags(self.val[field.name])))) else: yield (field.name, format_nested(self.val[field.name])) pp_set.add_printer('zend_internal_function', '^_zend_internal_function$', ZendInternalFunctionPrettyPrinter) class ZendRefcountedHPrettyPrinter(gdb.printing.PrettyPrinter): "Print a zend_refcounted_h" def __init__(self, val): self.val = val def children(self): for field in self.val.type.fields(): if field.name == 'u': val = self.val[field.name] if val == None: val = self.val for subfield in val.type.fields(): if subfield.name == 'type_info': flags = int(val[subfield.name]) yield (('%s.%s' % (field.name, subfield.name)), '%d (%s)' % (flags, ZendRefTypeInfo.format(flags))) else: yield (('%s.%s' % (field.name, subfield.name)), format_nested(val[subfield.name])) else: yield (('%s' % field.name), format_nested(self.val[field.name])) pp_set.add_printer('zend_refcounted_h', '^_zend_refcounted_h$', ZendRefcountedHPrettyPrinter) class PrintAccFlagsCommand(gdb.Command): "Pretty print ACC flags" def __init__ (self, type): self.type = type name = 'print_%s_flags' % type super(PrintAccFlagsCommand, self).__init__(name, gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION) def invoke (self, arg, from_tty): arg = int(gdb.parse_and_eval(arg)) print(ZendAccFlags.format_flags(arg, self.type)) PrintAccFlagsCommand('fn') PrintAccFlagsCommand('ce') PrintAccFlagsCommand('prop') PrintAccFlagsCommand('const') class PrintOpcodeCommand(gdb.Command): "Pretty print opcode" def __init__ (self): super(PrintOpcodeCommand, self).__init__("print_opcode", gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION) def invoke (self, arg, from_tty): arg = int(gdb.parse_and_eval(arg)) print(ZendOpcodes.name(arg)) PrintOpcodeCommand() class PrintRefTypeInfoCommand(gdb.Command): "Pretty print zend_refcounted.gc.u.type_info" def __init__ (self): super(PrintRefTypeInfoCommand, self).__init__("print_ref_type_info", gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION) def invoke (self, arg, from_tty): arg = int(gdb.parse_and_eval(arg)) print(ZendRefTypeInfo.format(arg)) PrintRefTypeInfoCommand() class DumpOpArrayCommand(gdb.Command): "Dump an op_array" def __init__ (self): super(DumpOpArrayCommand, self).__init__("dump_op_array", gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION) def invoke (self, arg, from_tty): op_array = gdb.parse_and_eval(arg) if op_array.type.code != gdb.TYPE_CODE_PTR: print("Must pass a zend_op_array* (got a %s)" % op_array.type) return if str(gdb.types.get_basic_type(op_array.type.target())) != 'struct _zend_op_array': print("Must pass a zend_op_array* (got a %s)" % op_array.type) return if int(op_array) == 0: print("NULL") return gdb.execute("call zend_dump_op_array((zend_op_array*)0x%x, 0, 0, 0)" % (int(op_array))) return DumpOpArrayCommand() class ZendTypeBits: _bits = None @classmethod def zendTypeName(self, bit): self._load() for name in self._bits: if bit == self._bits[name]: return name @classmethod def zvalTypeName(self, bit): # Same as zendTypeName, but return the last matching one # e.g. 13 is IS_PTR, not IS_ITERABLE self._load() ret = None for name in self._bits: if bit == self._bits[name]: ret = name return ret @classmethod def bit(self, name): self._load() return self._bits.get(name) @classmethod def _load(self): if self._bits != None: return dirname = detect_source_dir() filename = os.path.join(dirname, 'zend_types.h') bits = {} with open(filename, 'r') as file: content = file.read() pattern = re.compile(r'#define _ZEND_TYPE_([^\s]+)_BIT\s+\(1u << (\d+)\)') matches = pattern.findall(content) for name, bit in matches: bits[name.lower()] = int(bit) pattern = re.compile(r'#define IS_([^\s]+)\s+(\d+)') matches = pattern.findall(content) for name, bit in matches: bits[name.lower()] = int(bit) self._bits = bits class ZendFnTypes: ZEND_INTERNAL_FUNCTION = 1 ZEND_USER_FUNCTION = 2 ZEND_EVAL_CODE = 4 class ZendAccFlag: ce = False fn = False prop = False const = False bit = 0 def __init__(self, ce, fn, prop, const, bit): self.ce = ce self.fn = fn self.prop = prop self.const = const self.bit = bit def applies_to(self, type): return getattr(self, type) class ZendAccFlags: _flags = None @classmethod def fn_flag_name(self, bit): self.flag_name(bit, 'fn') @classmethod def ce_flag_name(self, bit): self.flag_name(bit, 'ce') @classmethod def prop_flag_name(self, bit): self.flag_name(bit, 'prop') @classmethod def const_flag_name(self, bit): self.flag_name(bit, 'const') @classmethod def flag_name(self, bit, type): self._load() for name in self._flags: flag = self._flags[name] if flag.applies_to(type) and bit == flag.bit: return name @classmethod def flag_bit(self, name): self._load() return self._flags[name] @classmethod def format_flags(self, flags, type): flags = int(flags) names = [] for i in range(0, 31): if (flags & (1 << i)) != 0: name = self.flag_name(i, type) if name == None: names.append('(1 << %d)' % (i)) else: names.append(name) return ' | '.join(names) @classmethod def format_fn_flags(self, flags): return self.format_flags(flags, 'fn') @classmethod def format_ce_flags(self, flags): return self.format_flags(flags, 'ce') @classmethod def format_prop_flags(self, flags): return self.format_flags(flags, 'prop') @classmethod def format_const_flags(self, flags): return self.format_flags(flags, 'const') @classmethod def _load(self): if self._flags != None: return dirname = detect_source_dir() filename = os.path.join(dirname, 'zend_compile.h') flags = {} with open(filename, 'r') as file: content = file.read() pattern = re.compile(r'#define (ZEND_ACC_[^\s]+)\s+\(1U?\s+<<\s+(\d+)\)\s+/\*\s+(X?)\s+\|\s+(X?)\s+\|\s+(X?)\s+\|\s+(X?)\s+\*/') matches = pattern.findall(content) for name, bit, cls, func, prop, const in matches: flags[name] = ZendAccFlag(cls == 'X', func == 'X', prop == 'X', const == 'X', int(bit)) self._flags = flags class ZendOpcodes: _opcodes = None @classmethod def name(self, number): self._load() number = int(number) for name in self._opcodes: if number == self._opcodes[name]: return name @classmethod def number(self, name): self._load() return self._opcodes[name] @classmethod def _load(self): if self._opcodes != None: return dirname = detect_source_dir() filename = os.path.join(dirname, 'zend_vm_opcodes.h') opcodes = {} found_nop = False with open(filename, 'r') as file: content = file.read() pattern = re.compile(r'#define (ZEND_[^\s]+)\s+([0-9]+)') matches = pattern.findall(content) for name, number in matches: if not found_nop: if name == 'ZEND_NOP': found_nop = True else: continue if name == 'ZEND_VM_LAST_OPCODE': break opcodes[name] = int(number) self._opcodes = opcodes class ZendRefTypeInfo: _bits = None @classmethod def flag_name(self, bit): return self._flag_name_in(bit, self._bits) @classmethod def _flag_name_in(self, bit, bits): for name in bits: if bit == bits[name]: return name @classmethod def bit(self, name): return self._bits.get(name) @classmethod def format(self, flags): self._load() names = [] type = flags & self._type_mask type_name = ZendTypeBits.zvalTypeName(type) if type_name is not None: names.append('IS_%s' % type_name.upper()) bits = self._bits type_bits = None match type_name: case 'string': type_bits = self._str_bits case 'array': type_bits = self._array_bits case 'object': type_bits = self._obj_bits type_flags = flags & self._flags_mask for i in range(0, 31): if (1< type_flags: break if (type_flags & (1<> self._info_shift) & self._gc_color match gc_color: case self._gc_black: names.append('GC_BLACK') case self._gc_white: names.append('GC_WHITE') case self._gc_grey: names.append('GC_GREY') case self._gc_purple: names.append('GC_PURPLE') gc_address = (flags >> self._info_shift) & self._gc_address if gc_address != 0: names.append('GC_ADDRESS(%d)' % gc_address) else: info = flags & self._info_mask if info != 0: names.append('GC_INFO(%d)' % info) return ' | '.join(names) @classmethod def _load(self): if self._bits != None: return dirname = detect_source_dir() filename = os.path.join(dirname, 'zend_types.h') bits = {} str_bits = {} array_bits = {} obj_bits = {} with open(filename, 'r') as file: content = file.read() # GC_NOT_COLLECTABLE (1<<4) pattern = re.compile(r'#define (GC_[^\s]+)\s+\(\s*1\s*<<\s*([0-9]+)\s*\)') matches = pattern.findall(content) for name, bit in matches: bits[name] = int(bit) # GC_TYPE_MASK 0x0000000f # GC_INFO_SHIFT 10 pattern = re.compile(r'#define (GC_[^\s]+)\s+((0x)?[0-9a-f]+)') matches = pattern.findall(content) for name, bit, _ in matches: match name: case 'GC_TYPE_MASK': self._type_mask = int(bit, 0) case 'GC_FLAGS_MASK': self._flags_mask = int(bit, 0) case 'GC_INFO_MASK': self._info_mask = int(bit, 0) case 'GC_INFO_SHIFT': self._info_shift = int(bit, 0) case 'GC_FLAGS_SHIFT': self._flags_shift = int(bit, 0) # IS_STR_INTERNED GC_IMMUTABLE # IS_STR_PERMANENT (1<<8) pattern = re.compile(r'#define (IS_(STR|ARRAY|OBJ)_[^\s]+)\s+(\(\s*1\s*<<\s*([0-9]+)\s*\)|GC_[a-zA-Z_]+)') matches = pattern.findall(content) for name, type, val, bit in matches: if bit == '': bit = bits.get(val) if bit == None: continue match type: case 'STR': target = str_bits case 'ARRAY': target = array_bits case 'OBJ': target = obj_bits target[name] = int(bit) # Hard coded because these are not exposed in header files self._gc_address = 0x0fffff self._gc_color = 0x300000 self._gc_black = 0x000000 self._gc_white = 0x100000 self._gc_grey = 0x200000 self._gc_purple = 0x300000 self._bits = bits self._str_bits = str_bits self._array_bits = array_bits self._obj_bits = obj_bits def detect_source_dir(): (symbol,_) = gdb.lookup_symbol("zend_visibility_to_set_visibility") if symbol == None: raise Exception("Could not find zend_compile.h: symbol zend_visibility_to_set_visibility not found") filename = symbol.symtab.fullname() dirname = os.path.dirname(filename) return dirname def lookup_symbol(name): (symbol, _) = gdb.lookup_symbol(name) if symbol == None: raise Exception("Could not lookup symbol %s" % name) return symbol def enum_value(name): symbol = lookup_symbol(name) return int(symbol.value()) def array_size(ary_type): # array types have a single field whose type represents its range return ary_type.fields()[0].type.range()[1]+1 def format_zstr(zstr): len = int(zstr['len']) truncated = False if len > 200: len = 200 truncated = True ptr_type = gdb.lookup_type('char').pointer() ary_type = gdb.lookup_type('char').array(len) str = zstr['val'].cast(ptr_type).dereference().cast(ary_type) str = str.format_string() if truncated: str += ' (%d bytes total)' % int(zstr['len']) return str def format_nested(value): orig_value = value type = value.type # Null pointers if type.code == gdb.TYPE_CODE_PTR and int(value) == 0: return orig_value addr = orig_value.address while type.code == gdb.TYPE_CODE_PTR: addr = int(value) type = type.target() try: value = value.dereference() except: pass type = gdb.types.get_basic_type(type) if type.tag and re.match(r'^_zend_string$', type.tag): return format_zstr(value) elif type.tag and re.match(r'^_zend_class_entry$', type.tag): return '((zend_class_entry*)0x%x) %s' % (addr, format_zstr(value['name'])) elif type.tag and re.match(r'^_zend_array$', type.tag): return '((zend_array*)0x%x) array(%d)' % (addr, value['nNumOfElements']) return orig_value gdb.printing.register_pretty_printer(gdb, pp_set, replace=True)