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