Newer
Older
# Copyright (c) 2013-2014, The Linux Foundation. All rights reserved.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 and
# only version 2 as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
import sys
import re
import subprocess
GDB_SENTINEL = '(gdb) '
GDB_DATA_LINE = '~'
GDB_OOB_LINE = '^'
def gdb_hex_to_dec(val):
match = re.search('(0x[0-9a-fA-F]+)', val)
return int(match.group(1), 16)
class GdbSymbol(object):
def __init__(self, symbol, section, addr):
self.symbol = symbol
self.section = section
self.addr = addr
class GdbMIResult(object):
def __init__(self, lines, oob_lines):
self.lines = lines
self.oob_lines = oob_lines
class GdbMIException(Exception):
def __init__(self, *args):
self.value = '\n *** '.join([str(i) for i in args])
def __str__(self):
return self.value
class GdbMI(object):
def __init__(self, gdb_path, elf):
self.gdb_path = gdb_path
self.elf = elf
self._cache = {}
self._gdbmi = None
def open(self):
self._gdbmi = subprocess.Popen(
[self.gdb_path, '--interpreter=mi2', self.elf],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE
)
self._flush_gdbmi()
def close(self):
self._gdbmi.communicate('quit')
def __enter__(self):
self.open()
return self
def __exit__(self, ex_type, ex_value, ex_traceback):
self.close()
def _flush_gdbmi(self):
while True:
line = self._gdbmi.stdout.readline().rstrip('\r\n')
if line == GDB_SENTINEL:
break
def _run(self, cmd, skip_cache=False, save_in_cache=True):
"""Runs a gdb command and returns a GdbMIResult of the result. Results
are cached (unless skip_cache=True) for quick future lookups.
- cmd: Command to run (e.g. "show version")
- skip_cache: Don't use a previously cached result
- save_in_cache: Whether we should save this result in the cache
"""
if self._gdbmi is None:
raise Exception(
'BUG: GdbMI not initialized. ' +
'Please use GdbMI.open or a context manager.')
if not skip_cache:
if cmd in self._cache:
return GdbMIResult(self._cache[cmd], [])
self._gdbmi.stdin.write(cmd.rstrip('\n') + '\n')
self._gdbmi.stdin.flush()
output = []
oob_output = []
while True:
line = self._gdbmi.stdout.readline().rstrip('\r\n')
if line == GDB_SENTINEL:
break
if line.startswith(GDB_DATA_LINE):
# strip the leading "~"
line = line[1:]
# strip the leading and trailing "
line = line[1:-1]
# strip any trailing (possibly escaped) newlines
if line.endswith('\\n'):
line = line[:-2]
elif line.endswith('\n'):
line = line.rstrip('\n')
output.append(line)
if line.startswith(GDB_OOB_LINE):
oob_output.append(line[1:])
if save_in_cache:
self._cache[cmd] = output
return GdbMIResult(output, oob_output)
def _run_for_one(self, cmd):
result = self._run(cmd)
if len(result.lines) != 1:
raise GdbMIException(
cmd, '\n'.join(result.lines + result.oob_lines))
return result.lines[0]
def _run_for_first(self, cmd):
return self._run(cmd).lines[0]
def version(self):
"""Return GDB version"""
return self._run_for_first('show version')
def field_offset(self, the_type, field):
"""Returns the offset of a field in a struct or type.
Example:
gdbmi.field_offset("struct ion_buffer", "heap")
-> 20
- `the_type': struct or type (note that if it's a struct you
should include the word "struct" (e.g.: "struct
ion_buffer"))
- `field': the field whose offset we want to return
"""
cmd = 'print /x (int)&(({0} *)0)->{1}'.format(the_type, field)
result = self._run_for_one(cmd)
return gdb_hex_to_dec(result)
def container_of(self, ptr, the_type, member):
return ptr - self.field_offset(the_type, member)
def sibling_field_addr(self, ptr, parent_type, member, sibling):
"""Returns the address of a sibling field within the parent
structure.
Example:
Given:
struct pizza {
int price;
int *qty;
};
int quanitity = 42;
struct pizza mypizza = {.price = 10, .qty = &quanitity};
qtyp = dump.addr_lookup('quantity')
price = dump.read_int(gdbmi.sibling_field_addr(qtyp, 'struct pizza', 'qty', 'price'))
"""
return self.container_of(ptr, parent_type, member) + \
self.field_offset(parent_type, sibling)
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
def sizeof(self, the_type):
"""Returns the size of the type specified by `the_type'."""
result = self._run_for_one('print /x sizeof({0})'.format(the_type))
return gdb_hex_to_dec(result)
def address_of(self, symbol):
"""Returns the address of the specified symbol."""
result = self._run_for_one('print /x &{0}'.format(symbol))
return int(result.split(' ')[-1], 16)
def get_symbol_info(self, address):
"""Returns a GdbSymbol representing the nearest symbol found at
`address'."""
result = self._run_for_one('info symbol ' + hex(address))
parts = result.split(' ')
if len(parts) < 2:
raise GdbMIException('Output looks bogus...', result)
symbol = parts[0]
section = parts[-1]
return GdbSymbol(symbol, section, address)
def symbol_at(self, address):
"""Get the symbol at the specified address (using `get_symbol_info')"""
return self.get_symbol_info(address).symbol
def get_enum_lookup_table(self, enum, upperbound):
"""Return a table translating enum values to human readable strings."""
table = []
for i in range(0, upperbound):
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
result = self._run_for_first(
'print ((enum {0}){1})'.format(enum, i))
parts = result.split(' ')
if len(parts) < 3:
raise GdbMIException(
"can't parse enum {0} {1}\n".format(enum, i), result)
table.append(parts[2].rstrip())
return table
def get_func_info(self, address):
"""Returns the function info at a particular address, specifically line
and file."""
result = self._run_for_one('info line *0x{0:x}'.format(address))
m = re.search(r'(Line \d+ of \\?\".*\\?\")', result)
if m is not None:
return m.group(0)
else:
return '(unknown info for address 0x{0:x})'.format(address)
def get_value_of(self, symbol):
"""Returns the value of a symbol (in decimal)"""
result = self._run_for_one('print /d {0}'.format(symbol))
return int(result.split(' ')[-1], 10)
if __name__ == '__main__':
if len(sys.argv) != 3:
print('Usage: gdbmi.py gdb_path elf')
sys.exit(1)
gdb_path, elf = sys.argv[1:]
with GdbMI(gdb_path, elf) as g:
print('GDB Version: ' + g.version())
print('ion_buffer.heap offset: ' + str(g.field_offset('struct ion_buffer', 'heap')))
print('atomic_t.counter offset: ' + str(g.field_offset('atomic_t', 'counter')))
print('sizeof(struct ion_buffer): ' + str(g.sizeof('struct ion_buffer')))
addr = g.address_of('kernel_config_data')
print('address of kernel_config_data: ' + hex(addr))
symbol = g.get_symbol_info(addr)
print('symbol at ' + hex(addr) + ' : ' + symbol.symbol + \
' which is in section ' + symbol.section)