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