blob: 167f50351a227b89ae11d9b9f4e11f1c683362a4 [file] [log] [blame]
# Copyright 2020 - The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import gdb
import gdb.printing
import re
from abc import ABC, abstractmethod
import json
# our code now only support Python3. In python2 we need to use intptr=long. Use this typedef
# for easier support of Python2 later.
intptr = int
def check_optimized_out(to_json):
""" A decorator for to_json method of JsonPrinters. Check if the value in the printer
is already optimized out.
If the value is already optimized out, return {'type': 'optimized_out'}.
Args:
to_json: A to_json method of a JsonPrinter.
Returns:
decorated to_json method
"""
def decorator(self, *args, **kwargs):
if self.value is None or self.value.is_optimized_out:
return {'type': 'optimized_out'}
return to_json(self, *args, **kwargs)
return decorator
def check_visited(to_json):
""" A decorator for to_json method of JsonPrinters. Check if the value in the printer
has appeared in visited_addresses_and_types.
If the value has appeared in visited_addresses_and_types, return {'type': 'visited',
'ctype': str(self.value.type), 'address': self.address}
Args:
to_json: A to_json method of a JsonPrinter.
Returns:
decorated to_json method
"""
def decorator(self):
addresstype = self.get_address_type()
if addresstype != None and addresstype in self.visited_addresses_and_types:
return {
'type': 'visited',
'ctype': str(self.value.type),
'address': self.address
}
self.visited_addresses_and_types.add(self.get_address_type())
return to_json(self)
return decorator
class JsonPrinter(ABC):
"""Base class for all json printers.
Attributes:
value:
The value to print, note that the lifetime of printer is limited to single print command.
visited_addresses_and_types:
A set of all `address`_`type` string that already visited during printing.
If the address and type is in the set and the value is a struct or pointer,
the printer will mark 'type' as visited and no longer expand the value. Can be None.
"""
def __init__(self, value, visited_addresses_and_types=None):
""" Constructor. A JsonPrinter will be created for each time a variable is printed.
Args:
value:
A gdb.Value object, the value to print.
visited_addresses_and_types:
A set of all `address`_`type` string that already visited during printing.
If the address and type is in the set and the value is a struct or pointer,
the printer will mark 'type' as visited and no longer expand the value. Can be None.
"""
self.value = value
self.visited_addresses_and_types = visited_addresses_and_types
if self.visited_addresses_and_types == None:
self.visited_addresses_and_types = set()
# value may not have address attribute if it's in register
if hasattr(self.value, 'address') and self.value.address is not None:
self.address = hex(intptr(self.value.address))
else:
self.address = None
def to_string(self):
""" Gdb Python interface to print a gdb.Value.
Returns:
A string representing the json format of a gdb.Value.
"""
return json.dumps(self.to_json(), indent=4)
@abstractmethod
def to_json(self):
""" The method to be implemented. Convert a gdb.Value object into a Python json object.
Returns:
A Python json object.
"""
pass
# we use address and type to identify a printed value and avoid circular references
def get_address_type(self):
address_str = self.address if self.address is not None else ""
return address_str + "_" + str(self.value.type.strip_typedefs())
class BasicTypePrinter(JsonPrinter):
""" Printer for basic types, now supports Enum, Int, Float, and Void.
All integer variables in C++ will be considered as Int, e.g. char, short, long. Similarly,
float and double are all considered as Float. For enum variables, they are actually int in
C++, gdb knows they are enum variables, but can only print them as integer.
"""
"""
BasicTypePrinter uses this dict to print basic_types.
"""
basic_type_json_info = {
gdb.TYPE_CODE_ENUM: {'type': 'enum', 'value_func': lambda value: str(int(value))},
gdb.TYPE_CODE_INT: {'type': 'int', 'value_func': lambda value: str(int(value))},
gdb.TYPE_CODE_FLT: {'type': 'float', 'value_func': lambda value: str(float(value))},
gdb.TYPE_CODE_VOID: {'type': 'void', 'value_func': lambda value: None},
}
@check_optimized_out
@check_visited
def to_json(self):
""" Output format is:
{
'type': 'int'/'float'/'enum'/'void',
'ctype': string format type in C++,
'address': string format address,
'value': string format value
}.
"""
type_code = self.value.type.strip_typedefs().code
json_type = BasicTypePrinter.basic_type_json_info[type_code]["type"]
value_func = BasicTypePrinter.basic_type_json_info[type_code]["value_func"]
value_json = {
'type': json_type,
'ctype': str(self.value.type),
'address': self.address,
'value': value_func(self.value)
}
return value_json
class ObjectPrinter(JsonPrinter):
""" A Printer for objects in C++.
The current version won't extract the dynamic/actual type of objects, and the member variable
of a parent/child class won't be printed. We expect to support this function later.
"""
@check_visited
def to_json_without_expanding_base_class(self):
""" A helper function for the to_json method.
It tries to extract all member variables of an object without casting it into base classes.
"""
value_json = {
'type': 'struct',
'ctype': str(self.value.type),
'address': self.address,
'fields': []
}
for field in self.value.type.fields():
if not field.is_base_class:
field_json = {
'field': field.name,
'value': None
}
field_printer = general_lookup_function(
self.value[field.name], self.visited_addresses_and_types)
try:
field_json['value'] = field_printer.to_json()
except:
field_json['value'] = "extract failed"
value_json['fields'].append(field_json)
return value_json
@check_optimized_out
def to_json(self, cast_to_dynamic_type=True):
"""Output format:
{
'type': 'struct',
'ctype': string format type in C++,
'address': string format address,
'base_classes': [] # a list of value casted into each base class
'fields': [] # a list of struct fields
}.
For each field in fields, its format is:
{
'field': string format of field name,
'field_type': 'base_class'/'member', if it is a base class. the value will be the
object which is casted into that base_class. Otherwise, the value is the json of
the member variable.
'value': json of the field
}.
"""
if cast_to_dynamic_type and \
self.value.type.strip_typedefs() != self.value.dynamic_type.strip_typedefs():
self.value = self.value.cast(self.value.dynamic_type)
# address/type pair is set to visited after casted to dynamic type to avoid base class
# being filtered by the visited check
value_json = self.to_json_without_expanding_base_class()
# if the type is visited, it's not necessary to explore its ancestors.
if value_json["type"] != "visited":
base_classes_list = []
for field in self.value.type.fields():
if field.is_base_class:
field_json = {
'base_class': field.name,
'value': None
}
base_class_printer = ObjectPrinter(
self.value.cast(field.type),
self.visited_addresses_and_types)
field_json["value"] = base_class_printer.to_json(
cast_to_dynamic_type=False)
base_classes_list.append(field_json)
value_json['base_classes'] = base_classes_list
return value_json
class RefAndPtrPrinter(JsonPrinter):
""" Printer for reference and raw pointer in C++.
"""
def __init__(self, value, visited_addresses_and_types=None):
super().__init__(value, visited_addresses_and_types)
self.void_ptr_re = re.compile(r"^.*void\s?\*$")
@check_optimized_out
@check_visited
def to_json(self):
"""Output format:
{
'type': 'pointer'/"reference,
'ctype': string format type in C++,
'address': string format address,
'reference': Json for a C++ object
}.
If the pointer is a void* pointer, reference would be "cannot extract void ptr",
because gdb cannot extract content from a void* pointer. If the pointer is nullptr,
reference would be "nullptr".
"""
value_type = 'pointer' if self.value.type.code == gdb.TYPE_CODE_PTR else 'reference'
value_json = {
'type': value_type,
'ctype': str(self.value.type),
'address': self.address,
'reference': None
}
# handle void pointer, dereference a void pointer will cause exception
if self.void_ptr_re.match(str(self.value.type.strip_typedefs())) is not None:
value_json['reference'] = "cannot extract void ptr"
return value_json
# handle nullptr
if value_type == 'pointer' and int(self.value) == 0:
value_json['reference'] = "nullptr"
return value_json
deref_value = self.value.referenced_value()
deref_value_printer = general_lookup_function(
deref_value, self.visited_addresses_and_types)
value_json['reference'] = deref_value_printer.to_json()
return value_json
class ExtractFailedPrinter(JsonPrinter):
""" Printer for the case that cannot be extracted by current printer.
"""
def to_json(self):
""" output format: {'type': 'extract failed'}
"""
return {'type': 'extract failed'}
class OptimizedOutPrinter(JsonPrinter):
""" Printer for the case that a variable is already optimized out.
"""
def to_json(self):
""" Output format: {'type': 'optimized out'}.
"""
return {'type': 'optimized out'}
class StackArrayPrinter(JsonPrinter):
""" Printer for the arrays in C++.
Note that this printer works only for C++ arrays(with type= T[]). It cannot
print out buffers on heap.
Output format:
{
'type': 'array',
'element_type': string format type of array elements in C++,
'values': A list of Value json indiciating each element in array
}.
"""
@check_optimized_out
@check_visited
def to_json(self):
total_size = self.value.type.sizeof
element_size = self.value.type.target().sizeof
element_count = total_size // element_size
stack_array_json = {
'type': 'array',
'element_type': str(self.value.type.target()),
'values': []
}
for idx in range(element_count):
element_json = general_lookup_function(
self.value[idx], self.visited_addresses_and_types).to_json()
stack_array_json['values'].append(element_json)
return stack_array_json
""" The table indiciating which printer should be called given a gdb value.
"""
gdb_type_printer_map = {
gdb.TYPE_CODE_PTR: RefAndPtrPrinter,
gdb.TYPE_CODE_ARRAY: StackArrayPrinter,
gdb.TYPE_CODE_STRUCT: ObjectPrinter,
gdb.TYPE_CODE_UNION: ObjectPrinter,
gdb.TYPE_CODE_ENUM: BasicTypePrinter,
gdb.TYPE_CODE_FLAGS: None,
gdb.TYPE_CODE_FUNC: None,
gdb.TYPE_CODE_INT: BasicTypePrinter,
gdb.TYPE_CODE_FLT: BasicTypePrinter,
gdb.TYPE_CODE_VOID: BasicTypePrinter,
gdb.TYPE_CODE_SET: None, # not exist in C++
gdb.TYPE_CODE_RANGE: None, # not exist in C++?
# not exist in C++, in C++, string is not a built-in type
gdb.TYPE_CODE_STRING: None,
gdb.TYPE_CODE_BITSTRING: None, # deprecated
gdb.TYPE_CODE_ERROR: None,
gdb.TYPE_CODE_METHOD: None,
gdb.TYPE_CODE_METHODPTR: None,
gdb.TYPE_CODE_MEMBERPTR: None,
gdb.TYPE_CODE_REF: RefAndPtrPrinter,
gdb.TYPE_CODE_RVALUE_REF: None,
gdb.TYPE_CODE_CHAR: None, # char is an integer in C++
gdb.TYPE_CODE_BOOL: None, # bool is actually char in C++
gdb.TYPE_CODE_COMPLEX: None, # not exist in C++
gdb.TYPE_CODE_TYPEDEF: None,
gdb.TYPE_CODE_NAMESPACE: None,
gdb.TYPE_CODE_DECFLOAT: None, # not exist in C++
gdb.TYPE_CODE_INTERNAL_FUNCTION: None
}
def general_lookup_function(value, visited_addresses_and_types=None):
""" The actual printer installed, it will select JsonPrinter based on the gdb value given.
"""
if value.is_optimized_out:
return OptimizedOutPrinter(value)
type_code = value.type.strip_typedefs().code
if type_code not in gdb_type_printer_map or gdb_type_printer_map[type_code] is None:
return ExtractFailedPrinter(value)
# TODO: add regex match and specific printer for some type here? such as shared_ptr
return gdb_type_printer_map[type_code](value, visited_addresses_and_types)
def register_printers():
""" Call this function in your ~/.gdbinit to register the printer into gdb.
"""
gdb.pretty_printers.append(general_lookup_function)