OILS / devtools / oil_gdb.py View on Github | oilshell.org

194 lines, 76 significant
1"""
2oil_gdb.py
3
4https://fy.blackhats.net.au/blog/html/2017/08/04/so_you_want_to_script_gdb_with_python.html
5"""
6from __future__ import print_function
7
8import struct
9
10import gdb
11
12class 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.
24SimpleCommand()
25
26
27# Following:
28# https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/developer_guide/debuggingprettyprinters
29
30class 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
48class 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
67class 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
136class 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
164def 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.
182asdl_types = []
183gdb.execute('source _devbuild/gen/syntax_asdl_debug.py')
184asdl_types.append((cpp_namespace, tags_to_types))
185gdb.execute('source _devbuild/gen/runtime_asdl_debug.py')
186asdl_types.append((cpp_namespace, tags_to_types))
187
188sum_type_lookup = Preprocess(asdl_types)
189
190#from pprint import pprint
191#pprint(type_lookup)
192
193
194gdb.pretty_printers.append(TypeLookup(sum_type_lookup))