1# Copyright 2020 - The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15import gdb 16import gdb.printing 17import re 18from abc import ABC, abstractmethod 19import json 20 21# our code now only support Python3. In python2 we need to use intptr=long. Use this typedef 22# for easier support of Python2 later. 23intptr = int 24 25 26def check_optimized_out(to_json): 27 """ A decorator for to_json method of JsonPrinters. Check if the value in the printer 28 is already optimized out. 29 If the value is already optimized out, return {'type': 'optimized_out'}. 30 31 Args: 32 to_json: A to_json method of a JsonPrinter. 33 34 Returns: 35 decorated to_json method 36 """ 37 38 def decorator(self, *args, **kwargs): 39 if self.value is None or self.value.is_optimized_out: 40 return {'type': 'optimized_out'} 41 42 return to_json(self, *args, **kwargs) 43 return decorator 44 45 46def check_visited(to_json): 47 """ A decorator for to_json method of JsonPrinters. Check if the value in the printer 48 has appeared in visited_addresses_and_types. 49 If the value has appeared in visited_addresses_and_types, return {'type': 'visited', 50 'ctype': str(self.value.type), 'address': self.address} 51 52 Args: 53 to_json: A to_json method of a JsonPrinter. 54 55 Returns: 56 decorated to_json method 57 """ 58 59 def decorator(self): 60 addresstype = self.get_address_type() 61 if addresstype != None and addresstype in self.visited_addresses_and_types: 62 return { 63 'type': 'visited', 64 'ctype': str(self.value.type), 65 'address': self.address 66 } 67 self.visited_addresses_and_types.add(self.get_address_type()) 68 return to_json(self) 69 return decorator 70 71 72class JsonPrinter(ABC): 73 """Base class for all json printers. 74 75 Attributes: 76 value: 77 The value to print, note that the lifetime of printer is limited to single print command. 78 visited_addresses_and_types: 79 A set of all `address`_`type` string that already visited during printing. 80 If the address and type is in the set and the value is a struct or pointer, 81 the printer will mark 'type' as visited and no longer expand the value. Can be None. 82 """ 83 84 def __init__(self, value, visited_addresses_and_types=None): 85 """ Constructor. A JsonPrinter will be created for each time a variable is printed. 86 87 Args: 88 value: 89 A gdb.Value object, the value to print. 90 visited_addresses_and_types: 91 A set of all `address`_`type` string that already visited during printing. 92 If the address and type is in the set and the value is a struct or pointer, 93 the printer will mark 'type' as visited and no longer expand the value. Can be None. 94 """ 95 self.value = value 96 self.visited_addresses_and_types = visited_addresses_and_types 97 if self.visited_addresses_and_types == None: 98 self.visited_addresses_and_types = set() 99 100 # value may not have address attribute if it's in register 101 if hasattr(self.value, 'address') and self.value.address is not None: 102 self.address = hex(intptr(self.value.address)) 103 else: 104 self.address = None 105 106 def to_string(self): 107 """ Gdb Python interface to print a gdb.Value. 108 109 Returns: 110 A string representing the json format of a gdb.Value. 111 """ 112 return json.dumps(self.to_json(), indent=4) 113 114 @abstractmethod 115 def to_json(self): 116 """ The method to be implemented. Convert a gdb.Value object into a Python json object. 117 118 Returns: 119 A Python json object. 120 """ 121 pass 122 123 # we use address and type to identify a printed value and avoid circular references 124 def get_address_type(self): 125 address_str = self.address if self.address is not None else "" 126 return address_str + "_" + str(self.value.type.strip_typedefs()) 127 128 129class BasicTypePrinter(JsonPrinter): 130 """ Printer for basic types, now supports Enum, Int, Float, and Void. 131 132 All integer variables in C++ will be considered as Int, e.g. char, short, long. Similarly, 133 float and double are all considered as Float. For enum variables, they are actually int in 134 C++, gdb knows they are enum variables, but can only print them as integer. 135 """ 136 137 """ 138 BasicTypePrinter uses this dict to print basic_types. 139 """ 140 basic_type_json_info = { 141 gdb.TYPE_CODE_ENUM: {'type': 'enum', 'value_func': lambda value: str(int(value))}, 142 gdb.TYPE_CODE_INT: {'type': 'int', 'value_func': lambda value: str(int(value))}, 143 gdb.TYPE_CODE_FLT: {'type': 'float', 'value_func': lambda value: str(float(value))}, 144 gdb.TYPE_CODE_VOID: {'type': 'void', 'value_func': lambda value: None}, 145 } 146 147 @check_optimized_out 148 @check_visited 149 def to_json(self): 150 """ Output format is: 151 { 152 'type': 'int'/'float'/'enum'/'void', 153 'ctype': string format type in C++, 154 'address': string format address, 155 'value': string format value 156 }. 157 """ 158 type_code = self.value.type.strip_typedefs().code 159 json_type = BasicTypePrinter.basic_type_json_info[type_code]["type"] 160 value_func = BasicTypePrinter.basic_type_json_info[type_code]["value_func"] 161 value_json = { 162 'type': json_type, 163 'ctype': str(self.value.type), 164 'address': self.address, 165 'value': value_func(self.value) 166 } 167 return value_json 168 169 170class ObjectPrinter(JsonPrinter): 171 """ A Printer for objects in C++. 172 173 The current version won't extract the dynamic/actual type of objects, and the member variable 174 of a parent/child class won't be printed. We expect to support this function later. 175 """ 176 177 @check_visited 178 def to_json_without_expanding_base_class(self): 179 """ A helper function for the to_json method. 180 181 It tries to extract all member variables of an object without casting it into base classes. 182 """ 183 value_json = { 184 'type': 'struct', 185 'ctype': str(self.value.type), 186 'address': self.address, 187 'fields': [] 188 } 189 190 for field in self.value.type.fields(): 191 if not field.is_base_class: 192 field_json = { 193 'field': field.name, 194 'value': None 195 } 196 197 field_printer = general_lookup_function( 198 self.value[field.name], self.visited_addresses_and_types) 199 try: 200 field_json['value'] = field_printer.to_json() 201 except: 202 field_json['value'] = "extract failed" 203 204 value_json['fields'].append(field_json) 205 206 return value_json 207 208 @check_optimized_out 209 def to_json(self, cast_to_dynamic_type=True): 210 """Output format: 211 { 212 'type': 'struct', 213 'ctype': string format type in C++, 214 'address': string format address, 215 'base_classes': [] # a list of value casted into each base class 216 'fields': [] # a list of struct fields 217 }. 218 For each field in fields, its format is: 219 { 220 'field': string format of field name, 221 'field_type': 'base_class'/'member', if it is a base class. the value will be the 222 object which is casted into that base_class. Otherwise, the value is the json of 223 the member variable. 224 'value': json of the field 225 }. 226 """ 227 if cast_to_dynamic_type and \ 228 self.value.type.strip_typedefs() != self.value.dynamic_type.strip_typedefs(): 229 self.value = self.value.cast(self.value.dynamic_type) 230 231 # address/type pair is set to visited after casted to dynamic type to avoid base class 232 # being filtered by the visited check 233 value_json = self.to_json_without_expanding_base_class() 234 235 # if the type is visited, it's not necessary to explore its ancestors. 236 if value_json["type"] != "visited": 237 base_classes_list = [] 238 for field in self.value.type.fields(): 239 if field.is_base_class: 240 field_json = { 241 'base_class': field.name, 242 'value': None 243 } 244 245 base_class_printer = ObjectPrinter( 246 self.value.cast(field.type), 247 self.visited_addresses_and_types) 248 field_json["value"] = base_class_printer.to_json( 249 cast_to_dynamic_type=False) 250 base_classes_list.append(field_json) 251 value_json['base_classes'] = base_classes_list 252 253 return value_json 254 255 256class RefAndPtrPrinter(JsonPrinter): 257 """ Printer for reference and raw pointer in C++. 258 """ 259 260 def __init__(self, value, visited_addresses_and_types=None): 261 super().__init__(value, visited_addresses_and_types) 262 self.void_ptr_re = re.compile(r"^.*void\s?\*$") 263 264 @check_optimized_out 265 @check_visited 266 def to_json(self): 267 """Output format: 268 { 269 'type': 'pointer'/"reference, 270 'ctype': string format type in C++, 271 'address': string format address, 272 'reference': Json for a C++ object 273 }. 274 If the pointer is a void* pointer, reference would be "cannot extract void ptr", 275 because gdb cannot extract content from a void* pointer. If the pointer is nullptr, 276 reference would be "nullptr". 277 """ 278 value_type = 'pointer' if self.value.type.code == gdb.TYPE_CODE_PTR else 'reference' 279 value_json = { 280 'type': value_type, 281 'ctype': str(self.value.type), 282 'address': self.address, 283 'reference': None 284 } 285 286 # handle void pointer, dereference a void pointer will cause exception 287 if self.void_ptr_re.match(str(self.value.type.strip_typedefs())) is not None: 288 value_json['reference'] = "cannot extract void ptr" 289 return value_json 290 291 # handle nullptr 292 if value_type == 'pointer' and int(self.value) == 0: 293 value_json['reference'] = "nullptr" 294 return value_json 295 296 deref_value = self.value.referenced_value() 297 deref_value_printer = general_lookup_function( 298 deref_value, self.visited_addresses_and_types) 299 value_json['reference'] = deref_value_printer.to_json() 300 return value_json 301 302 303class ExtractFailedPrinter(JsonPrinter): 304 """ Printer for the case that cannot be extracted by current printer. 305 """ 306 307 def to_json(self): 308 """ output format: {'type': 'extract failed'} 309 """ 310 return {'type': 'extract failed'} 311 312 313class OptimizedOutPrinter(JsonPrinter): 314 """ Printer for the case that a variable is already optimized out. 315 """ 316 317 def to_json(self): 318 """ Output format: {'type': 'optimized out'}. 319 """ 320 return {'type': 'optimized out'} 321 322 323class StackArrayPrinter(JsonPrinter): 324 """ Printer for the arrays in C++. 325 326 Note that this printer works only for C++ arrays(with type= T[]). It cannot 327 print out buffers on heap. 328 Output format: 329 { 330 'type': 'array', 331 'element_type': string format type of array elements in C++, 332 'values': A list of Value json indiciating each element in array 333 }. 334 """ 335 @check_optimized_out 336 @check_visited 337 def to_json(self): 338 total_size = self.value.type.sizeof 339 element_size = self.value.type.target().sizeof 340 element_count = total_size // element_size 341 stack_array_json = { 342 'type': 'array', 343 'element_type': str(self.value.type.target()), 344 'values': [] 345 } 346 for idx in range(element_count): 347 element_json = general_lookup_function( 348 self.value[idx], self.visited_addresses_and_types).to_json() 349 stack_array_json['values'].append(element_json) 350 return stack_array_json 351 352 353""" The table indiciating which printer should be called given a gdb value. 354""" 355gdb_type_printer_map = { 356 gdb.TYPE_CODE_PTR: RefAndPtrPrinter, 357 gdb.TYPE_CODE_ARRAY: StackArrayPrinter, 358 gdb.TYPE_CODE_STRUCT: ObjectPrinter, 359 gdb.TYPE_CODE_UNION: ObjectPrinter, 360 gdb.TYPE_CODE_ENUM: BasicTypePrinter, 361 gdb.TYPE_CODE_FLAGS: None, 362 gdb.TYPE_CODE_FUNC: None, 363 gdb.TYPE_CODE_INT: BasicTypePrinter, 364 gdb.TYPE_CODE_FLT: BasicTypePrinter, 365 gdb.TYPE_CODE_VOID: BasicTypePrinter, 366 gdb.TYPE_CODE_SET: None, # not exist in C++ 367 gdb.TYPE_CODE_RANGE: None, # not exist in C++? 368 # not exist in C++, in C++, string is not a built-in type 369 gdb.TYPE_CODE_STRING: None, 370 gdb.TYPE_CODE_BITSTRING: None, # deprecated 371 gdb.TYPE_CODE_ERROR: None, 372 gdb.TYPE_CODE_METHOD: None, 373 gdb.TYPE_CODE_METHODPTR: None, 374 gdb.TYPE_CODE_MEMBERPTR: None, 375 gdb.TYPE_CODE_REF: RefAndPtrPrinter, 376 gdb.TYPE_CODE_RVALUE_REF: None, 377 gdb.TYPE_CODE_CHAR: None, # char is an integer in C++ 378 gdb.TYPE_CODE_BOOL: None, # bool is actually char in C++ 379 gdb.TYPE_CODE_COMPLEX: None, # not exist in C++ 380 gdb.TYPE_CODE_TYPEDEF: None, 381 gdb.TYPE_CODE_NAMESPACE: None, 382 gdb.TYPE_CODE_DECFLOAT: None, # not exist in C++ 383 gdb.TYPE_CODE_INTERNAL_FUNCTION: None 384} 385 386 387def general_lookup_function(value, visited_addresses_and_types=None): 388 """ The actual printer installed, it will select JsonPrinter based on the gdb value given. 389 """ 390 if value.is_optimized_out: 391 return OptimizedOutPrinter(value) 392 type_code = value.type.strip_typedefs().code 393 if type_code not in gdb_type_printer_map or gdb_type_printer_map[type_code] is None: 394 return ExtractFailedPrinter(value) 395 396 # TODO: add regex match and specific printer for some type here? such as shared_ptr 397 398 return gdb_type_printer_map[type_code](value, visited_addresses_and_types) 399 400 401def register_printers(): 402 """ Call this function in your ~/.gdbinit to register the printer into gdb. 403 """ 404 gdb.pretty_printers.append(general_lookup_function) 405