From 9903a88bc4fd660146e3b61a452c4220fbde8e9e Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 21 Feb 2024 19:26:56 +0100 Subject: [PATCH 1/2] Add GDB pretty-printers for zend_string, zend_type --- .gdb.py | 140 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 .gdb.py diff --git a/.gdb.py b/.gdb.py new file mode 100644 index 0000000000000..6561810cdfcf5 --- /dev/null +++ b/.gdb.py @@ -0,0 +1,140 @@ + +"""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(): + 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']) + 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) From c37f4dd768649cefc82a8ee9fd84a8f93b861306 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Tue, 5 Mar 2024 12:35:57 +0100 Subject: [PATCH 2/2] Do not print string twice --- .gdb.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.gdb.py b/.gdb.py index 6561810cdfcf5..f4054519f5f27 100644 --- a/.gdb.py +++ b/.gdb.py @@ -32,10 +32,7 @@ def to_string(self): def children(self): for field in self.val.type.fields(): - if field.name == 'val': - yield ('val', self.format_string()) - else: - yield (field.name, self.val[field.name]) + yield (field.name, self.val[field.name]) def format_string(self): len = int(self.val['len'])