xref: /php-src/scripts/gdb/php_gdb.py (revision e34eebb8)
1
2"""GDB support for PHP types
3
4This is auto-loaded by GDB if Python Auto-loading is enabled (the default), and
5the PHP binary is in the auto load safe path:
6
7    # ~/.config/gdb/gdbinit (not ~/.gdbinit)
8    add-auto-load-safe-path /path/to/php-src
9
10See https://sourceware.org/gdb/current/onlinedocs/gdb.html/Python-Auto_002dloading.html
11See https://sourceware.org/gdb/current/onlinedocs/gdb.html/Auto_002dloading-safe-path.html#Auto_002dloading-safe-path
12
13If needed, pretty printers can be by-passed by using the /r flag:
14  (gdb) p /r any_variable
15
16Use |set print pretty| to enable multi-line printing and indentation:
17  (gdb) set print pretty on
18
19Use |set print max-depth| to control the maximum print depth for nested
20structures:
21  (gdb) set print pretty on
22
23To interactively type Python for development of the printers, use
24  (gdb) python foo = gdb.parse_and_eval('bar')
25to put the C value 'bar' in the current scope into a Python variable 'foo'.
26Then you can interact with that variable:
27  (gdb) python print foo['impl_']
28"""
29
30import gdb
31import re
32
33pp_set = gdb.printing.RegexpCollectionPrettyPrinter("php")
34
35class ZendStringPrettyPrinter(gdb.printing.PrettyPrinter):
36    "Print a zend_string"
37
38    def __init__(self, val):
39        self.val = val
40
41    def to_string(self):
42        return self.format_string()
43
44    def children(self):
45        for field in self.val.type.fields():
46            if field.name == 'val':
47                yield ('val', self.format_string())
48            else:
49                yield (field.name, self.val[field.name])
50
51    def format_string(self):
52        len = int(self.val['len'])
53        truncated = False
54        if len > 50:
55            len = 50
56            truncated = True
57
58        ptr_type = gdb.lookup_type('char').pointer()
59        ary_type = gdb.lookup_type('char').array(len)
60        str = self.val['val'].cast(ptr_type).dereference().cast(ary_type)
61        str = str.format_string()
62        if truncated:
63            str += ' (%d bytes total)' % int(self.val['len'])
64
65        return str
66
67
68pp_set.add_printer('zend_string', '^_zend_string$', ZendStringPrettyPrinter)
69
70def zendStringPointerPrinter(ptr):
71    "Given a pointer to a zend_string, show the contents (if non-NULL)"
72    if int(ptr) == 0:
73        return '0x0'
74    return ZendStringPrettyPrinter(ptr.dereference()).to_string()
75
76class ZendTypePrettyPrinter(gdb.printing.PrettyPrinter):
77    "Print a zend_type"
78
79    def __init__(self, val):
80        self.val = val
81        load_type_bits()
82
83    def to_string(self):
84        return self.format_type(self.val)
85
86    def children(self):
87        for field in self.val.type.fields():
88            yield (field.name, self.val[field.name])
89
90    def format_type(self, t):
91        type_mask = int(t['type_mask'])
92        type_mask_size = t['type_mask'].type.sizeof * 8
93        separator = '|'
94        parts = []
95        meta = []
96        for bit in range(0, type_mask_size):
97            if type_mask & (1 << bit):
98                type_name = type_bit_to_name.get(bit)
99                match type_name:
100                    case None:
101                        parts.append('(1<<%d)' % bit)
102                    case 'list':
103                        list = t['ptr'].cast(gdb.lookup_type('zend_type_list').pointer())
104                        num_types = int(list['num_types'])
105                        types = list['types'].dereference().cast(gdb.lookup_type('zend_type').array(num_types))
106                        for i in range(0, num_types):
107                            str = self.format_type(types[i])
108                            if any((c in set('|&()')) for c in str):
109                                str = '(%s)' % str
110                            parts.append(str)
111                    case 'union' | 'arena':
112                        meta.append(type_name)
113                    case 'intersection':
114                        meta.append(type_name)
115                        separator = '&'
116                    case 'name':
117                        str = t['ptr'].cast(gdb.lookup_type('zend_string').pointer())
118                        parts.append(ZendStringPrettyPrinter(str).to_string())
119                    case _:
120                        parts.append(type_name)
121
122        str = separator.join(parts)
123
124        if len(meta) > 0:
125            str = '[%s] %s' % (','.join(meta), str)
126
127        return str
128
129
130pp_set.add_printer('zend_type', '^zend_type$', ZendTypePrettyPrinter)
131
132class ZendAstKindPrettyPrinter(gdb.printing.PrettyPrinter):
133    "Print a zend_ast_kind"
134
135    def __init__(self, val):
136        self.val = val
137
138    def to_string(self):
139        return self.val.cast(gdb.lookup_type('enum _zend_ast_kind'))
140
141
142pp_set.add_printer('zend_ast_kind', '^zend_ast_kind$', ZendAstKindPrettyPrinter)
143
144class ZendAstPrettyPrinter(gdb.printing.PrettyPrinter):
145    "Print a zend_ast"
146
147    def __init__(self, val):
148        self.val = val
149
150    def to_string(self):
151        return '((%s*)0x%x)' % (str(self.cast().type), self.val.address)
152
153    def children(self):
154        val = self.cast()
155        for field in val.type.fields():
156            if field.name == 'child':
157                children = val[field.name]
158                num_children = self.num_children()
159
160                ptr_type = gdb.lookup_type('zend_ast').pointer().pointer()
161                children = children.cast(ptr_type)
162
163                for i in range(0, num_children):
164                    c = children[i]
165                    if int(c) != 0:
166                        c = c.dereference()
167                    yield ('child[%d]' % i, c)
168            elif field.name == 'name':
169                yield (field.name, ZendStringPrettyPrinter(val[field.name].dereference()).to_string())
170            elif field.name == 'val':
171                yield (field.name, ZvalPrettyPrinter(val[field.name]).to_string())
172            else:
173                yield (field.name, val[field.name])
174
175    def is_special(self):
176        special_shift = 6 # ZEND_AST_SPECIAL_SHIFT
177        kind = self.val['kind']
178        return (kind >> special_shift) & 1
179
180    def is_decl(self):
181        return self.is_special() and int(self.val['kind']) >= enum_value('ZEND_AST_FUNC_DECL')
182
183    def is_list(self):
184        list_shift = 7 # ZEND_AST_IS_LIST_SHIFT
185        kind = self.val['kind']
186        return (kind >> list_shift) & 1
187
188    def cast(self):
189        kind = int(self.val['kind'])
190
191        if kind == enum_value('ZEND_AST_ZVAL') or kind == enum_value('ZEND_AST_CONSTANT'):
192            return self.val.cast(gdb.lookup_type('zend_ast_zval'))
193        if kind == enum_value('ZEND_AST_ZNODE'):
194            return self.val.cast(gdb.lookup_type('zend_ast_znode'))
195        if self.is_decl():
196            return self.val.cast(gdb.lookup_type('zend_ast_decl'))
197        if self.is_list():
198            return self.val.cast(gdb.lookup_type('zend_ast_list'))
199
200        return self.val
201
202    def num_children(self):
203        if self.is_decl():
204            decl_type = gdb.lookup_type('zend_ast_decl')
205            child_type = decl_type['child'].type
206            return array_size(child_type)
207        if self.is_special():
208            return 0
209        elif self.is_list():
210            return int(self.cast()['children'])
211        else:
212            num_children_shift = 8 # ZEND_AST_NUM_CHILDREN_SHIFT
213            kind = self.val['kind']
214            return kind >> num_children_shift
215
216
217pp_set.add_printer('zend_ast', '^_zend_ast$', ZendAstPrettyPrinter)
218
219class ZvalPrettyPrinter(gdb.printing.PrettyPrinter):
220    "Print a zval"
221
222    def __init__(self, val):
223        self.val = val
224        load_type_bits()
225
226    def to_string(self):
227        return self.value_to_string()
228
229    def value_to_string(self):
230        t = int(self.val['u1']['v']['type'])
231        if t == type_name_to_bit['undef']:
232            return 'undef'
233        elif t == type_name_to_bit['null']:
234            return 'null'
235        elif t == type_name_to_bit['false']:
236            return 'false'
237        elif t == type_name_to_bit['true']:
238            return 'true'
239        elif t == type_name_to_bit['long']:
240            return str(self.val['value']['lval'])
241        elif t == type_name_to_bit['double']:
242            return str(self.val['value']['dval'])
243        elif t == type_name_to_bit['string']:
244            return ZendStringPrettyPrinter(self.val['value']['str'].dereference()).to_string()
245        elif t == type_name_to_bit['array']:
246            return 'array'
247        elif t == type_name_to_bit['object']:
248            return 'object(%s)' % ZendStringPrettyPrinter(self.val['value']['obj']['ce']['name'].dereference()).to_string()
249        elif t == type_name_to_bit['resource']:
250            return 'resource'
251        elif t == type_name_to_bit['reference']:
252            return 'reference'
253        elif t == type_name_to_bit['constant_ast']:
254            return 'constant_ast'
255        else:
256            return 'zval of type %d' % int(self.val['u1']['v']['type'])
257
258    def children(self):
259        for field in self.val.type.fields():
260            if field.name == 'value':
261                value = self.val['value']
262                t = int(self.val['u1']['v']['type'])
263                if t == type_name_to_bit['undef']:
264                    value = value['lval']
265                elif t == type_name_to_bit['null']:
266                    value = value['lval']
267                elif t == type_name_to_bit['false']:
268                    value = value['lval']
269                elif t == type_name_to_bit['true']:
270                    value = value['lval']
271                elif t == type_name_to_bit['long']:
272                    value = value['lval']
273                elif t == type_name_to_bit['double']:
274                    value = value['dval']
275                elif t == type_name_to_bit['string']:
276                    value = value['str'].dereference()
277                elif t == type_name_to_bit['array']:
278                    value = value['ht'].dereference()
279                elif t == type_name_to_bit['object']:
280                    value = value['obj'].dereference()
281                elif t == type_name_to_bit['resource']:
282                    value = value['res'].dereference()
283                elif t == type_name_to_bit['reference']:
284                    value = value['ref'].dereference()
285                elif t == type_name_to_bit['constant_ast']:
286                    value = value['ast'].dereference()
287                else:
288                    value = value['ptr']
289                yield (field.name, value)
290            elif field.name == 'u2':
291                yield ('u2', self.val[field.name]['extra'])
292            else:
293                yield (field.name, self.val[field.name])
294
295
296pp_set.add_printer('zval', '^_zval_struct$', ZvalPrettyPrinter)
297
298class ZendClassEntryPrettyPrinter(gdb.printing.PrettyPrinter):
299    "Print a zend_class_entry"
300
301    # String pointers, show the string contents if possible
302    STRING_FIELDS = [ 'name', 'doc_comment' ]
303
304    def __init__(self, val):
305        self.val = val
306
307    def to_string(self):
308        return zendStringPointerPrinter(self.val['name'])
309
310    def children(self):
311        for field in self.val.type.fields():
312            if field.name is not None:
313                if field.name in self.STRING_FIELDS:
314                    yield (field.name, zendStringPointerPrinter(self.val[field.name]))
315                else:
316                    yield (field.name, self.val[field.name])
317            else:
318                # Don't break on the union fields. Unfortunately, pretty
319                # printers done in python cannot match the default formatting of
320                # C anonymous fields, which omit the name entirely, see
321                # binutils-gdb/gdb/cp-valprint.c#248 (as of commit
322                # b6532accdd8e24329cc69bb58bc2883796008776)
323                yield ('<anonymous>', self.val[field])
324
325pp_set.add_printer('zend_class_entry', '^_zend_class_entry$', ZendClassEntryPrettyPrinter)
326
327class ZendClassConstantPrettyPrinter(gdb.printing.PrettyPrinter):
328    "Print a zend_class_constant"
329
330    def __init__(self, val):
331        self.val = val
332
333    def children(self):
334        for field in self.val.type.fields():
335            if field.name == 'doc_comment':
336                yield ('doc_comment', zendStringPointerPrinter(self.val['doc_comment']))
337            elif field.name == 'ce':
338                yield ('ce', zendStringPointerPrinter(self.val['ce']['name']))
339            else:
340                yield (field.name, self.val[field.name])
341
342pp_set.add_printer('zend_class_constant', '^_zend_class_constant$', ZendClassConstantPrettyPrinter)
343
344type_bit_to_name = None
345type_name_to_bit = None
346
347def load_type_bits():
348    global type_bit_to_name
349    global type_name_to_bit
350
351    if type_bit_to_name != None:
352        return
353
354    (symbol,_) = gdb.lookup_symbol("zend_gc_refcount")
355    if symbol == None:
356        raise Exception("Could not find zend_types.h: symbol zend_gc_refcount not found")
357    filename = symbol.symtab.fullname()
358
359    bits = {}
360
361    with open(filename, 'r') as file:
362        content = file.read()
363
364        pattern = re.compile(r'#define _ZEND_TYPE_([^\s]+)_BIT\s+\(1u << (\d+)\)')
365        matches = pattern.findall(content)
366        for name, bit in matches:
367            bits[int(bit)] = name.lower()
368
369        pattern = re.compile(r'#define IS_([^\s]+)\s+(\d+)')
370        matches = pattern.findall(content)
371        for name, bit in matches:
372            if not int(bit) in bits:
373                bits[int(bit)] = name.lower()
374
375    types = {}
376    for bit in bits:
377        types[bits[bit]] = bit
378
379    type_bit_to_name = bits
380    type_name_to_bit = types
381
382def lookup_symbol(name):
383    (symbol, _) = gdb.lookup_symbol(name)
384    if symbol == None:
385        raise Exception("Could not lookup symbol %s" % name)
386    return symbol
387
388def enum_value(name):
389    symbol = lookup_symbol(name)
390    return int(symbol.value())
391
392def array_size(ary_type):
393    # array types have a single field whose type represents its range
394    return ary_type.fields()[0].type.range()[1]+1
395
396gdb.printing.register_pretty_printer(gdb, pp_set, replace=True)
397