xref: /PHP-8.4/scripts/gdb/php_gdb.py (revision 31e2ec63)
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
70class ZendTypePrettyPrinter(gdb.printing.PrettyPrinter):
71    "Print a zend_type"
72
73    def __init__(self, val):
74        self.val = val
75        load_type_bits()
76
77    def to_string(self):
78        return self.format_type(self.val)
79
80    def children(self):
81        for field in self.val.type.fields():
82            yield (field.name, self.val[field.name])
83
84    def format_type(self, t):
85        type_mask = int(t['type_mask'])
86        type_mask_size = t['type_mask'].type.sizeof * 8
87        separator = '|'
88        parts = []
89        meta = []
90        for bit in range(0, type_mask_size):
91            if type_mask & (1 << bit):
92                type_name = type_bit_to_name.get(bit)
93                match type_name:
94                    case None:
95                        parts.append('(1<<%d)' % bit)
96                    case 'list':
97                        list = t['ptr'].cast(gdb.lookup_type('zend_type_list').pointer())
98                        num_types = int(list['num_types'])
99                        types = list['types'].dereference().cast(gdb.lookup_type('zend_type').array(num_types))
100                        for i in range(0, num_types):
101                            str = self.format_type(types[i])
102                            if any((c in set('|&()')) for c in str):
103                                str = '(%s)' % str
104                            parts.append(str)
105                    case 'union' | 'arena':
106                        meta.append(type_name)
107                    case 'intersection':
108                        meta.append(type_name)
109                        separator = '&'
110                    case 'name':
111                        str = t['ptr'].cast(gdb.lookup_type('zend_string').pointer())
112                        parts.append(ZendStringPrettyPrinter(str).to_string())
113                    case _:
114                        parts.append(type_name)
115
116        str = separator.join(parts)
117
118        if len(meta) > 0:
119            str = '[%s] %s' % (','.join(meta), str)
120
121        return str
122
123
124pp_set.add_printer('zend_type', '^zend_type$', ZendTypePrettyPrinter)
125
126class ZendAstKindPrettyPrinter(gdb.printing.PrettyPrinter):
127    "Print a zend_ast_kind"
128
129    def __init__(self, val):
130        self.val = val
131
132    def to_string(self):
133        return self.val.cast(gdb.lookup_type('enum _zend_ast_kind'))
134
135
136pp_set.add_printer('zend_ast_kind', '^zend_ast_kind$', ZendAstKindPrettyPrinter)
137
138class ZendAstPrettyPrinter(gdb.printing.PrettyPrinter):
139    "Print a zend_ast"
140
141    def __init__(self, val):
142        self.val = val
143
144    def to_string(self):
145        return '((%s*)0x%x)' % (str(self.cast().type), self.val.address)
146
147    def children(self):
148        val = self.cast()
149        for field in val.type.fields():
150            if field.name == 'child':
151                children = val[field.name]
152                num_children = self.num_children()
153
154                ptr_type = gdb.lookup_type('zend_ast').pointer().pointer()
155                children = children.cast(ptr_type)
156
157                for i in range(0, num_children):
158                    c = children[i]
159                    if int(c) != 0:
160                        c = c.dereference()
161                    yield ('child[%d]' % i, c)
162            elif field.name == 'name':
163                yield (field.name, ZendStringPrettyPrinter(val[field.name].dereference()).to_string())
164            elif field.name == 'val':
165                yield (field.name, ZvalPrettyPrinter(val[field.name]).to_string())
166            else:
167                yield (field.name, val[field.name])
168
169    def is_special(self):
170        special_shift = 6 # ZEND_AST_SPECIAL_SHIFT
171        kind = self.val['kind']
172        return (kind >> special_shift) & 1
173
174    def is_decl(self):
175        return self.is_special() and int(self.val['kind']) >= enum_value('ZEND_AST_FUNC_DECL')
176
177    def is_list(self):
178        list_shift = 7 # ZEND_AST_IS_LIST_SHIFT
179        kind = self.val['kind']
180        return (kind >> list_shift) & 1
181
182    def cast(self):
183        kind = int(self.val['kind'])
184
185        if kind == enum_value('ZEND_AST_ZVAL') or kind == enum_value('ZEND_AST_CONSTANT'):
186            return self.val.cast(gdb.lookup_type('zend_ast_zval'))
187        if kind == enum_value('ZEND_AST_ZNODE'):
188            return self.val.cast(gdb.lookup_type('zend_ast_znode'))
189        if self.is_decl():
190            return self.val.cast(gdb.lookup_type('zend_ast_decl'))
191        if self.is_list():
192            return self.val.cast(gdb.lookup_type('zend_ast_list'))
193
194        return self.val
195
196    def num_children(self):
197        if self.is_decl():
198            decl_type = gdb.lookup_type('zend_ast_decl')
199            child_type = decl_type['child'].type
200            return array_size(child_type)
201        if self.is_special():
202            return 0
203        elif self.is_list():
204            return int(self.cast()['children'])
205        else:
206            num_children_shift = 8 # ZEND_AST_NUM_CHILDREN_SHIFT
207            kind = self.val['kind']
208            return kind >> num_children_shift
209
210
211pp_set.add_printer('zend_ast', '^_zend_ast$', ZendAstPrettyPrinter)
212
213class ZvalPrettyPrinter(gdb.printing.PrettyPrinter):
214    "Print a zval"
215
216    def __init__(self, val):
217        self.val = val
218        load_type_bits()
219
220    def to_string(self):
221        return self.value_to_string()
222
223    def value_to_string(self):
224        t = int(self.val['u1']['v']['type'])
225        if t == type_name_to_bit['undef']:
226            return 'undef'
227        elif t == type_name_to_bit['null']:
228            return 'null'
229        elif t == type_name_to_bit['false']:
230            return 'false'
231        elif t == type_name_to_bit['true']:
232            return 'true'
233        elif t == type_name_to_bit['long']:
234            return str(self.val['value']['lval'])
235        elif t == type_name_to_bit['double']:
236            return str(self.val['value']['dval'])
237        elif t == type_name_to_bit['string']:
238            return ZendStringPrettyPrinter(self.val['value']['str'].dereference()).to_string()
239        elif t == type_name_to_bit['array']:
240            return 'array'
241        elif t == type_name_to_bit['object']:
242            return 'object(%s)' % ZendStringPrettyPrinter(self.val['value']['obj']['ce']['name'].dereference()).to_string()
243        elif t == type_name_to_bit['resource']:
244            return 'resource'
245        elif t == type_name_to_bit['reference']:
246            return 'reference'
247        elif t == type_name_to_bit['constant_ast']:
248            return 'constant_ast'
249        else:
250            return 'zval of type %d' % int(self.val['u1']['v']['type'])
251
252    def children(self):
253        for field in self.val.type.fields():
254            if field.name == 'value':
255                value = self.val['value']
256                t = int(self.val['u1']['v']['type'])
257                if t == type_name_to_bit['undef']:
258                    value = value['lval']
259                elif t == type_name_to_bit['null']:
260                    value = value['lval']
261                elif t == type_name_to_bit['false']:
262                    value = value['lval']
263                elif t == type_name_to_bit['true']:
264                    value = value['lval']
265                elif t == type_name_to_bit['long']:
266                    value = value['lval']
267                elif t == type_name_to_bit['double']:
268                    value = value['dval']
269                elif t == type_name_to_bit['string']:
270                    value = value['str'].dereference()
271                elif t == type_name_to_bit['array']:
272                    value = value['ht'].dereference()
273                elif t == type_name_to_bit['object']:
274                    value = value['obj'].dereference()
275                elif t == type_name_to_bit['resource']:
276                    value = value['res'].dereference()
277                elif t == type_name_to_bit['reference']:
278                    value = value['ref'].dereference()
279                elif t == type_name_to_bit['constant_ast']:
280                    value = value['ast'].dereference()
281                else:
282                    value = value['ptr']
283                yield (field.name, value)
284            elif field.name == 'u2':
285                yield ('u2', self.val[field.name]['extra'])
286            else:
287                yield (field.name, self.val[field.name])
288
289
290pp_set.add_printer('zval', '^_zval_struct$', ZvalPrettyPrinter)
291
292type_bit_to_name = None
293type_name_to_bit = None
294
295def load_type_bits():
296    global type_bit_to_name
297    global type_name_to_bit
298
299    if type_bit_to_name != None:
300        return
301
302    (symbol,_) = gdb.lookup_symbol("zend_gc_refcount")
303    if symbol == None:
304        raise Exception("Could not find zend_types.h: symbol zend_gc_refcount not found")
305    filename = symbol.symtab.fullname()
306
307    bits = {}
308
309    with open(filename, 'r') as file:
310        content = file.read()
311
312        pattern = re.compile(r'#define _ZEND_TYPE_([^\s]+)_BIT\s+\(1u << (\d+)\)')
313        matches = pattern.findall(content)
314        for name, bit in matches:
315            bits[int(bit)] = name.lower()
316
317        pattern = re.compile(r'#define IS_([^\s]+)\s+(\d+)')
318        matches = pattern.findall(content)
319        for name, bit in matches:
320            if not int(bit) in bits:
321                bits[int(bit)] = name.lower()
322
323    types = {}
324    for bit in bits:
325        types[bits[bit]] = bit
326
327    type_bit_to_name = bits
328    type_name_to_bit = types
329
330def lookup_symbol(name):
331    (symbol, _) = gdb.lookup_symbol(name)
332    if symbol == None:
333        raise Exception("Could not lookup symbol %s" % name)
334    return symbol
335
336def enum_value(name):
337    symbol = lookup_symbol(name)
338    return int(symbol.value())
339
340def array_size(ary_type):
341    # array types have a single field whose type represents its range
342    return ary_type.fields()[0].type.range()[1]+1
343
344gdb.printing.register_pretty_printer(gdb, pp_set, replace=True)
345