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:
Arnaud Le Blanc 2025-01-30 12:22:07 +01:00 committed by GitHub
parent fa7c67d622
commit 9df1c930bf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 1462 additions and 242 deletions

View file

@ -144,7 +144,7 @@ asm(
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
".ascii \" printf \\\") \\\"\\n\"\n" ".ascii \" printf \\\") \\\"\\n\"\n"
".ascii \" else\\n\"\n" ".ascii \" else\\n\"\n"
".ascii \" printf \\\"??? \\\"\\n\"\n" ".ascii \" printf \\\"\?\?\? \\\"\\n\"\n"
".ascii \" end\\n\"\n" ".ascii \" end\\n\"\n"
".ascii \" if $func != 0\\n\"\n" ".ascii \" if $func != 0\\n\"\n"
".ascii \" if $func->type == 2\\n\"\n" ".ascii \" if $func->type == 2\\n\"\n"
@ -688,7 +688,7 @@ asm(
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
".ascii \"Use |set print max-depth| to control the maximum print depth for nested\\n\"\n" ".ascii \"Use |set print max-depth| to control the maximum print depth for nested\\n\"\n"
".ascii \"structures:\\n\"\n" ".ascii \"structures:\\n\"\n"
".ascii \" (gdb) set print pretty on\\n\"\n" ".ascii \" (gdb) set print max-depth 5\\n\"\n"
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
".ascii \"To interactively type Python for development of the printers, use\\n\"\n" ".ascii \"To interactively type Python for development of the printers, use\\n\"\n"
".ascii \" (gdb) python foo = gdb.parse_and_eval('bar')\\n\"\n" ".ascii \" (gdb) python foo = gdb.parse_and_eval('bar')\\n\"\n"
@ -699,6 +699,8 @@ asm(
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
".ascii \"import gdb\\n\"\n" ".ascii \"import gdb\\n\"\n"
".ascii \"import re\\n\"\n" ".ascii \"import re\\n\"\n"
".ascii \"import traceback\\n\"\n"
".ascii \"import os\\n\"\n"
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
".ascii \"pp_set = gdb.printing.RegexpCollectionPrettyPrinter(\\\"php\\\")\\n\"\n" ".ascii \"pp_set = gdb.printing.RegexpCollectionPrettyPrinter(\\\"php\\\")\\n\"\n"
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
@ -709,53 +711,65 @@ asm(
".ascii \" self.val = val\\n\"\n" ".ascii \" self.val = val\\n\"\n"
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
".ascii \" def to_string(self):\\n\"\n" ".ascii \" def to_string(self):\\n\"\n"
".ascii \" return self.format_string()\\n\"\n" ".ascii \" return '((zend_string*) 0x%x) %s' % (self.val.address, format_zstr(self.val))\\n\"\n"
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
".ascii \" def children(self):\\n\"\n" ".ascii \" def children(self):\\n\"\n"
".ascii \" for field in self.val.type.fields():\\n\"\n" ".ascii \" for field in self.val.type.fields():\\n\"\n"
".ascii \" if field.name == 'val':\\n\"\n" ".ascii \" if field.name == 'val':\\n\"\n"
".ascii \" yield ('val', self.format_string())\\n\"\n" ".ascii \" yield ('val', format_zstr(self.val))\\n\"\n"
".ascii \" else:\\n\"\n" ".ascii \" else:\\n\"\n"
".ascii \" yield (field.name, self.val[field.name])\\n\"\n" ".ascii \" yield (field.name, format_nested(self.val[field.name]))\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" def format_string(self):\\n\"\n"
".ascii \" len = int(self.val['len'])\\n\"\n"
".ascii \" truncated = False\\n\"\n"
".ascii \" if len > 50:\\n\"\n"
".ascii \" len = 50\\n\"\n"
".ascii \" truncated = True\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" ptr_type = gdb.lookup_type('char').pointer()\\n\"\n"
".ascii \" ary_type = gdb.lookup_type('char').array(len)\\n\"\n"
".ascii \" str = self.val['val'].cast(ptr_type).dereference().cast(ary_type)\\n\"\n"
".ascii \" str = str.format_string()\\n\"\n"
".ascii \" if truncated:\\n\"\n"
".ascii \" str += ' (%d bytes total)' % int(self.val['len'])\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" return str\\n\"\n"
".ascii \"\\n\"\n"
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
".ascii \"pp_set.add_printer('zend_string', '^_zend_string$', ZendStringPrettyPrinter)\\n\"\n" ".ascii \"pp_set.add_printer('zend_string', '^_zend_string$', ZendStringPrettyPrinter)\\n\"\n"
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
".ascii \"def zendStringPointerPrinter(ptr):\\n\"\n" ".ascii \"class ZendObjectPrettyPrinter(gdb.printing.PrettyPrinter):\\n\"\n"
".ascii \" \\\"Given a pointer to a zend_string, show the contents (if non-NULL)\\\"\\n\"\n" ".ascii \" \\\"Print a zend_object\\\"\\n\"\n"
".ascii \" if int(ptr) == 0:\\n\"\n" ".ascii \"\\n\"\n"
".ascii \" return '0x0'\\n\"\n" ".ascii \" def __init__(self, val):\\n\"\n"
".ascii \" return ZendStringPrettyPrinter(ptr.dereference()).to_string()\\n\"\n" ".ascii \" self.val = val\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" def to_string(self):\\n\"\n"
".ascii \" return 'object(%s)' % format_zstr(self.val['ce']['name'])\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" def children(self):\\n\"\n"
".ascii \" for field in self.val.type.fields():\\n\"\n"
".ascii \" yield (field.name, format_nested(self.val[field.name]))\\n\"\n"
".ascii \"\\n\"\n"
".ascii \"pp_set.add_printer('zend_object', '^_zend_object$', ZendObjectPrettyPrinter)\\n\"\n"
".ascii \"\\n\"\n"
".ascii \"class ZendArrayPrettyPrinter(gdb.printing.PrettyPrinter):\\n\"\n"
".ascii \" \\\"Print a zend_array\\\"\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" def __init__(self, val):\\n\"\n"
".ascii \" self.val = val\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" def to_string(self):\\n\"\n"
".ascii \" return 'array(%d)' % self.val['nNumOfElements']\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" def children(self):\\n\"\n"
".ascii \" for field in self.val.type.fields():\\n\"\n"
".ascii \" if field.name is None:\\n\"\n"
".ascii \" name = '<anonymous>'\\n\"\n"
".ascii \" val = self.val[field]\\n\"\n"
".ascii \" else:\\n\"\n"
".ascii \" name = field.name\\n\"\n"
".ascii \" val = self.val[field.name]\\n\"\n"
".ascii \" yield (name, format_nested(val))\\n\"\n"
".ascii \"\\n\"\n"
".ascii \"pp_set.add_printer('zend_array', '^_zend_array$', ZendArrayPrettyPrinter)\\n\"\n"
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
".ascii \"class ZendTypePrettyPrinter(gdb.printing.PrettyPrinter):\\n\"\n" ".ascii \"class ZendTypePrettyPrinter(gdb.printing.PrettyPrinter):\\n\"\n"
".ascii \" \\\"Print a zend_type\\\"\\n\"\n" ".ascii \" \\\"Print a zend_type\\\"\\n\"\n"
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
".ascii \" def __init__(self, val):\\n\"\n" ".ascii \" def __init__(self, val):\\n\"\n"
".ascii \" self.val = val\\n\"\n" ".ascii \" self.val = val\\n\"\n"
".ascii \" load_type_bits()\\n\"\n"
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
".ascii \" def to_string(self):\\n\"\n" ".ascii \" def to_string(self):\\n\"\n"
".ascii \" return self.format_type(self.val)\\n\"\n" ".ascii \" return '((zend_type*) 0x%x) %s' % (self.val.address, self.format_type(self.val))\\n\"\n"
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
".ascii \" def children(self):\\n\"\n" ".ascii \" def children(self):\\n\"\n"
".ascii \" for field in self.val.type.fields():\\n\"\n" ".ascii \" for field in self.val.type.fields():\\n\"\n"
".ascii \" yield (field.name, self.val[field.name])\\n\"\n" ".ascii \" yield (field.name, format_nested(self.val[field.name]))\\n\"\n"
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
".ascii \" def format_type(self, t):\\n\"\n" ".ascii \" def format_type(self, t):\\n\"\n"
".ascii \" type_mask = int(t['type_mask'])\\n\"\n" ".ascii \" type_mask = int(t['type_mask'])\\n\"\n"
@ -765,7 +779,7 @@ asm(
".ascii \" meta = []\\n\"\n" ".ascii \" meta = []\\n\"\n"
".ascii \" for bit in range(0, type_mask_size):\\n\"\n" ".ascii \" for bit in range(0, type_mask_size):\\n\"\n"
".ascii \" if type_mask & (1 << bit):\\n\"\n" ".ascii \" if type_mask & (1 << bit):\\n\"\n"
".ascii \" type_name = type_bit_to_name.get(bit)\\n\"\n" ".ascii \" type_name = ZendTypeBits.zendTypeName(bit)\\n\"\n"
".ascii \" match type_name:\\n\"\n" ".ascii \" match type_name:\\n\"\n"
".ascii \" case None:\\n\"\n" ".ascii \" case None:\\n\"\n"
".ascii \" parts.append('(1<<%d)' % bit)\\n\"\n" ".ascii \" parts.append('(1<<%d)' % bit)\\n\"\n"
@ -785,7 +799,7 @@ asm(
".ascii \" separator = '&'\\n\"\n" ".ascii \" separator = '&'\\n\"\n"
".ascii \" case 'name':\\n\"\n" ".ascii \" case 'name':\\n\"\n"
".ascii \" str = t['ptr'].cast(gdb.lookup_type('zend_string').pointer())\\n\"\n" ".ascii \" str = t['ptr'].cast(gdb.lookup_type('zend_string').pointer())\\n\"\n"
".ascii \" parts.append(ZendStringPrettyPrinter(str).to_string())\\n\"\n" ".ascii \" parts.append(format_zstr(str))\\n\"\n"
".ascii \" case _:\\n\"\n" ".ascii \" case _:\\n\"\n"
".ascii \" parts.append(type_name)\\n\"\n" ".ascii \" parts.append(type_name)\\n\"\n"
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
@ -836,11 +850,11 @@ asm(
".ascii \" c = c.dereference()\\n\"\n" ".ascii \" c = c.dereference()\\n\"\n"
".ascii \" yield ('child[%d]' % i, c)\\n\"\n" ".ascii \" yield ('child[%d]' % i, c)\\n\"\n"
".ascii \" elif field.name == 'name':\\n\"\n" ".ascii \" elif field.name == 'name':\\n\"\n"
".ascii \" yield (field.name, ZendStringPrettyPrinter(val[field.name].dereference()).to_string())\\n\"\n" ".ascii \" yield (field.name, format_zstr(val[field.name]))\\n\"\n"
".ascii \" elif field.name == 'val':\\n\"\n" ".ascii \" elif field.name == 'val':\\n\"\n"
".ascii \" yield (field.name, ZvalPrettyPrinter(val[field.name]).to_string())\\n\"\n" ".ascii \" yield (field.name, ZvalPrettyPrinter(val[field.name]).to_string())\\n\"\n"
".ascii \" else:\\n\"\n" ".ascii \" else:\\n\"\n"
".ascii \" yield (field.name, val[field.name])\\n\"\n" ".ascii \" yield (field.name, format_nested(self.val[field.name]))\\n\"\n"
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
".ascii \" def is_special(self):\\n\"\n" ".ascii \" def is_special(self):\\n\"\n"
".ascii \" special_shift = 6 # ZEND_AST_SPECIAL_SHIFT\\n\"\n" ".ascii \" special_shift = 6 # ZEND_AST_SPECIAL_SHIFT\\n\"\n"
@ -893,37 +907,46 @@ asm(
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
".ascii \" def __init__(self, val):\\n\"\n" ".ascii \" def __init__(self, val):\\n\"\n"
".ascii \" self.val = val\\n\"\n" ".ascii \" self.val = val\\n\"\n"
".ascii \" load_type_bits()\\n\"\n"
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
".ascii \" def to_string(self):\\n\"\n" ".ascii \" def to_string(self):\\n\"\n"
".ascii \" return self.value_to_string()\\n\"\n" ".ascii \" return '((zval*) 0x%x) %s' % (self.val.address, self.value_to_string())\\n\"\n"
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
".ascii \" def value_to_string(self):\\n\"\n" ".ascii \" def value_to_string(self):\\n\"\n"
".ascii \" t = int(self.val['u1']['v']['type'])\\n\"\n" ".ascii \" t = int(self.val['u1']['v']['type'])\\n\"\n"
".ascii \" if t == type_name_to_bit['undef']:\\n\"\n" ".ascii \" if t == ZendTypeBits.bit('undef'):\\n\"\n"
".ascii \" return 'undef'\\n\"\n" ".ascii \" return 'undef'\\n\"\n"
".ascii \" elif t == type_name_to_bit['null']:\\n\"\n" ".ascii \" elif t == ZendTypeBits.bit('null'):\\n\"\n"
".ascii \" return 'null'\\n\"\n" ".ascii \" return 'null'\\n\"\n"
".ascii \" elif t == type_name_to_bit['false']:\\n\"\n" ".ascii \" elif t == ZendTypeBits.bit('false'):\\n\"\n"
".ascii \" return 'false'\\n\"\n" ".ascii \" return 'false'\\n\"\n"
".ascii \" elif t == type_name_to_bit['true']:\\n\"\n" ".ascii \" elif t == ZendTypeBits.bit('true'):\\n\"\n"
".ascii \" return 'true'\\n\"\n" ".ascii \" return 'true'\\n\"\n"
".ascii \" elif t == type_name_to_bit['long']:\\n\"\n" ".ascii \" elif t == ZendTypeBits.bit('long'):\\n\"\n"
".ascii \" return str(self.val['value']['lval'])\\n\"\n" ".ascii \" return str(self.val['value']['lval'])\\n\"\n"
".ascii \" elif t == type_name_to_bit['double']:\\n\"\n" ".ascii \" elif t == ZendTypeBits.bit('double'):\\n\"\n"
".ascii \" return str(self.val['value']['dval'])\\n\"\n" ".ascii \" return str(self.val['value']['dval'])\\n\"\n"
".ascii \" elif t == type_name_to_bit['string']:\\n\"\n" ".ascii \" elif t == ZendTypeBits.bit('string'):\\n\"\n"
".ascii \" return ZendStringPrettyPrinter(self.val['value']['str'].dereference()).to_string()\\n\"\n" ".ascii \" return format_zstr(self.val['value']['str'])\\n\"\n"
".ascii \" elif t == type_name_to_bit['array']:\\n\"\n" ".ascii \" elif t == ZendTypeBits.bit('array'):\\n\"\n"
".ascii \" return 'array'\\n\"\n" ".ascii \" return 'array'\\n\"\n"
".ascii \" elif t == type_name_to_bit['object']:\\n\"\n" ".ascii \" elif t == ZendTypeBits.bit('object'):\\n\"\n"
".ascii \" return 'object(%s)' % ZendStringPrettyPrinter(self.val['value']['obj']['ce']['name'].dereference()).to_string()\\n\"\n" ".ascii \" return 'object(%s)' % format_zstr(self.val['value']['obj']['ce']['name'])\\n\"\n"
".ascii \" elif t == type_name_to_bit['resource']:\\n\"\n" ".ascii \" elif t == ZendTypeBits.bit('resource'):\\n\"\n"
".ascii \" return 'resource'\\n\"\n" ".ascii \" return 'resource'\\n\"\n"
".ascii \" elif t == type_name_to_bit['reference']:\\n\"\n" ".ascii \" elif t == ZendTypeBits.bit('reference'):\\n\"\n"
".ascii \" return 'reference'\\n\"\n" ".ascii \" return 'reference'\\n\"\n"
".ascii \" elif t == type_name_to_bit['constant_ast']:\\n\"\n" ".ascii \" elif t == ZendTypeBits.bit('constant_ast'):\\n\"\n"
".ascii \" return 'constant_ast'\\n\"\n" ".ascii \" return 'constant_ast'\\n\"\n"
".ascii \" elif t == ZendTypeBits.bit('indirect'):\\n\"\n"
".ascii \" value = self.val['value']['zv']\\n\"\n"
".ascii \" valuestr = ZvalPrettyPrinter(value).to_string()\\n\"\n"
".ascii \" return 'indirect: ((zval*) 0x%x) %s' % (int(value), valuestr)\\n\"\n"
".ascii \" elif t == ZendTypeBits.bit('ptr'):\\n\"\n"
".ascii \" value = int(self.val['value']['ptr'])\\n\"\n"
".ascii \" return 'ptr: ((void*) 0x%x)' % (value)\\n\"\n"
".ascii \" elif t == ZendTypeBits.bit('alias_ptr'):\\n\"\n"
".ascii \" value = int(self.val['value']['ptr'])\\n\"\n"
".ascii \" return 'alias_ptr: ((void*) 0x%x)' % (value)\\n\"\n"
".ascii \" else:\\n\"\n" ".ascii \" else:\\n\"\n"
".ascii \" return 'zval of type %d' % int(self.val['u1']['v']['type'])\\n\"\n" ".ascii \" return 'zval of type %d' % int(self.val['u1']['v']['type'])\\n\"\n"
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
@ -931,38 +954,42 @@ asm(
".ascii \" for field in self.val.type.fields():\\n\"\n" ".ascii \" for field in self.val.type.fields():\\n\"\n"
".ascii \" if field.name == 'value':\\n\"\n" ".ascii \" if field.name == 'value':\\n\"\n"
".ascii \" value = self.val['value']\\n\"\n" ".ascii \" value = self.val['value']\\n\"\n"
".ascii \" sub_field = 'ptr'\\n\"\n"
".ascii \" t = int(self.val['u1']['v']['type'])\\n\"\n" ".ascii \" t = int(self.val['u1']['v']['type'])\\n\"\n"
".ascii \" if t == type_name_to_bit['undef']:\\n\"\n" ".ascii \" if t == ZendTypeBits.bit('undef'):\\n\"\n"
".ascii \" value = value['lval']\\n\"\n" ".ascii \" sub_field = 'lval'\\n\"\n"
".ascii \" elif t == type_name_to_bit['null']:\\n\"\n" ".ascii \" elif t == ZendTypeBits.bit('null'):\\n\"\n"
".ascii \" value = value['lval']\\n\"\n" ".ascii \" sub_field = 'lval'\\n\"\n"
".ascii \" elif t == type_name_to_bit['false']:\\n\"\n" ".ascii \" elif t == ZendTypeBits.bit('false'):\\n\"\n"
".ascii \" value = value['lval']\\n\"\n" ".ascii \" sub_field = 'lval'\\n\"\n"
".ascii \" elif t == type_name_to_bit['true']:\\n\"\n" ".ascii \" elif t == ZendTypeBits.bit('true'):\\n\"\n"
".ascii \" value = value['lval']\\n\"\n" ".ascii \" sub_field = 'lval'\\n\"\n"
".ascii \" elif t == type_name_to_bit['long']:\\n\"\n" ".ascii \" elif t == ZendTypeBits.bit('long'):\\n\"\n"
".ascii \" value = value['lval']\\n\"\n" ".ascii \" sub_field = 'lval'\\n\"\n"
".ascii \" elif t == type_name_to_bit['double']:\\n\"\n" ".ascii \" elif t == ZendTypeBits.bit('double'):\\n\"\n"
".ascii \" value = value['dval']\\n\"\n" ".ascii \" sub_field = 'dval'\\n\"\n"
".ascii \" elif t == type_name_to_bit['string']:\\n\"\n" ".ascii \" elif t == ZendTypeBits.bit('string'):\\n\"\n"
".ascii \" value = value['str'].dereference()\\n\"\n" ".ascii \" sub_field = 'str'\\n\"\n"
".ascii \" elif t == type_name_to_bit['array']:\\n\"\n" ".ascii \" elif t == ZendTypeBits.bit('array'):\\n\"\n"
".ascii \" value = value['ht'].dereference()\\n\"\n" ".ascii \" sub_field = 'arr'\\n\"\n"
".ascii \" elif t == type_name_to_bit['object']:\\n\"\n" ".ascii \" elif t == ZendTypeBits.bit('object'):\\n\"\n"
".ascii \" value = value['obj'].dereference()\\n\"\n" ".ascii \" sub_field = 'obj'\\n\"\n"
".ascii \" elif t == type_name_to_bit['resource']:\\n\"\n" ".ascii \" elif t == ZendTypeBits.bit('resource'):\\n\"\n"
".ascii \" value = value['res'].dereference()\\n\"\n" ".ascii \" sub_field = 'res'\\n\"\n"
".ascii \" elif t == type_name_to_bit['reference']:\\n\"\n" ".ascii \" elif t == ZendTypeBits.bit('reference'):\\n\"\n"
".ascii \" value = value['ref'].dereference()\\n\"\n" ".ascii \" sub_field = 'ref'\\n\"\n"
".ascii \" elif t == type_name_to_bit['constant_ast']:\\n\"\n" ".ascii \" elif t == ZendTypeBits.bit('constant_ast'):\\n\"\n"
".ascii \" value = value['ast'].dereference()\\n\"\n" ".ascii \" sub_field = 'ast'\\n\"\n"
".ascii \" else:\\n\"\n" ".ascii \" elif t == ZendTypeBits.bit('indirect'):\\n\"\n"
".ascii \" value = value['ptr']\\n\"\n" ".ascii \" sub_field = 'zv'\\n\"\n"
".ascii \" yield (field.name, value)\\n\"\n" ".ascii \" value = value[sub_field]\\n\"\n"
".ascii \" if sub_field != 'ptr' and value.type.code == gdb.TYPE_CODE_PTR:\\n\"\n"
".ascii \" value = value.dereference()\\n\"\n"
".ascii \" yield ('%s.%s' % (field.name, sub_field), value)\\n\"\n"
".ascii \" elif field.name == 'u2':\\n\"\n" ".ascii \" elif field.name == 'u2':\\n\"\n"
".ascii \" yield ('u2', self.val[field.name]['extra'])\\n\"\n" ".ascii \" yield ('u2', self.val[field.name]['extra'])\\n\"\n"
".ascii \" else:\\n\"\n" ".ascii \" else:\\n\"\n"
".ascii \" yield (field.name, self.val[field.name])\\n\"\n" ".ascii \" yield (field.name, format_nested(self.val[field.name]))\\n\"\n"
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
".ascii \"pp_set.add_printer('zval', '^_zval_struct$', ZvalPrettyPrinter)\\n\"\n" ".ascii \"pp_set.add_printer('zval', '^_zval_struct$', ZvalPrettyPrinter)\\n\"\n"
@ -970,22 +997,20 @@ asm(
".ascii \"class ZendClassEntryPrettyPrinter(gdb.printing.PrettyPrinter):\\n\"\n" ".ascii \"class ZendClassEntryPrettyPrinter(gdb.printing.PrettyPrinter):\\n\"\n"
".ascii \" \\\"Print a zend_class_entry\\\"\\n\"\n" ".ascii \" \\\"Print a zend_class_entry\\\"\\n\"\n"
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
".ascii \" # String pointers, show the string contents if possible\\n\"\n"
".ascii \" STRING_FIELDS = [ 'name', 'doc_comment' ]\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" def __init__(self, val):\\n\"\n" ".ascii \" def __init__(self, val):\\n\"\n"
".ascii \" self.val = val\\n\"\n" ".ascii \" self.val = val\\n\"\n"
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
".ascii \" def to_string(self):\\n\"\n" ".ascii \" def to_string(self):\\n\"\n"
".ascii \" return zendStringPointerPrinter(self.val['name'])\\n\"\n" ".ascii \" return '((zend_class_entry*) 0x%x) %s' % (self.val.address, format_zstr(self.val['name']))\\n\"\n"
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
".ascii \" def children(self):\\n\"\n" ".ascii \" def children(self):\\n\"\n"
".ascii \" for field in self.val.type.fields():\\n\"\n" ".ascii \" for field in self.val.type.fields():\\n\"\n"
".ascii \" if field.name is not None:\\n\"\n" ".ascii \" if field.name is not None:\\n\"\n"
".ascii \" if field.name in self.STRING_FIELDS:\\n\"\n" ".ascii \" if field.name == 'ce_flags':\\n\"\n"
".ascii \" yield (field.name, zendStringPointerPrinter(self.val[field.name]))\\n\"\n" ".ascii \" flags = self.val[field.name]\\n\"\n"
".ascii \" yield (field.name, '%d (%s)' % (flags, ZendAccFlags.format_ce_flags(flags)))\\n\"\n"
".ascii \" else:\\n\"\n" ".ascii \" else:\\n\"\n"
".ascii \" yield (field.name, self.val[field.name])\\n\"\n" ".ascii \" yield (field.name, format_nested(self.val[field.name]))\\n\"\n"
".ascii \" else:\\n\"\n" ".ascii \" else:\\n\"\n"
".ascii \" # Don't break on the union fields. Unfortunately, pretty\\n\"\n" ".ascii \" # Don't break on the union fields. Unfortunately, pretty\\n\"\n"
".ascii \" # printers done in python cannot match the default formatting of\\n\"\n" ".ascii \" # printers done in python cannot match the default formatting of\\n\"\n"
@ -1004,52 +1029,592 @@ asm(
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
".ascii \" def children(self):\\n\"\n" ".ascii \" def children(self):\\n\"\n"
".ascii \" for field in self.val.type.fields():\\n\"\n" ".ascii \" for field in self.val.type.fields():\\n\"\n"
".ascii \" if field.name == 'doc_comment':\\n\"\n" ".ascii \" if field.name == 'value':\\n\"\n"
".ascii \" yield ('doc_comment', zendStringPointerPrinter(self.val['doc_comment']))\\n\"\n" ".ascii \" flags = self.val[field.name]['u2']['constant_flags']\\n\"\n"
".ascii \" elif field.name == 'ce':\\n\"\n" ".ascii \" yield ('value.u2.constant_flags', '%d (%s)' % (flags, ZendAccFlags.format_const_flags(flags)))\\n\"\n"
".ascii \" yield ('ce', zendStringPointerPrinter(self.val['ce']['name']))\\n\"\n"
".ascii \" else:\\n\"\n"
".ascii \" yield (field.name, self.val[field.name])\\n\"\n" ".ascii \" yield (field.name, self.val[field.name])\\n\"\n"
".ascii \" else:\\n\"\n"
".ascii \" yield (field.name, format_nested(self.val[field.name]))\\n\"\n"
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
".ascii \"pp_set.add_printer('zend_class_constant', '^_zend_class_constant$', ZendClassConstantPrettyPrinter)\\n\"\n" ".ascii \"pp_set.add_printer('zend_class_constant', '^_zend_class_constant$', ZendClassConstantPrettyPrinter)\\n\"\n"
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
".ascii \"type_bit_to_name = None\\n\"\n" ".ascii \"class ZendPropertyInfoPrettyPrinter(gdb.printing.PrettyPrinter):\\n\"\n"
".ascii \"type_name_to_bit = None\\n\"\n" ".ascii \" \\\"Print a zend_property_info\\\"\\n\"\n"
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
".ascii \"def load_type_bits():\\n\"\n" ".ascii \" def __init__(self, val):\\n\"\n"
".ascii \" global type_bit_to_name\\n\"\n" ".ascii \" self.val = val\\n\"\n"
".ascii \" global type_name_to_bit\\n\"\n"
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
".ascii \" if type_bit_to_name != None:\\n\"\n" ".ascii \" def children(self):\\n\"\n"
".ascii \" for field in self.val.type.fields():\\n\"\n"
".ascii \" if field.name == 'flags':\\n\"\n"
".ascii \" flags = self.val[field.name]\\n\"\n"
".ascii \" yield ('flags', '%d (%s)' % (flags, ZendAccFlags.format_prop_flags(flags)))\\n\"\n"
".ascii \" else:\\n\"\n"
".ascii \" yield (field.name, format_nested(self.val[field.name]))\\n\"\n"
".ascii \"\\n\"\n"
".ascii \"pp_set.add_printer('zend_property_info', '^_zend_property_info$', ZendPropertyInfoPrettyPrinter)\\n\"\n"
".ascii \"\\n\"\n"
".ascii \"class ZendFunctionPrettyPrinter(gdb.printing.PrettyPrinter):\\n\"\n"
".ascii \" \\\"Print a zend_function\\\"\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" def __init__(self, val):\\n\"\n"
".ascii \" self.val = val\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" def to_string(self):\\n\"\n"
".ascii \" match int(self.val['type']):\\n\"\n"
".ascii \" case ZendFnTypes.ZEND_INTERNAL_FUNCTION:\\n\"\n"
".ascii \" typestr = 'internal'\\n\"\n"
".ascii \" case ZendFnTypes.ZEND_USER_FUNCTION:\\n\"\n"
".ascii \" typestr = 'user'\\n\"\n"
".ascii \" case ZendFnTypes.ZEND_EVAL_CODE:\\n\"\n"
".ascii \" typestr = 'eval'\\n\"\n"
".ascii \" case _:\\n\"\n"
".ascii \" typestr = '\?\?\?'\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" if self.val['common']['function_name']:\\n\"\n"
".ascii \" namestr = format_zstr(self.val['common']['function_name'])\\n\"\n"
".ascii \" else:\\n\"\n"
".ascii \" namestr = '{main}'\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" if self.val['common']['scope']:\\n\"\n"
".ascii \" str = '%s method %s::%s' % (typestr, format_zstr(self.val['common']['scope']['name']), namestr)\\n\"\n"
".ascii \" else:\\n\"\n"
".ascii \" str = '%s function %s' % (typestr, namestr)\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" if int(self.val['type']) == ZendFnTypes.ZEND_USER_FUNCTION or int(self.val['type']) == ZendFnTypes.ZEND_EVAL_CODE:\\n\"\n"
".ascii \" str = '%s %s:%d' % (str, format_zstr(self.val['op_array']['filename']), int(self.val['op_array']['line_start']))\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" return '((zend_function*) 0x%x) %s' % (self.val.address, str)\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" def children(self):\\n\"\n"
".ascii \" for field in self.val.type.fields():\\n\"\n"
".ascii \" if field.name == 'common' or field.name == 'type' or field.name == 'quick_arg_flags':\\n\"\n"
".ascii \" # Redundant with op_array / internal_function\\n\"\n"
".ascii \" continue\\n\"\n"
".ascii \" elif field.name == 'op_array':\\n\"\n"
".ascii \" if int(self.val['type']) == ZendFnTypes.ZEND_USER_FUNCTION or int(self.val['type']) == ZendFnTypes.ZEND_EVAL_CODE:\\n\"\n"
".ascii \" yield (field.name, self.val[field.name])\\n\"\n"
".ascii \" elif field.name == 'internal_function':\\n\"\n"
".ascii \" if int(self.val['type']) == ZendFnTypes.ZEND_INTERNAL_FUNCTION:\\n\"\n"
".ascii \" yield (field.name, self.val[field.name])\\n\"\n"
".ascii \" else:\\n\"\n"
".ascii \" yield (field.name, format_nested(self.val[field.name]))\\n\"\n"
".ascii \"\\n\"\n"
".ascii \"pp_set.add_printer('zend_function', '^_zend_function$', ZendFunctionPrettyPrinter)\\n\"\n"
".ascii \"\\n\"\n"
".ascii \"class ZendOpArrayPrettyPrinter(gdb.printing.PrettyPrinter):\\n\"\n"
".ascii \" \\\"Print a zend_op_array\\\"\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" def __init__(self, val):\\n\"\n"
".ascii \" self.val = val\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" def to_string(self):\\n\"\n"
".ascii \" if self.val['function_name']:\\n\"\n"
".ascii \" namestr = format_zstr(self.val['function_name'])\\n\"\n"
".ascii \" else:\\n\"\n"
".ascii \" namestr = '{main}'\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" if self.val['scope']:\\n\"\n"
".ascii \" str = 'method %s::%s' % (format_zstr(self.val['scope']['name']), namestr)\\n\"\n"
".ascii \" else:\\n\"\n"
".ascii \" str = 'function %s' % (namestr)\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" str = '%s %s:%d' % (str, format_zstr(self.val['filename']), int(self.val['line_start']))\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" return '((zend_op_array*) 0x%x) %s' % (self.val.address, str)\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" def children(self):\\n\"\n"
".ascii \" for field in self.val.type.fields():\\n\"\n"
".ascii \" if field.name == 'fn_flags':\\n\"\n"
".ascii \" value = self.val[field.name]\\n\"\n"
".ascii \" yield (field.name, '%d (%s)' % (value, ZendAccFlags.format_fn_flags(value)))\\n\"\n"
".ascii \" else:\\n\"\n"
".ascii \" yield (field.name, format_nested(self.val[field.name]))\\n\"\n"
".ascii \"\\n\"\n"
".ascii \"pp_set.add_printer('zend_op_array', '^_zend_op_array$', ZendOpArrayPrettyPrinter)\\n\"\n"
".ascii \"\\n\"\n"
".ascii \"class ZendOpPrettyPrinter(gdb.printing.PrettyPrinter):\\n\"\n"
".ascii \" \\\"Print a zend_op\\\"\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" def __init__(self, val):\\n\"\n"
".ascii \" self.val = val\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" def children(self):\\n\"\n"
".ascii \" for field in self.val.type.fields():\\n\"\n"
".ascii \" if field.name == 'opcode':\\n\"\n"
".ascii \" opcode = int(self.val[field.name])\\n\"\n"
".ascii \" yield (field.name, '%d (%s)' % (opcode, ZendOpcodes.name(opcode)))\\n\"\n"
".ascii \" else:\\n\"\n"
".ascii \" yield (field.name, format_nested(self.val[field.name]))\\n\"\n"
".ascii \"\\n\"\n"
".ascii \"pp_set.add_printer('zend_op', '^_zend_op$', ZendOpPrettyPrinter)\\n\"\n"
".ascii \"\\n\"\n"
".ascii \"class ZendInternalFunctionPrettyPrinter(gdb.printing.PrettyPrinter):\\n\"\n"
".ascii \" \\\"Print a zend_internal_function\\\"\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" def __init__(self, val):\\n\"\n"
".ascii \" self.val = val\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" def to_string(self):\\n\"\n"
".ascii \" if self.val['function_name']:\\n\"\n"
".ascii \" namestr = format_zstr(self.val['function_name'])\\n\"\n"
".ascii \" else:\\n\"\n"
".ascii \" namestr = '\?\?\?'\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" if self.val['scope']:\\n\"\n"
".ascii \" str = 'method %s::%s' % (format_zstr(self.val['scope']['name']), namestr)\\n\"\n"
".ascii \" else:\\n\"\n"
".ascii \" str = 'function %s' % (namestr)\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" return '((zend_internal_function*) 0x%x) %s' % (self.val.address, str)\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" def children(self):\\n\"\n"
".ascii \" for field in self.val.type.fields():\\n\"\n"
".ascii \" if field.name == 'fn_flags':\\n\"\n"
".ascii \" yield ('fn_flags', ('%d (%s)' % (self.val[field.name], ZendAccFlags.format_fn_flags(self.val[field.name]))))\\n\"\n"
".ascii \" else:\\n\"\n"
".ascii \" yield (field.name, format_nested(self.val[field.name]))\\n\"\n"
".ascii \"\\n\"\n"
".ascii \"pp_set.add_printer('zend_internal_function', '^_zend_internal_function$', ZendInternalFunctionPrettyPrinter)\\n\"\n"
".ascii \"\\n\"\n"
".ascii \"class ZendRefcountedHPrettyPrinter(gdb.printing.PrettyPrinter):\\n\"\n"
".ascii \" \\\"Print a zend_refcounted_h\\\"\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" def __init__(self, val):\\n\"\n"
".ascii \" self.val = val\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" def children(self):\\n\"\n"
".ascii \" for field in self.val.type.fields():\\n\"\n"
".ascii \" if field.name == 'u':\\n\"\n"
".ascii \" val = self.val[field.name]\\n\"\n"
".ascii \" if val == None:\\n\"\n"
".ascii \" val = self.val\\n\"\n"
".ascii \" for subfield in val.type.fields():\\n\"\n"
".ascii \" if subfield.name == 'type_info':\\n\"\n"
".ascii \" flags = int(val[subfield.name])\\n\"\n"
".ascii \" yield (('%s.%s' % (field.name, subfield.name)), '%d (%s)' % (flags, ZendRefTypeInfo.format(flags)))\\n\"\n"
".ascii \" else:\\n\"\n"
".ascii \" yield (('%s.%s' % (field.name, subfield.name)), format_nested(val[subfield.name]))\\n\"\n"
".ascii \" else:\\n\"\n"
".ascii \" yield (('%s' % field.name), format_nested(self.val[field.name]))\\n\"\n"
".ascii \"\\n\"\n"
".ascii \"pp_set.add_printer('zend_refcounted_h', '^_zend_refcounted_h$', ZendRefcountedHPrettyPrinter)\\n\"\n"
".ascii \"\\n\"\n"
".ascii \"class PrintAccFlagsCommand(gdb.Command):\\n\"\n"
".ascii \" \\\"Pretty print ACC flags\\\"\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" def __init__ (self, type):\\n\"\n"
".ascii \" self.type = type\\n\"\n"
".ascii \" name = 'print_%s_flags' % type\\n\"\n"
".ascii \" super(PrintAccFlagsCommand, self).__init__(name, gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION)\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" def invoke (self, arg, from_tty):\\n\"\n"
".ascii \" arg = int(gdb.parse_and_eval(arg))\\n\"\n"
".ascii \" print(ZendAccFlags.format_flags(arg, self.type))\\n\"\n"
".ascii \"\\n\"\n"
".ascii \"PrintAccFlagsCommand('fn')\\n\"\n"
".ascii \"PrintAccFlagsCommand('ce')\\n\"\n"
".ascii \"PrintAccFlagsCommand('prop')\\n\"\n"
".ascii \"PrintAccFlagsCommand('const')\\n\"\n"
".ascii \"\\n\"\n"
".ascii \"class PrintOpcodeCommand(gdb.Command):\\n\"\n"
".ascii \" \\\"Pretty print opcode\\\"\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" def __init__ (self):\\n\"\n"
".ascii \" super(PrintOpcodeCommand, self).__init__(\\\"print_opcode\\\", gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION)\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" def invoke (self, arg, from_tty):\\n\"\n"
".ascii \" arg = int(gdb.parse_and_eval(arg))\\n\"\n"
".ascii \" print(ZendOpcodes.name(arg))\\n\"\n"
".ascii \"\\n\"\n"
".ascii \"PrintOpcodeCommand()\\n\"\n"
".ascii \"\\n\"\n"
".ascii \"class PrintRefTypeInfoCommand(gdb.Command):\\n\"\n"
".ascii \" \\\"Pretty print zend_refcounted.gc.u.type_info\\\"\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" def __init__ (self):\\n\"\n"
".ascii \" super(PrintRefTypeInfoCommand, self).__init__(\\\"print_ref_type_info\\\", gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION)\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" def invoke (self, arg, from_tty):\\n\"\n"
".ascii \" arg = int(gdb.parse_and_eval(arg))\\n\"\n"
".ascii \" print(ZendRefTypeInfo.format(arg))\\n\"\n"
".ascii \"\\n\"\n"
".ascii \"PrintRefTypeInfoCommand()\\n\"\n"
".ascii \"\\n\"\n"
".ascii \"class DumpOpArrayCommand(gdb.Command):\\n\"\n"
".ascii \" \\\"Dump an op_array\\\"\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" def __init__ (self):\\n\"\n"
".ascii \" super(DumpOpArrayCommand, self).__init__(\\\"dump_op_array\\\", gdb.COMMAND_USER, gdb.COMPLETE_EXPRESSION)\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" def invoke (self, arg, from_tty):\\n\"\n"
".ascii \" op_array = gdb.parse_and_eval(arg)\\n\"\n"
".ascii \" if op_array.type.code != gdb.TYPE_CODE_PTR:\\n\"\n"
".ascii \" print(\\\"Must pass a zend_op_array* (got a %s)\\\" % op_array.type)\\n\"\n"
".ascii \" return\\n\"\n"
".ascii \" if str(gdb.types.get_basic_type(op_array.type.target())) != 'struct _zend_op_array':\\n\"\n"
".ascii \" print(\\\"Must pass a zend_op_array* (got a %s)\\\" % op_array.type)\\n\"\n"
".ascii \" return\\n\"\n"
".ascii \" if int(op_array) == 0:\\n\"\n"
".ascii \" print(\\\"NULL\\\")\\n\"\n"
".ascii \" return\\n\"\n"
".ascii \" gdb.execute(\\\"call zend_dump_op_array((zend_op_array*)0x%x, 0, 0, 0)\\\" % (int(op_array)))\\n\"\n"
".ascii \" return\\n\"\n" ".ascii \" return\\n\"\n"
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
".ascii \" (symbol,_) = gdb.lookup_symbol(\\\"zend_gc_refcount\\\")\\n\"\n" ".ascii \"DumpOpArrayCommand()\\n\"\n"
".ascii \"\\n\"\n"
".ascii \"class ZendTypeBits:\\n\"\n"
".ascii \" _bits = None\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" @classmethod\\n\"\n"
".ascii \" def zendTypeName(self, bit):\\n\"\n"
".ascii \" self._load()\\n\"\n"
".ascii \" for name in self._bits:\\n\"\n"
".ascii \" if bit == self._bits[name]:\\n\"\n"
".ascii \" return name\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" @classmethod\\n\"\n"
".ascii \" def zvalTypeName(self, bit):\\n\"\n"
".ascii \" # Same as zendTypeName, but return the last matching one\\n\"\n"
".ascii \" # e.g. 13 is IS_PTR, not IS_ITERABLE\\n\"\n"
".ascii \" self._load()\\n\"\n"
".ascii \" ret = None\\n\"\n"
".ascii \" for name in self._bits:\\n\"\n"
".ascii \" if bit == self._bits[name]:\\n\"\n"
".ascii \" ret = name\\n\"\n"
".ascii \" return ret\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" @classmethod\\n\"\n"
".ascii \" def bit(self, name):\\n\"\n"
".ascii \" self._load()\\n\"\n"
".ascii \" return self._bits.get(name)\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" @classmethod\\n\"\n"
".ascii \" def _load(self):\\n\"\n"
".ascii \" if self._bits != None:\\n\"\n"
".ascii \" return\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" dirname = detect_source_dir()\\n\"\n"
".ascii \" filename = os.path.join(dirname, 'zend_types.h')\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" bits = {}\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" with open(filename, 'r') as file:\\n\"\n"
".ascii \" content = file.read()\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" pattern = re.compile(r'#define _ZEND_TYPE_([^\\\\s]+)_BIT\\\\s+\\\\(1u << (\\\\d+)\\\\)')\\n\"\n"
".ascii \" matches = pattern.findall(content)\\n\"\n"
".ascii \" for name, bit in matches:\\n\"\n"
".ascii \" bits[name.lower()] = int(bit)\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" pattern = re.compile(r'#define IS_([^\\\\s]+)\\\\s+(\\\\d+)')\\n\"\n"
".ascii \" matches = pattern.findall(content)\\n\"\n"
".ascii \" for name, bit in matches:\\n\"\n"
".ascii \" bits[name.lower()] = int(bit)\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" self._bits = bits\\n\"\n"
".ascii \"\\n\"\n"
".ascii \"class ZendFnTypes:\\n\"\n"
".ascii \" ZEND_INTERNAL_FUNCTION = 1\\n\"\n"
".ascii \" ZEND_USER_FUNCTION = 2\\n\"\n"
".ascii \" ZEND_EVAL_CODE = 4\\n\"\n"
".ascii \"\\n\"\n"
".ascii \"class ZendAccFlag:\\n\"\n"
".ascii \" ce = False\\n\"\n"
".ascii \" fn = False\\n\"\n"
".ascii \" prop = False\\n\"\n"
".ascii \" const = False\\n\"\n"
".ascii \" bit = 0\\n\"\n"
".ascii \" def __init__(self, ce, fn, prop, const, bit):\\n\"\n"
".ascii \" self.ce = ce\\n\"\n"
".ascii \" self.fn = fn\\n\"\n"
".ascii \" self.prop = prop\\n\"\n"
".ascii \" self.const = const\\n\"\n"
".ascii \" self.bit = bit\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" def applies_to(self, type):\\n\"\n"
".ascii \" return getattr(self, type)\\n\"\n"
".ascii \"\\n\"\n"
".ascii \"class ZendAccFlags:\\n\"\n"
".ascii \" _flags = None\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" @classmethod\\n\"\n"
".ascii \" def fn_flag_name(self, bit):\\n\"\n"
".ascii \" self.flag_name(bit, 'fn')\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" @classmethod\\n\"\n"
".ascii \" def ce_flag_name(self, bit):\\n\"\n"
".ascii \" self.flag_name(bit, 'ce')\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" @classmethod\\n\"\n"
".ascii \" def prop_flag_name(self, bit):\\n\"\n"
".ascii \" self.flag_name(bit, 'prop')\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" @classmethod\\n\"\n"
".ascii \" def const_flag_name(self, bit):\\n\"\n"
".ascii \" self.flag_name(bit, 'const')\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" @classmethod\\n\"\n"
".ascii \" def flag_name(self, bit, type):\\n\"\n"
".ascii \" self._load()\\n\"\n"
".ascii \" for name in self._flags:\\n\"\n"
".ascii \" flag = self._flags[name]\\n\"\n"
".ascii \" if flag.applies_to(type) and bit == flag.bit:\\n\"\n"
".ascii \" return name\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" @classmethod\\n\"\n"
".ascii \" def flag_bit(self, name):\\n\"\n"
".ascii \" self._load()\\n\"\n"
".ascii \" return self._flags[name]\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" @classmethod\\n\"\n"
".ascii \" def format_flags(self, flags, type):\\n\"\n"
".ascii \" flags = int(flags)\\n\"\n"
".ascii \" names = []\\n\"\n"
".ascii \" for i in range(0, 31):\\n\"\n"
".ascii \" if (flags & (1 << i)) != 0:\\n\"\n"
".ascii \" name = self.flag_name(i, type)\\n\"\n"
".ascii \" if name == None:\\n\"\n"
".ascii \" names.append('(1 << %d)' % (i))\\n\"\n"
".ascii \" else:\\n\"\n"
".ascii \" names.append(name)\\n\"\n"
".ascii \" return ' | '.join(names)\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" @classmethod\\n\"\n"
".ascii \" def format_fn_flags(self, flags):\\n\"\n"
".ascii \" return self.format_flags(flags, 'fn')\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" @classmethod\\n\"\n"
".ascii \" def format_ce_flags(self, flags):\\n\"\n"
".ascii \" return self.format_flags(flags, 'ce')\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" @classmethod\\n\"\n"
".ascii \" def format_prop_flags(self, flags):\\n\"\n"
".ascii \" return self.format_flags(flags, 'prop')\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" @classmethod\\n\"\n"
".ascii \" def format_const_flags(self, flags):\\n\"\n"
".ascii \" return self.format_flags(flags, 'const')\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" @classmethod\\n\"\n"
".ascii \" def _load(self):\\n\"\n"
".ascii \" if self._flags != None:\\n\"\n"
".ascii \" return\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" dirname = detect_source_dir()\\n\"\n"
".ascii \" filename = os.path.join(dirname, 'zend_compile.h')\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" flags = {}\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" with open(filename, 'r') as file:\\n\"\n"
".ascii \" content = file.read()\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" 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+\\\\*/')\\n\"\n"
".ascii \" matches = pattern.findall(content)\\n\"\n"
".ascii \" for name, bit, cls, func, prop, const in matches:\\n\"\n"
".ascii \" flags[name] = ZendAccFlag(cls == 'X', func == 'X', prop == 'X', const == 'X', int(bit))\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" self._flags = flags\\n\"\n"
".ascii \"\\n\"\n"
".ascii \"class ZendOpcodes:\\n\"\n"
".ascii \" _opcodes = None\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" @classmethod\\n\"\n"
".ascii \" def name(self, number):\\n\"\n"
".ascii \" self._load()\\n\"\n"
".ascii \" number = int(number)\\n\"\n"
".ascii \" for name in self._opcodes:\\n\"\n"
".ascii \" if number == self._opcodes[name]:\\n\"\n"
".ascii \" return name\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" @classmethod\\n\"\n"
".ascii \" def number(self, name):\\n\"\n"
".ascii \" self._load()\\n\"\n"
".ascii \" return self._opcodes[name]\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" @classmethod\\n\"\n"
".ascii \" def _load(self):\\n\"\n"
".ascii \" if self._opcodes != None:\\n\"\n"
".ascii \" return\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" dirname = detect_source_dir()\\n\"\n"
".ascii \" filename = os.path.join(dirname, 'zend_vm_opcodes.h')\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" opcodes = {}\\n\"\n"
".ascii \" found_nop = False\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" with open(filename, 'r') as file:\\n\"\n"
".ascii \" content = file.read()\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" pattern = re.compile(r'#define (ZEND_[^\\\\s]+)\\\\s+([0-9]+)')\\n\"\n"
".ascii \" matches = pattern.findall(content)\\n\"\n"
".ascii \" for name, number in matches:\\n\"\n"
".ascii \" if not found_nop:\\n\"\n"
".ascii \" if name == 'ZEND_NOP':\\n\"\n"
".ascii \" found_nop = True\\n\"\n"
".ascii \" else:\\n\"\n"
".ascii \" continue\\n\"\n"
".ascii \" if name == 'ZEND_VM_LAST_OPCODE':\\n\"\n"
".ascii \" break\\n\"\n"
".ascii \" opcodes[name] = int(number)\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" self._opcodes = opcodes\\n\"\n"
".ascii \"\\n\"\n"
".ascii \"class ZendRefTypeInfo:\\n\"\n"
".ascii \" _bits = None\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" @classmethod\\n\"\n"
".ascii \" def flag_name(self, bit):\\n\"\n"
".ascii \" return self._flag_name_in(bit, self._bits)\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" @classmethod\\n\"\n"
".ascii \" def _flag_name_in(self, bit, bits):\\n\"\n"
".ascii \" for name in bits:\\n\"\n"
".ascii \" if bit == bits[name]:\\n\"\n"
".ascii \" return name\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" @classmethod\\n\"\n"
".ascii \" def bit(self, name):\\n\"\n"
".ascii \" return self._bits.get(name)\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" @classmethod\\n\"\n"
".ascii \" def format(self, flags):\\n\"\n"
".ascii \" self._load()\\n\"\n"
".ascii \" names = []\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" type = flags & self._type_mask\\n\"\n"
".ascii \" type_name = ZendTypeBits.zvalTypeName(type)\\n\"\n"
".ascii \" if type_name is not None:\\n\"\n"
".ascii \" names.append('IS_%s' % type_name.upper())\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" bits = self._bits\\n\"\n"
".ascii \" type_bits = None\\n\"\n"
".ascii \" match type_name:\\n\"\n"
".ascii \" case 'string':\\n\"\n"
".ascii \" type_bits = self._str_bits\\n\"\n"
".ascii \" case 'array':\\n\"\n"
".ascii \" type_bits = self._array_bits\\n\"\n"
".ascii \" case 'object':\\n\"\n"
".ascii \" type_bits = self._obj_bits\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" type_flags = flags & self._flags_mask\\n\"\n"
".ascii \" for i in range(0, 31):\\n\"\n"
".ascii \" if (1<<i) > type_flags:\\n\"\n"
".ascii \" break\\n\"\n"
".ascii \" if (type_flags & (1<<i)) != 0:\\n\"\n"
".ascii \" name = self.flag_name(i)\\n\"\n"
".ascii \" if type_bits is not None:\\n\"\n"
".ascii \" name2 = self._flag_name_in(i, type_bits)\\n\"\n"
".ascii \" if name2 is not None:\\n\"\n"
".ascii \" if name is not None:\\n\"\n"
".ascii \" names.append('%s(%s)' % (name2, name))\\n\"\n"
".ascii \" else:\\n\"\n"
".ascii \" names.append(name2)\\n\"\n"
".ascii \" continue\\n\"\n"
".ascii \" if name is not None:\\n\"\n"
".ascii \" names.append(name)\\n\"\n"
".ascii \" else:\\n\"\n"
".ascii \" names.append('(1 << %d)' % (i))\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" if (flags & (1<<self.bit('GC_NOT_COLLECTABLE'))) == 0:\\n\"\n"
".ascii \" gc_color = (flags >> self._info_shift) & self._gc_color\\n\"\n"
".ascii \" match gc_color:\\n\"\n"
".ascii \" case self._gc_black:\\n\"\n"
".ascii \" names.append('GC_BLACK')\\n\"\n"
".ascii \" case self._gc_white:\\n\"\n"
".ascii \" names.append('GC_WHITE')\\n\"\n"
".ascii \" case self._gc_grey:\\n\"\n"
".ascii \" names.append('GC_GREY')\\n\"\n"
".ascii \" case self._gc_purple:\\n\"\n"
".ascii \" names.append('GC_PURPLE')\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" gc_address = (flags >> self._info_shift) & self._gc_address\\n\"\n"
".ascii \" if gc_address != 0:\\n\"\n"
".ascii \" names.append('GC_ADDRESS(%d)' % gc_address)\\n\"\n"
".ascii \" else:\\n\"\n"
".ascii \" info = flags & self._info_mask\\n\"\n"
".ascii \" if info != 0:\\n\"\n"
".ascii \" names.append('GC_INFO(%d)' % info)\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" return ' | '.join(names)\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" @classmethod\\n\"\n"
".ascii \" def _load(self):\\n\"\n"
".ascii \" if self._bits != None:\\n\"\n"
".ascii \" return\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" dirname = detect_source_dir()\\n\"\n"
".ascii \" filename = os.path.join(dirname, 'zend_types.h')\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" bits = {}\\n\"\n"
".ascii \" str_bits = {}\\n\"\n"
".ascii \" array_bits = {}\\n\"\n"
".ascii \" obj_bits = {}\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" with open(filename, 'r') as file:\\n\"\n"
".ascii \" content = file.read()\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" # GC_NOT_COLLECTABLE (1<<4)\\n\"\n"
".ascii \" pattern = re.compile(r'#define (GC_[^\\\\s]+)\\\\s+\\\\(\\\\s*1\\\\s*<<\\\\s*([0-9]+)\\\\s*\\\\)')\\n\"\n"
".ascii \" matches = pattern.findall(content)\\n\"\n"
".ascii \" for name, bit in matches:\\n\"\n"
".ascii \" bits[name] = int(bit)\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" # GC_TYPE_MASK 0x0000000f\\n\"\n"
".ascii \" # GC_INFO_SHIFT 10\\n\"\n"
".ascii \" pattern = re.compile(r'#define (GC_[^\\\\s]+)\\\\s+((0x)\?[0-9a-f]+)')\\n\"\n"
".ascii \" matches = pattern.findall(content)\\n\"\n"
".ascii \" for name, bit, _ in matches:\\n\"\n"
".ascii \" match name:\\n\"\n"
".ascii \" case 'GC_TYPE_MASK':\\n\"\n"
".ascii \" self._type_mask = int(bit, 0)\\n\"\n"
".ascii \" case 'GC_FLAGS_MASK':\\n\"\n"
".ascii \" self._flags_mask = int(bit, 0)\\n\"\n"
".ascii \" case 'GC_INFO_MASK':\\n\"\n"
".ascii \" self._info_mask = int(bit, 0)\\n\"\n"
".ascii \" case 'GC_INFO_SHIFT':\\n\"\n"
".ascii \" self._info_shift = int(bit, 0)\\n\"\n"
".ascii \" case 'GC_FLAGS_SHIFT':\\n\"\n"
".ascii \" self._flags_shift = int(bit, 0)\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" # IS_STR_INTERNED GC_IMMUTABLE\\n\"\n"
".ascii \" # IS_STR_PERMANENT (1<<8)\\n\"\n"
".ascii \" pattern = re.compile(r'#define (IS_(STR|ARRAY|OBJ)_[^\\\\s]+)\\\\s+(\\\\(\\\\s*1\\\\s*<<\\\\s*([0-9]+)\\\\s*\\\\)|GC_[a-zA-Z_]+)')\\n\"\n"
".ascii \" matches = pattern.findall(content)\\n\"\n"
".ascii \" for name, type, val, bit in matches:\\n\"\n"
".ascii \" if bit == '':\\n\"\n"
".ascii \" bit = bits.get(val)\\n\"\n"
".ascii \" if bit == None:\\n\"\n"
".ascii \" continue\\n\"\n"
".ascii \" match type:\\n\"\n"
".ascii \" case 'STR':\\n\"\n"
".ascii \" target = str_bits\\n\"\n"
".ascii \" case 'ARRAY':\\n\"\n"
".ascii \" target = array_bits\\n\"\n"
".ascii \" case 'OBJ':\\n\"\n"
".ascii \" target = obj_bits\\n\"\n"
".ascii \" target[name] = int(bit)\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" # Hard coded because these are not exposed in header files\\n\"\n"
".ascii \" self._gc_address = 0x0fffff\\n\"\n"
".ascii \" self._gc_color = 0x300000\\n\"\n"
".ascii \" self._gc_black = 0x000000\\n\"\n"
".ascii \" self._gc_white = 0x100000\\n\"\n"
".ascii \" self._gc_grey = 0x200000\\n\"\n"
".ascii \" self._gc_purple = 0x300000\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" self._bits = bits\\n\"\n"
".ascii \" self._str_bits = str_bits\\n\"\n"
".ascii \" self._array_bits = array_bits\\n\"\n"
".ascii \" self._obj_bits = obj_bits\\n\"\n"
".ascii \"\\n\"\n"
".ascii \"def detect_source_dir():\\n\"\n"
".ascii \" (symbol,_) = gdb.lookup_symbol(\\\"zend_visibility_to_set_visibility\\\")\\n\"\n"
".ascii \" if symbol == None:\\n\"\n" ".ascii \" if symbol == None:\\n\"\n"
".ascii \" raise Exception(\\\"Could not find zend_types.h: symbol zend_gc_refcount not found\\\")\\n\"\n" ".ascii \" raise Exception(\\\"Could not find zend_compile.h: symbol zend_visibility_to_set_visibility not found\\\")\\n\"\n"
".ascii \" filename = symbol.symtab.fullname()\\n\"\n" ".ascii \" filename = symbol.symtab.fullname()\\n\"\n"
".ascii \"\\n\"\n" ".ascii \" dirname = os.path.dirname(filename)\\n\"\n"
".ascii \" bits = {}\\n\"\n" ".ascii \" return dirname\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" with open(filename, 'r') as file:\\n\"\n"
".ascii \" content = file.read()\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" pattern = re.compile(r'#define _ZEND_TYPE_([^\\\\s]+)_BIT\\\\s+\\\\(1u << (\\\\d+)\\\\)')\\n\"\n"
".ascii \" matches = pattern.findall(content)\\n\"\n"
".ascii \" for name, bit in matches:\\n\"\n"
".ascii \" bits[int(bit)] = name.lower()\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" pattern = re.compile(r'#define IS_([^\\\\s]+)\\\\s+(\\\\d+)')\\n\"\n"
".ascii \" matches = pattern.findall(content)\\n\"\n"
".ascii \" for name, bit in matches:\\n\"\n"
".ascii \" if not int(bit) in bits:\\n\"\n"
".ascii \" bits[int(bit)] = name.lower()\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" types = {}\\n\"\n"
".ascii \" for bit in bits:\\n\"\n"
".ascii \" types[bits[bit]] = bit\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" type_bit_to_name = bits\\n\"\n"
".ascii \" type_name_to_bit = types\\n\"\n"
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
".ascii \"def lookup_symbol(name):\\n\"\n" ".ascii \"def lookup_symbol(name):\\n\"\n"
".ascii \" (symbol, _) = gdb.lookup_symbol(name)\\n\"\n" ".ascii \" (symbol, _) = gdb.lookup_symbol(name)\\n\"\n"
@ -1065,6 +1630,51 @@ asm(
".ascii \" # array types have a single field whose type represents its range\\n\"\n" ".ascii \" # array types have a single field whose type represents its range\\n\"\n"
".ascii \" return ary_type.fields()[0].type.range()[1]+1\\n\"\n" ".ascii \" return ary_type.fields()[0].type.range()[1]+1\\n\"\n"
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
".ascii \"def format_zstr(zstr):\\n\"\n"
".ascii \" len = int(zstr['len'])\\n\"\n"
".ascii \" truncated = False\\n\"\n"
".ascii \" if len > 200:\\n\"\n"
".ascii \" len = 200\\n\"\n"
".ascii \" truncated = True\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" ptr_type = gdb.lookup_type('char').pointer()\\n\"\n"
".ascii \" ary_type = gdb.lookup_type('char').array(len)\\n\"\n"
".ascii \" str = zstr['val'].cast(ptr_type).dereference().cast(ary_type)\\n\"\n"
".ascii \" str = str.format_string()\\n\"\n"
".ascii \" if truncated:\\n\"\n"
".ascii \" str += ' (%d bytes total)' % int(zstr['len'])\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" return str\\n\"\n"
".ascii \"\\n\"\n"
".ascii \"def format_nested(value):\\n\"\n"
".ascii \" orig_value = value\\n\"\n"
".ascii \" type = value.type\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" # Null pointers\\n\"\n"
".ascii \" if type.code == gdb.TYPE_CODE_PTR and int(value) == 0:\\n\"\n"
".ascii \" return orig_value\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" addr = orig_value.address\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" while type.code == gdb.TYPE_CODE_PTR:\\n\"\n"
".ascii \" addr = int(value)\\n\"\n"
".ascii \" type = type.target()\\n\"\n"
".ascii \" try:\\n\"\n"
".ascii \" value = value.dereference()\\n\"\n"
".ascii \" except:\\n\"\n"
".ascii \" pass\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" type = gdb.types.get_basic_type(type)\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" if type.tag and re.match(r'^_zend_string$', type.tag):\\n\"\n"
".ascii \" return format_zstr(value)\\n\"\n"
".ascii \" elif type.tag and re.match(r'^_zend_class_entry$', type.tag):\\n\"\n"
".ascii \" return '((zend_class_entry*)0x%x) %s' % (addr, format_zstr(value['name']))\\n\"\n"
".ascii \" elif type.tag and re.match(r'^_zend_array$', type.tag):\\n\"\n"
".ascii \" return '((zend_array*)0x%x) array(%d)' % (addr, value['nNumOfElements'])\\n\"\n"
".ascii \"\\n\"\n"
".ascii \" return orig_value\\n\"\n"
".ascii \"\\n\"\n"
".ascii \"gdb.printing.register_pretty_printer(gdb, pp_set, replace=True)\\n\"\n" ".ascii \"gdb.printing.register_pretty_printer(gdb, pp_set, replace=True)\\n\"\n"
".ascii \"\\n\"\n" ".ascii \"\\n\"\n"
".byte 0\n" ".byte 0\n"

View file

@ -40,7 +40,7 @@ $ccode = sprintf(
basename(__FILE__), basename(__FILE__),
implode("\n ", array_map(function ($line) { implode("\n ", array_map(function ($line) {
$escapedPy = addcslashes($line."\n", "\"\n\\"); $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); return sprintf('"%s"', $escapedAsm);
}, explode("\n", $pyscript))), }, explode("\n", $pyscript))),
); );

View file

@ -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 Use |set print max-depth| to control the maximum print depth for nested
structures: structures:
(gdb) set print pretty on (gdb) set print max-depth 5
To interactively type Python for development of the printers, use To interactively type Python for development of the printers, use
(gdb) python foo = gdb.parse_and_eval('bar') (gdb) python foo = gdb.parse_and_eval('bar')
@ -29,6 +29,8 @@ Then you can interact with that variable:
import gdb import gdb
import re import re
import traceback
import os
pp_set = gdb.printing.RegexpCollectionPrettyPrinter("php") pp_set = gdb.printing.RegexpCollectionPrettyPrinter("php")
@ -39,53 +41,65 @@ class ZendStringPrettyPrinter(gdb.printing.PrettyPrinter):
self.val = val self.val = val
def to_string(self): def to_string(self):
return self.format_string() return '((zend_string*) 0x%x) %s' % (self.val.address, format_zstr(self.val))
def children(self): def children(self):
for field in self.val.type.fields(): for field in self.val.type.fields():
if field.name == 'val': if field.name == 'val':
yield ('val', self.format_string()) yield ('val', format_zstr(self.val))
else: else:
yield (field.name, self.val[field.name]) yield (field.name, format_nested(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
pp_set.add_printer('zend_string', '^_zend_string$', ZendStringPrettyPrinter) pp_set.add_printer('zend_string', '^_zend_string$', ZendStringPrettyPrinter)
def zendStringPointerPrinter(ptr): class ZendObjectPrettyPrinter(gdb.printing.PrettyPrinter):
"Given a pointer to a zend_string, show the contents (if non-NULL)" "Print a zend_object"
if int(ptr) == 0:
return '0x0' def __init__(self, val):
return ZendStringPrettyPrinter(ptr.dereference()).to_string() 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): class ZendTypePrettyPrinter(gdb.printing.PrettyPrinter):
"Print a zend_type" "Print a zend_type"
def __init__(self, val): def __init__(self, val):
self.val = val self.val = val
load_type_bits()
def to_string(self): 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): def children(self):
for field in self.val.type.fields(): 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): def format_type(self, t):
type_mask = int(t['type_mask']) type_mask = int(t['type_mask'])
@ -95,7 +109,7 @@ class ZendTypePrettyPrinter(gdb.printing.PrettyPrinter):
meta = [] meta = []
for bit in range(0, type_mask_size): for bit in range(0, type_mask_size):
if type_mask & (1 << bit): if type_mask & (1 << bit):
type_name = type_bit_to_name.get(bit) type_name = ZendTypeBits.zendTypeName(bit)
match type_name: match type_name:
case None: case None:
parts.append('(1<<%d)' % bit) parts.append('(1<<%d)' % bit)
@ -115,7 +129,7 @@ class ZendTypePrettyPrinter(gdb.printing.PrettyPrinter):
separator = '&' separator = '&'
case 'name': case 'name':
str = t['ptr'].cast(gdb.lookup_type('zend_string').pointer()) str = t['ptr'].cast(gdb.lookup_type('zend_string').pointer())
parts.append(ZendStringPrettyPrinter(str).to_string()) parts.append(format_zstr(str))
case _: case _:
parts.append(type_name) parts.append(type_name)
@ -166,11 +180,11 @@ class ZendAstPrettyPrinter(gdb.printing.PrettyPrinter):
c = c.dereference() c = c.dereference()
yield ('child[%d]' % i, c) yield ('child[%d]' % i, c)
elif field.name == 'name': 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': elif field.name == 'val':
yield (field.name, ZvalPrettyPrinter(val[field.name]).to_string()) yield (field.name, ZvalPrettyPrinter(val[field.name]).to_string())
else: else:
yield (field.name, val[field.name]) yield (field.name, format_nested(self.val[field.name]))
def is_special(self): def is_special(self):
special_shift = 6 # ZEND_AST_SPECIAL_SHIFT special_shift = 6 # ZEND_AST_SPECIAL_SHIFT
@ -223,37 +237,46 @@ class ZvalPrettyPrinter(gdb.printing.PrettyPrinter):
def __init__(self, val): def __init__(self, val):
self.val = val self.val = val
load_type_bits()
def to_string(self): 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): def value_to_string(self):
t = int(self.val['u1']['v']['type']) t = int(self.val['u1']['v']['type'])
if t == type_name_to_bit['undef']: if t == ZendTypeBits.bit('undef'):
return 'undef' return 'undef'
elif t == type_name_to_bit['null']: elif t == ZendTypeBits.bit('null'):
return 'null' return 'null'
elif t == type_name_to_bit['false']: elif t == ZendTypeBits.bit('false'):
return 'false' return 'false'
elif t == type_name_to_bit['true']: elif t == ZendTypeBits.bit('true'):
return 'true' return 'true'
elif t == type_name_to_bit['long']: elif t == ZendTypeBits.bit('long'):
return str(self.val['value']['lval']) return str(self.val['value']['lval'])
elif t == type_name_to_bit['double']: elif t == ZendTypeBits.bit('double'):
return str(self.val['value']['dval']) return str(self.val['value']['dval'])
elif t == type_name_to_bit['string']: elif t == ZendTypeBits.bit('string'):
return ZendStringPrettyPrinter(self.val['value']['str'].dereference()).to_string() return format_zstr(self.val['value']['str'])
elif t == type_name_to_bit['array']: elif t == ZendTypeBits.bit('array'):
return 'array' return 'array'
elif t == type_name_to_bit['object']: elif t == ZendTypeBits.bit('object'):
return 'object(%s)' % ZendStringPrettyPrinter(self.val['value']['obj']['ce']['name'].dereference()).to_string() return 'object(%s)' % format_zstr(self.val['value']['obj']['ce']['name'])
elif t == type_name_to_bit['resource']: elif t == ZendTypeBits.bit('resource'):
return 'resource' return 'resource'
elif t == type_name_to_bit['reference']: elif t == ZendTypeBits.bit('reference'):
return 'reference' return 'reference'
elif t == type_name_to_bit['constant_ast']: elif t == ZendTypeBits.bit('constant_ast'):
return '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: else:
return 'zval of type %d' % int(self.val['u1']['v']['type']) 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(): for field in self.val.type.fields():
if field.name == 'value': if field.name == 'value':
value = self.val['value'] value = self.val['value']
sub_field = 'ptr'
t = int(self.val['u1']['v']['type']) t = int(self.val['u1']['v']['type'])
if t == type_name_to_bit['undef']: if t == ZendTypeBits.bit('undef'):
value = value['lval'] sub_field = 'lval'
elif t == type_name_to_bit['null']: elif t == ZendTypeBits.bit('null'):
value = value['lval'] sub_field = 'lval'
elif t == type_name_to_bit['false']: elif t == ZendTypeBits.bit('false'):
value = value['lval'] sub_field = 'lval'
elif t == type_name_to_bit['true']: elif t == ZendTypeBits.bit('true'):
value = value['lval'] sub_field = 'lval'
elif t == type_name_to_bit['long']: elif t == ZendTypeBits.bit('long'):
value = value['lval'] sub_field = 'lval'
elif t == type_name_to_bit['double']: elif t == ZendTypeBits.bit('double'):
value = value['dval'] sub_field = 'dval'
elif t == type_name_to_bit['string']: elif t == ZendTypeBits.bit('string'):
value = value['str'].dereference() sub_field = 'str'
elif t == type_name_to_bit['array']: elif t == ZendTypeBits.bit('array'):
value = value['ht'].dereference() sub_field = 'arr'
elif t == type_name_to_bit['object']: elif t == ZendTypeBits.bit('object'):
value = value['obj'].dereference() sub_field = 'obj'
elif t == type_name_to_bit['resource']: elif t == ZendTypeBits.bit('resource'):
value = value['res'].dereference() sub_field = 'res'
elif t == type_name_to_bit['reference']: elif t == ZendTypeBits.bit('reference'):
value = value['ref'].dereference() sub_field = 'ref'
elif t == type_name_to_bit['constant_ast']: elif t == ZendTypeBits.bit('constant_ast'):
value = value['ast'].dereference() sub_field = 'ast'
else: elif t == ZendTypeBits.bit('indirect'):
value = value['ptr'] sub_field = 'zv'
yield (field.name, value) 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': elif field.name == 'u2':
yield ('u2', self.val[field.name]['extra']) yield ('u2', self.val[field.name]['extra'])
else: 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) 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): class ZendClassEntryPrettyPrinter(gdb.printing.PrettyPrinter):
"Print a zend_class_entry" "Print a zend_class_entry"
# String pointers, show the string contents if possible
STRING_FIELDS = [ 'name', 'doc_comment' ]
def __init__(self, val): def __init__(self, val):
self.val = val self.val = val
def to_string(self): 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): def children(self):
for field in self.val.type.fields(): for field in self.val.type.fields():
if field.name is not None: if field.name is not None:
if field.name in self.STRING_FIELDS: if field.name == 'ce_flags':
yield (field.name, zendStringPointerPrinter(self.val[field.name])) flags = self.val[field.name]
yield (field.name, '%d (%s)' % (flags, ZendAccFlags.format_ce_flags(flags)))
else: else:
yield (field.name, self.val[field.name]) yield (field.name, format_nested(self.val[field.name]))
else: else:
# Don't break on the union fields. Unfortunately, pretty # Don't break on the union fields. Unfortunately, pretty
# printers done in python cannot match the default formatting of # printers done in python cannot match the default formatting of
@ -334,52 +359,592 @@ class ZendClassConstantPrettyPrinter(gdb.printing.PrettyPrinter):
def children(self): def children(self):
for field in self.val.type.fields(): for field in self.val.type.fields():
if field.name == 'doc_comment': if field.name == 'value':
yield ('doc_comment', zendStringPointerPrinter(self.val['doc_comment'])) flags = self.val[field.name]['u2']['constant_flags']
elif field.name == 'ce': yield ('value.u2.constant_flags', '%d (%s)' % (flags, ZendAccFlags.format_const_flags(flags)))
yield ('ce', zendStringPointerPrinter(self.val['ce']['name']))
else:
yield (field.name, self.val[field.name]) 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) pp_set.add_printer('zend_class_constant', '^_zend_class_constant$', ZendClassConstantPrettyPrinter)
type_bit_to_name = None class ZendPropertyInfoPrettyPrinter(gdb.printing.PrettyPrinter):
type_name_to_bit = None "Print a zend_property_info"
def load_type_bits(): def __init__(self, val):
global type_bit_to_name self.val = val
global type_name_to_bit
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 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: 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() filename = symbol.symtab.fullname()
dirname = os.path.dirname(filename)
bits = {} return dirname
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
def lookup_symbol(name): def lookup_symbol(name):
(symbol, _) = gdb.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 # array types have a single field whose type represents its range
return ary_type.fields()[0].type.range()[1]+1 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) gdb.printing.register_pretty_printer(gdb, pp_set, replace=True)