| 1 | """
 | 
| 2 | oil_gdb.py
 | 
| 3 | 
 | 
| 4 | https://fy.blackhats.net.au/blog/html/2017/08/04/so_you_want_to_script_gdb_with_python.html
 | 
| 5 | """
 | 
| 6 | from __future__ import print_function
 | 
| 7 | 
 | 
| 8 | import struct
 | 
| 9 | 
 | 
| 10 | import gdb
 | 
| 11 | 
 | 
| 12 | class SimpleCommand(gdb.Command):
 | 
| 13 |     """Test command."""
 | 
| 14 |     def __init__(self):
 | 
| 15 |         # This registers our class as "simple_command"
 | 
| 16 |         super(SimpleCommand, self).__init__("simple_command", gdb.COMMAND_DATA)
 | 
| 17 | 
 | 
| 18 |     def invoke(self, arg, from_tty):
 | 
| 19 |         # When we call "simple_command" from gdb, this is the method
 | 
| 20 |         # that will be called.
 | 
| 21 |         print("Hello from simple_command!")
 | 
| 22 | 
 | 
| 23 | # This registers our class to the gdb runtime at "source" time.
 | 
| 24 | SimpleCommand()
 | 
| 25 | 
 | 
| 26 | 
 | 
| 27 | # Following:
 | 
| 28 | # https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/developer_guide/debuggingprettyprinters
 | 
| 29 | 
 | 
| 30 | class StrPrinter:
 | 
| 31 |     """Print the Str* type from mycpp/mylib."""
 | 
| 32 | 
 | 
| 33 |     def __init__(self, val):
 | 
| 34 |         self.val = val
 | 
| 35 | 
 | 
| 36 |     def to_string(self):
 | 
| 37 |         len_ = self.val['len_']
 | 
| 38 | 
 | 
| 39 |         # This is a gdb.Value object
 | 
| 40 |         data_ = self.val['data_']
 | 
| 41 |         
 | 
| 42 |         # Make a lazystring out of it
 | 
| 43 |         # https://sourceware.org/gdb/current/onlinedocs/gdb/Values-From-Inferior.html
 | 
| 44 |         # TODO: could try utf-8 too
 | 
| 45 |         return data_.lazy_string('ascii', len_)
 | 
| 46 | 
 | 
| 47 | 
 | 
| 48 | class GcStrPrinter:
 | 
| 49 |     """Print Str type"""
 | 
| 50 | 
 | 
| 51 |     def __init__(self, val):
 | 
| 52 |         self.val = val
 | 
| 53 | 
 | 
| 54 |     def to_string(self):
 | 
| 55 |         # Unused because GDB in CLion doesn't want us to print past end of array?
 | 
| 56 |         len_ = self.val['obj_len_'] - 12 - 1
 | 
| 57 | 
 | 
| 58 |         # This is a gdb.Value object
 | 
| 59 |         data_ = self.val['data_']
 | 
| 60 |         
 | 
| 61 |         # This only prints until first NUL, since we don't have the length.
 | 
| 62 |         # note: lazy_string() only prints the first char!
 | 
| 63 |         t = gdb.lookup_type('char').pointer()
 | 
| 64 |         return data_.reinterpret_cast(t)
 | 
| 65 | 
 | 
| 66 | 
 | 
| 67 | class AsdlPrinter(object):
 | 
| 68 |     """Print the variants of a particular ASDL sum type.
 | 
| 69 | 
 | 
| 70 |     This looks at the tag in memory and casts the "super" sum type to the
 | 
| 71 |     variant type.
 | 
| 72 | 
 | 
| 73 |     It uses the children() method to return a tree structure.
 | 
| 74 |     """
 | 
| 75 |     def __init__(self, val, variants):
 | 
| 76 |         self.val = val
 | 
| 77 |         self.variants = variants
 | 
| 78 |         self.asdl_tag = None
 | 
| 79 | 
 | 
| 80 |     # This doesn't seem to make a difference
 | 
| 81 |     #def display_hint(self):
 | 
| 82 |     #    return 'map' 
 | 
| 83 | 
 | 
| 84 |     def _GetTag(self):
 | 
| 85 |       """Helper for .children() and .to_string().
 | 
| 86 | 
 | 
| 87 |       We don't know the order in which they'll be called.
 | 
| 88 |       """
 | 
| 89 |       if self.asdl_tag is None:
 | 
| 90 |         # Get address of value and look at first 16 bits
 | 
| 91 |         obj = self.val.dereference()  # location of part_value_t
 | 
| 92 | 
 | 
| 93 |         # Read the uint16_t tag
 | 
| 94 |         tag_mem = gdb.selected_inferior().read_memory(obj.address, 2)
 | 
| 95 | 
 | 
| 96 |         # Unpack 2 bytes into an integer
 | 
| 97 |         (self.asdl_tag,) = struct.unpack('H', tag_mem)
 | 
| 98 | 
 | 
| 99 |       return self.asdl_tag
 | 
| 100 | 
 | 
| 101 |     def children(self):
 | 
| 102 |         tag = self._GetTag()
 | 
| 103 | 
 | 
| 104 |         if tag in self.variants:
 | 
| 105 |           value_type = gdb.lookup_type(self.variants[tag])
 | 
| 106 |         else:
 | 
| 107 |           #pprint(self.variants)
 | 
| 108 |           raise AssertionError('Invalid tag %d' % tag)
 | 
| 109 | 
 | 
| 110 |         sub_val = self.val.cast(value_type.pointer())
 | 
| 111 | 
 | 
| 112 |         # TODO: I want to also print the tag here, e.g. part_value.String
 | 
| 113 | 
 | 
| 114 |         #print('type %s' % value_type.name)
 | 
| 115 |         #print('fields %s' % value_type.fields())
 | 
| 116 | 
 | 
| 117 |         for field in value_type.fields():
 | 
| 118 |           if not field.is_base_class:
 | 
| 119 |             #print('field %s' % field.name)
 | 
| 120 | 
 | 
| 121 |             # TODO: special case for the 'tag' field
 | 
| 122 |             # e.g. turn 1005 -> word_part.SimpleVarSub, etc.
 | 
| 123 |             yield field.name, sub_val[field]
 | 
| 124 | 
 | 
| 125 |     def to_string(self):
 | 
| 126 |         tag = self._GetTag()
 | 
| 127 | 
 | 
| 128 |         #return 'ZZ ' + self.variants.get(tag)
 | 
| 129 | 
 | 
| 130 |         # Show the variant type name, not the sum type name
 | 
| 131 |         # Note: GDB 'print' displays this prefix, but it Eclipse doesn't appear
 | 
| 132 |         # to.
 | 
| 133 |         return self.variants.get(tag)
 | 
| 134 | 
 | 
| 135 | 
 | 
| 136 | class TypeLookup(object):
 | 
| 137 |   """Return a custom pretty printer based on GDB type information."""
 | 
| 138 | 
 | 
| 139 |   def __init__(self, sum_type_lookup):
 | 
| 140 |     self.sum_type_lookup = sum_type_lookup
 | 
| 141 | 
 | 
| 142 |   def __call__(self, val):
 | 
| 143 |     # TODO: 
 | 
| 144 |     # - Tuple{2,3,4} (may be a value, not a pointer)
 | 
| 145 |     # - Dict*
 | 
| 146 | 
 | 
| 147 |     typ = val.type
 | 
| 148 |     # Str*, etc.
 | 
| 149 |     if typ.code == gdb.TYPE_CODE_PTR:
 | 
| 150 |       target = typ.target()
 | 
| 151 |       #print('target %s' % target)
 | 
| 152 |       #print('target name %r' % target.name)
 | 
| 153 |       #print('target tag %r' % target.tag)
 | 
| 154 | 
 | 
| 155 |       if target.name == 'Str':
 | 
| 156 |           return GcStrPrinter(val)
 | 
| 157 | 
 | 
| 158 |       if target.name in self.sum_type_lookup:
 | 
| 159 |           return AsdlPrinter(val, self.sum_type_lookup[target.name])
 | 
| 160 | 
 | 
| 161 |     return None
 | 
| 162 | 
 | 
| 163 | 
 | 
| 164 | def Preprocess(t):
 | 
| 165 |   """
 | 
| 166 |   Take a list of raw dicts from the ASDL compiler and make a single dict that
 | 
| 167 |   TypeLookup() can use.
 | 
| 168 | 
 | 
| 169 |   Note: technically this could be done at build time.
 | 
| 170 |   """
 | 
| 171 |   type_lookup = {}
 | 
| 172 |   for (cpp_namespace, tags_to_types) in t:
 | 
| 173 |     for sum_name, d in tags_to_types.items():
 | 
| 174 |       d2 = {}
 | 
| 175 |       for tag, type_name in d.items():
 | 
| 176 |         d2[tag] = '%s::%s' % (cpp_namespace, type_name)
 | 
| 177 |       type_lookup['%s::%s' % (cpp_namespace, sum_name)] = d2
 | 
| 178 |   return type_lookup
 | 
| 179 | 
 | 
| 180 | 
 | 
| 181 | # Each of these files defines two variables.  We append them to a global list.
 | 
| 182 | asdl_types = []
 | 
| 183 | gdb.execute('source _devbuild/gen/syntax_asdl_debug.py')
 | 
| 184 | asdl_types.append((cpp_namespace, tags_to_types))
 | 
| 185 | gdb.execute('source _devbuild/gen/runtime_asdl_debug.py')
 | 
| 186 | asdl_types.append((cpp_namespace, tags_to_types))
 | 
| 187 | 
 | 
| 188 | sum_type_lookup = Preprocess(asdl_types)
 | 
| 189 | 
 | 
| 190 | #from pprint import pprint
 | 
| 191 | #pprint(type_lookup)
 | 
| 192 | 
 | 
| 193 | 
 | 
| 194 | gdb.pretty_printers.append(TypeLookup(sum_type_lookup))
 |