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