mirror of
https://github.com/php/php-src.git
synced 2025-08-15 13:38:49 +02:00
GDB script improvements (#17227)
* Support IS_INDIRECT, IS_PTR, IS_ALIAS_PTR when printing zvals * Pretty print the ce_flags field when pretty printing a zend_class_entry * Pretty print the const flags when pretty printing a zend_class_constant * Added a minimal zend_property_info pretty printer to pretty print the flags field * Added minimal zend_function, zend_op_array, and zend_internal_function pretty printers: The string value is the function/method name, function type (user/internal), and location (when possible). The fn_flags field is pretty printed * Added a minimal zend_op pretty printer to pretty print the opcode * Added a zend_refcounted_h pretty printer to pretty print the type_info * Added minimal pretty printers for zend_object and zend_array * Print the type and address of pretty-printed structs * Added gdb commands: print_fn_flags, print_ce_flags, print_prop_flags, print_const_flags, print_ref_type_info, print_opcode, dump_op_array * Fields of type zend_string, zend_class_entry, zend_array are printed by default in all our pretty printers, using short representation * Increased the maximum length of printed strings to 200 chars before truncation (was 50)
This commit is contained in:
parent
fa7c67d622
commit
9df1c930bf
3 changed files with 1462 additions and 242 deletions
|
@ -40,7 +40,7 @@ $ccode = sprintf(
|
|||
basename(__FILE__),
|
||||
implode("\n ", array_map(function ($line) {
|
||||
$escapedPy = addcslashes($line."\n", "\"\n\\");
|
||||
$escapedAsm = addcslashes(sprintf(".ascii \"%s\"\n", $escapedPy), "\"\n\\");
|
||||
$escapedAsm = addcslashes(sprintf(".ascii \"%s\"\n", $escapedPy), "\"\n\\?");
|
||||
return sprintf('"%s"', $escapedAsm);
|
||||
}, explode("\n", $pyscript))),
|
||||
);
|
||||
|
|
|
@ -18,7 +18,7 @@ Use |set print pretty| to enable multi-line printing and indentation:
|
|||
|
||||
Use |set print max-depth| to control the maximum print depth for nested
|
||||
structures:
|
||||
(gdb) set print pretty on
|
||||
(gdb) set print max-depth 5
|
||||
|
||||
To interactively type Python for development of the printers, use
|
||||
(gdb) python foo = gdb.parse_and_eval('bar')
|
||||
|
@ -29,6 +29,8 @@ Then you can interact with that variable:
|
|||
|
||||
import gdb
|
||||
import re
|
||||
import traceback
|
||||
import os
|
||||
|
||||
pp_set = gdb.printing.RegexpCollectionPrettyPrinter("php")
|
||||
|
||||
|
@ -39,53 +41,65 @@ class ZendStringPrettyPrinter(gdb.printing.PrettyPrinter):
|
|||
self.val = val
|
||||
|
||||
def to_string(self):
|
||||
return self.format_string()
|
||||
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', self.format_string())
|
||||
yield ('val', format_zstr(self.val))
|
||||
else:
|
||||
yield (field.name, self.val[field.name])
|
||||
|
||||
def format_string(self):
|
||||
len = int(self.val['len'])
|
||||
truncated = False
|
||||
if len > 50:
|
||||
len = 50
|
||||
truncated = True
|
||||
|
||||
ptr_type = gdb.lookup_type('char').pointer()
|
||||
ary_type = gdb.lookup_type('char').array(len)
|
||||
str = self.val['val'].cast(ptr_type).dereference().cast(ary_type)
|
||||
str = str.format_string()
|
||||
if truncated:
|
||||
str += ' (%d bytes total)' % int(self.val['len'])
|
||||
|
||||
return str
|
||||
|
||||
yield (field.name, format_nested(self.val[field.name]))
|
||||
|
||||
pp_set.add_printer('zend_string', '^_zend_string$', ZendStringPrettyPrinter)
|
||||
|
||||
def zendStringPointerPrinter(ptr):
|
||||
"Given a pointer to a zend_string, show the contents (if non-NULL)"
|
||||
if int(ptr) == 0:
|
||||
return '0x0'
|
||||
return ZendStringPrettyPrinter(ptr.dereference()).to_string()
|
||||
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
|
||||
load_type_bits()
|
||||
|
||||
def to_string(self):
|
||||
return self.format_type(self.val)
|
||||
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, self.val[field.name])
|
||||
yield (field.name, format_nested(self.val[field.name]))
|
||||
|
||||
def format_type(self, t):
|
||||
type_mask = int(t['type_mask'])
|
||||
|
@ -95,7 +109,7 @@ class ZendTypePrettyPrinter(gdb.printing.PrettyPrinter):
|
|||
meta = []
|
||||
for bit in range(0, type_mask_size):
|
||||
if type_mask & (1 << bit):
|
||||
type_name = type_bit_to_name.get(bit)
|
||||
type_name = ZendTypeBits.zendTypeName(bit)
|
||||
match type_name:
|
||||
case None:
|
||||
parts.append('(1<<%d)' % bit)
|
||||
|
@ -115,7 +129,7 @@ class ZendTypePrettyPrinter(gdb.printing.PrettyPrinter):
|
|||
separator = '&'
|
||||
case 'name':
|
||||
str = t['ptr'].cast(gdb.lookup_type('zend_string').pointer())
|
||||
parts.append(ZendStringPrettyPrinter(str).to_string())
|
||||
parts.append(format_zstr(str))
|
||||
case _:
|
||||
parts.append(type_name)
|
||||
|
||||
|
@ -166,11 +180,11 @@ class ZendAstPrettyPrinter(gdb.printing.PrettyPrinter):
|
|||
c = c.dereference()
|
||||
yield ('child[%d]' % i, c)
|
||||
elif field.name == 'name':
|
||||
yield (field.name, ZendStringPrettyPrinter(val[field.name].dereference()).to_string())
|
||||
yield (field.name, format_zstr(val[field.name]))
|
||||
elif field.name == 'val':
|
||||
yield (field.name, ZvalPrettyPrinter(val[field.name]).to_string())
|
||||
else:
|
||||
yield (field.name, val[field.name])
|
||||
yield (field.name, format_nested(self.val[field.name]))
|
||||
|
||||
def is_special(self):
|
||||
special_shift = 6 # ZEND_AST_SPECIAL_SHIFT
|
||||
|
@ -223,37 +237,46 @@ class ZvalPrettyPrinter(gdb.printing.PrettyPrinter):
|
|||
|
||||
def __init__(self, val):
|
||||
self.val = val
|
||||
load_type_bits()
|
||||
|
||||
def to_string(self):
|
||||
return self.value_to_string()
|
||||
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 == type_name_to_bit['undef']:
|
||||
if t == ZendTypeBits.bit('undef'):
|
||||
return 'undef'
|
||||
elif t == type_name_to_bit['null']:
|
||||
elif t == ZendTypeBits.bit('null'):
|
||||
return 'null'
|
||||
elif t == type_name_to_bit['false']:
|
||||
elif t == ZendTypeBits.bit('false'):
|
||||
return 'false'
|
||||
elif t == type_name_to_bit['true']:
|
||||
elif t == ZendTypeBits.bit('true'):
|
||||
return 'true'
|
||||
elif t == type_name_to_bit['long']:
|
||||
elif t == ZendTypeBits.bit('long'):
|
||||
return str(self.val['value']['lval'])
|
||||
elif t == type_name_to_bit['double']:
|
||||
elif t == ZendTypeBits.bit('double'):
|
||||
return str(self.val['value']['dval'])
|
||||
elif t == type_name_to_bit['string']:
|
||||
return ZendStringPrettyPrinter(self.val['value']['str'].dereference()).to_string()
|
||||
elif t == type_name_to_bit['array']:
|
||||
elif t == ZendTypeBits.bit('string'):
|
||||
return format_zstr(self.val['value']['str'])
|
||||
elif t == ZendTypeBits.bit('array'):
|
||||
return 'array'
|
||||
elif t == type_name_to_bit['object']:
|
||||
return 'object(%s)' % ZendStringPrettyPrinter(self.val['value']['obj']['ce']['name'].dereference()).to_string()
|
||||
elif t == type_name_to_bit['resource']:
|
||||
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 == type_name_to_bit['reference']:
|
||||
elif t == ZendTypeBits.bit('reference'):
|
||||
return 'reference'
|
||||
elif t == type_name_to_bit['constant_ast']:
|
||||
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'])
|
||||
|
||||
|
@ -261,38 +284,42 @@ class ZvalPrettyPrinter(gdb.printing.PrettyPrinter):
|
|||
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 == type_name_to_bit['undef']:
|
||||
value = value['lval']
|
||||
elif t == type_name_to_bit['null']:
|
||||
value = value['lval']
|
||||
elif t == type_name_to_bit['false']:
|
||||
value = value['lval']
|
||||
elif t == type_name_to_bit['true']:
|
||||
value = value['lval']
|
||||
elif t == type_name_to_bit['long']:
|
||||
value = value['lval']
|
||||
elif t == type_name_to_bit['double']:
|
||||
value = value['dval']
|
||||
elif t == type_name_to_bit['string']:
|
||||
value = value['str'].dereference()
|
||||
elif t == type_name_to_bit['array']:
|
||||
value = value['ht'].dereference()
|
||||
elif t == type_name_to_bit['object']:
|
||||
value = value['obj'].dereference()
|
||||
elif t == type_name_to_bit['resource']:
|
||||
value = value['res'].dereference()
|
||||
elif t == type_name_to_bit['reference']:
|
||||
value = value['ref'].dereference()
|
||||
elif t == type_name_to_bit['constant_ast']:
|
||||
value = value['ast'].dereference()
|
||||
else:
|
||||
value = value['ptr']
|
||||
yield (field.name, value)
|
||||
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, self.val[field.name])
|
||||
yield (field.name, format_nested(self.val[field.name]))
|
||||
|
||||
|
||||
pp_set.add_printer('zval', '^_zval_struct$', ZvalPrettyPrinter)
|
||||
|
@ -300,22 +327,20 @@ pp_set.add_printer('zval', '^_zval_struct$', ZvalPrettyPrinter)
|
|||
class ZendClassEntryPrettyPrinter(gdb.printing.PrettyPrinter):
|
||||
"Print a zend_class_entry"
|
||||
|
||||
# String pointers, show the string contents if possible
|
||||
STRING_FIELDS = [ 'name', 'doc_comment' ]
|
||||
|
||||
def __init__(self, val):
|
||||
self.val = val
|
||||
|
||||
def to_string(self):
|
||||
return zendStringPointerPrinter(self.val['name'])
|
||||
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 in self.STRING_FIELDS:
|
||||
yield (field.name, zendStringPointerPrinter(self.val[field.name]))
|
||||
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, self.val[field.name])
|
||||
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
|
||||
|
@ -334,52 +359,592 @@ class ZendClassConstantPrettyPrinter(gdb.printing.PrettyPrinter):
|
|||
|
||||
def children(self):
|
||||
for field in self.val.type.fields():
|
||||
if field.name == 'doc_comment':
|
||||
yield ('doc_comment', zendStringPointerPrinter(self.val['doc_comment']))
|
||||
elif field.name == 'ce':
|
||||
yield ('ce', zendStringPointerPrinter(self.val['ce']['name']))
|
||||
else:
|
||||
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)
|
||||
|
||||
type_bit_to_name = None
|
||||
type_name_to_bit = None
|
||||
class ZendPropertyInfoPrettyPrinter(gdb.printing.PrettyPrinter):
|
||||
"Print a zend_property_info"
|
||||
|
||||
def load_type_bits():
|
||||
global type_bit_to_name
|
||||
global type_name_to_bit
|
||||
def __init__(self, val):
|
||||
self.val = val
|
||||
|
||||
if type_bit_to_name != None:
|
||||
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
|
||||
|
||||
(symbol,_) = gdb.lookup_symbol("zend_gc_refcount")
|
||||
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_types.h: symbol zend_gc_refcount not found")
|
||||
raise Exception("Could not find zend_compile.h: symbol zend_visibility_to_set_visibility not found")
|
||||
filename = symbol.symtab.fullname()
|
||||
|
||||
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[int(bit)] = name.lower()
|
||||
|
||||
pattern = re.compile(r'#define IS_([^\s]+)\s+(\d+)')
|
||||
matches = pattern.findall(content)
|
||||
for name, bit in matches:
|
||||
if not int(bit) in bits:
|
||||
bits[int(bit)] = name.lower()
|
||||
|
||||
types = {}
|
||||
for bit in bits:
|
||||
types[bits[bit]] = bit
|
||||
|
||||
type_bit_to_name = bits
|
||||
type_name_to_bit = types
|
||||
dirname = os.path.dirname(filename)
|
||||
return dirname
|
||||
|
||||
def lookup_symbol(name):
|
||||
(symbol, _) = gdb.lookup_symbol(name)
|
||||
|
@ -395,4 +960,49 @@ 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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue