php-src/scripts/gdb/php_gdb.py

1006 lines
33 KiB
Python

"""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 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 = '<anonymous>'
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"
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_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)
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 ('<anonymous>', 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<<i) > type_flags:
break
if (type_flags & (1<<i)) != 0:
name = self.flag_name(i)
if type_bits is not None:
name2 = self._flag_name_in(i, type_bits)
if name2 is not None:
if name is not None:
names.append('%s(%s)' % (name2, name))
else:
names.append(name2)
continue
if name is not None:
names.append(name)
else:
names.append('(1 << %d)' % (i))
if (flags & (1<<self.bit('GC_NOT_COLLECTABLE'))) == 0:
gc_color = (flags >> 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)