Skip to content

Add GDB pretty-printers for zend_string, zend_type #13463

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 2 commits 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
137 changes: 137 additions & 0 deletions .gdb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@

"""GDB support for PHP types

Add this to your gdb by amending your ~/.gdbinit as follows:

source .gdb.py

Use
(gdb) p /r any_variable
to print |any_variable| without using any printers.

To interactively type Python for development of the printers, use
(gdb) python foo = gdb.parse_and_eval('bar')
to put the C value 'bar' in the current scope into a Python variable 'foo'.
Then you can interact with that variable:
(gdb) python print foo['impl_']
"""

import gdb
import re

pp_set = gdb.printing.RegexpCollectionPrettyPrinter("php")

class ZendStringPrettyPrinter(gdb.printing.PrettyPrinter):
"Print a zend_string"

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

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

def children(self):
for field in self.val.type.fields():
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
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()

def to_string(self):
return self.format_type(self.val)

def children(self):
for field in self.val.type.fields():
yield (field.name, self.val[field.name])

def format_type(self, t):
type_mask = int(t['type_mask'])
type_mask_size = t['type_mask'].type.sizeof * 8
separator = '|'
parts = []
meta = []
for bit in range(0, type_mask_size):
if type_mask & (1 << bit):
type_name = self.bits.get(bit)
match type_name:
case None:
parts.append('(1<<%d)' % bit)
case 'list':
list = t['ptr'].cast(gdb.lookup_type('zend_type_list').pointer())
num_types = int(list['num_types'])
types = list['types'].dereference().cast(gdb.lookup_type('zend_type').array(num_types))
for i in range(0, num_types):
str = self.format_type(types[i])
if any((c in set('|&()')) for c in str):
str = '(%s)' % str
parts.append(str)
case 'union' | 'arena':
meta.append(type_name)
case 'intersection':
meta.append(type_name)
separator = '&'
case 'name':
str = t['ptr'].cast(gdb.lookup_type('zend_string').pointer())
parts.append(ZendStringPrettyPrinter(str).to_string())
case _:
parts.append(type_name)

str = separator.join(parts)

if len(meta) > 0:
str = '[%s] %s' % (','.join(meta), str)

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 = {}

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

self.bits = bits
self.types = types
pp_set.add_printer('zend_type', '^zend_type$', ZendTypePrettyPrinter)

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