Skip to content

Add GDB pretty-printer for zend_ast #13520

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 12, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
256 changes: 229 additions & 27 deletions .gdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@

source .gdb.py

Use
If needed, pretty printers can be by-passed by using the /r flag:
(gdb) p /r any_variable
to print |any_variable| without using any printers.

Use |set print pretty| to enable multi-line printing and indentation:
(gdb) set print pretty on

Use |set print max-depth| to control the maximum print depth for nested
structures:
(gdb) set print pretty on

To interactively type Python for development of the printers, use
(gdb) python foo = gdb.parse_and_eval('bar')
Expand All @@ -32,7 +38,10 @@ def to_string(self):

def children(self):
for field in self.val.type.fields():
yield (field.name, self.val[field.name])
if field.name == 'val':
yield ('val', self.format_string())
else:
yield (field.name, self.val[field.name])

def format_string(self):
len = int(self.val['len'])
Expand All @@ -49,14 +58,16 @@ def format_string(self):
str += ' (%d bytes total)' % int(self.val['len'])

return str


pp_set.add_printer('zend_string', '^_zend_string$', ZendStringPrettyPrinter)

class ZendTypePrettyPrinter(gdb.printing.PrettyPrinter):
"Print a zend_type"

def __init__(self, val):
self.val = val
self.load_bits()
load_type_bits()

def to_string(self):
return self.format_type(self.val)
Expand All @@ -73,7 +84,7 @@ def format_type(self, t):
meta = []
for bit in range(0, type_mask_size):
if type_mask & (1 << bit):
type_name = self.bits.get(bit)
type_name = type_bit_to_name.get(bit)
match type_name:
case None:
parts.append('(1<<%d)' % bit)
Expand Down Expand Up @@ -104,34 +115,225 @@ def format_type(self, t):

return str

def load_bits(self):
(symbol,_) = gdb.lookup_symbol("zend_gc_refcount")
if symbol == None:
raise "Could not find zend_types.h: symbol zend_gc_refcount not found"
filename = symbol.symtab.fullname()

bits = {}
pp_set.add_printer('zend_type', '^zend_type$', ZendTypePrettyPrinter)

class ZendAstKindPrettyPrinter(gdb.printing.PrettyPrinter):
"Print a zend_ast_kind"

def __init__(self, val):
self.val = val

def to_string(self):
return self.val.cast(gdb.lookup_type('enum _zend_ast_kind'))


pp_set.add_printer('zend_ast_kind', '^zend_ast_kind$', ZendAstKindPrettyPrinter)

class ZendAstPrettyPrinter(gdb.printing.PrettyPrinter):
"Print a zend_ast"

def __init__(self, val):
self.val = val

def to_string(self):
return '((%s*)0x%x)' % (str(self.cast().type), self.val.address)

def children(self):
val = self.cast()
for field in val.type.fields():
if field.name == 'child':
children = val[field.name]
num_children = self.num_children()

ptr_type = gdb.lookup_type('zend_ast').pointer().pointer()
children = children.cast(ptr_type)

for i in range(0, num_children):
c = children[i]
if int(c) != 0:
c = c.dereference()
yield ('child[%d]' % i, c)
elif field.name == 'name':
yield (field.name, ZendStringPrettyPrinter(val[field.name].dereference()).to_string())
elif field.name == 'val':
yield (field.name, ZvalPrettyPrinter(val[field.name]).to_string())
else:
yield (field.name, val[field.name])

def is_special(self):
special_shift = 6 # ZEND_AST_SPECIAL_SHIFT
kind = self.val['kind']
return (kind >> special_shift) & 1

def is_decl(self):
return self.is_special() and int(self.val['kind']) >= enum_value('ZEND_AST_FUNC_DECL')

def is_list(self):
list_shift = 7 # ZEND_AST_IS_LIST_SHIFT
kind = self.val['kind']
return (kind >> list_shift) & 1

with open(filename, 'r') as file:
content = file.read()
def cast(self):
kind = int(self.val['kind'])

pattern = re.compile(r'#define _ZEND_TYPE_([^\s]+)_BIT\s+\(1u << (\d+)\)')
matches = pattern.findall(content)
for name, bit in matches:
if kind == enum_value('ZEND_AST_ZVAL') or kind == enum_value('ZEND_AST_CONSTANT'):
return self.val.cast(gdb.lookup_type('zend_ast_zval'))
if kind == enum_value('ZEND_AST_ZNODE'):
return self.val.cast(gdb.lookup_type('zend_ast_znode'))
if self.is_decl():
return self.val.cast(gdb.lookup_type('zend_ast_decl'))
if self.is_list():
return self.val.cast(gdb.lookup_type('zend_ast_list'))

return self.val

def num_children(self):
if self.is_decl():
decl_type = gdb.lookup_type('zend_ast_decl')
child_type = decl_type['child'].type
return array_size(child_type)
if self.is_special():
return 0
elif self.is_list():
return int(self.cast()['children'])
else:
num_children_shift = 8 # ZEND_AST_NUM_CHILDREN_SHIFT
kind = self.val['kind']
return kind >> num_children_shift


pp_set.add_printer('zend_ast', '^_zend_ast$', ZendAstPrettyPrinter)

class ZvalPrettyPrinter(gdb.printing.PrettyPrinter):
"Print a zval"

def __init__(self, val):
self.val = val
load_type_bits()

def to_string(self):
return self.value_to_string()

def value_to_string(self):
t = int(self.val['u1']['v']['type'])
if t == type_name_to_bit['undef']:
return 'undef'
elif t == type_name_to_bit['null']:
return 'null'
elif t == type_name_to_bit['false']:
return 'false'
elif t == type_name_to_bit['true']:
return 'true'
elif t == type_name_to_bit['long']:
return str(self.val['value']['lval'])
elif t == type_name_to_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']:
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']:
return 'resource'
elif t == type_name_to_bit['reference']:
return 'reference'
elif t == type_name_to_bit['constant_ast']:
return 'constant_ast'
else:
return 'zval of type %d' % int(self.val['u1']['v']['type'])

def children(self):
for field in self.val.type.fields():
if field.name == 'value':
value = self.val['value']
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)
elif field.name == 'u2':
yield ('u2', self.val[field.name]['extra'])
else:
yield (field.name, self.val[field.name])


pp_set.add_printer('zval', '^_zval_struct$', ZvalPrettyPrinter)

type_bit_to_name = None
type_name_to_bit = None

def load_type_bits():
global type_bit_to_name
global type_name_to_bit

if type_bit_to_name != None:
return

(symbol,_) = gdb.lookup_symbol("zend_gc_refcount")
if symbol == None:
raise "Could not find zend_types.h: symbol zend_gc_refcount 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()

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

types = {}
for bit in bits:
types[bits[bit]] = bit
type_bit_to_name = bits
type_name_to_bit = types

self.bits = bits
self.types = types
pp_set.add_printer('zend_type', '^zend_type$', ZendTypePrettyPrinter)
def lookup_symbol(name):
(symbol, _) = gdb.lookup_symbol(name)
if symbol == None:
raise Exception("Could not lookup symbol %s" % name)
return symbol

def enum_value(name):
symbol = lookup_symbol(name)
return int(symbol.value())

def array_size(ary_type):
# array types have a single field whose type represents its range
return ary_type.fields()[0].type.range()[1]+1

gdb.printing.register_pretty_printer(gdb, pp_set, replace=True)