Newer
Older
# Copyright (c) 2013-2020, The Linux Foundation. All rights reserved.
#
# 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
import module_table
from print_out import print_out_str
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
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):
"""Interface to the ``gdbmi`` subprocess. This should generally be
used as a context manager (using Python's ``with`` statement),
like so::
>>> with GdbMI(gdb_path, elf) as g:
print('GDB Version: ' + g.version())
"""
def __init__(self, gdb_path, elf, kaslr_offset=0):
self.gdb_path = gdb_path
self.elf = elf
self._cache = {}
self._gdbmi = None
def open(self):
"""Open the connection to the ``gdbmi`` backend. Not needed if using
``gdbmi`` as a context manager (recommended).
"""
self._gdbmi = subprocess.Popen(
[self.gdb_path, '--interpreter=mi2', self.elf],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE
)
self._flush_gdbmi()
def close(self):
"""Close the connection to the ``gdbmi`` backend. Not needed if using
``gdbmi`` as a context manager (recommended).
"""
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 setup_module_table(self, module_table):
self.mod_table = module_table
d = {"\\":"\\\\"}
for mod_tbl_ent in self.mod_table.module_table:
load_mod_sym_cmd = 'add-symbol-file %s 0x%x' % (mod_tbl_ent.get_sym_path(), mod_tbl_ent.module_offset)
gdb_cmd = ''.join(d.get(c, c) for c in load_mod_sym_cmd)
self._run(gdb_cmd)
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
161
162
163
164
165
166
167
168
169
170
171
172
173
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):
"""Like ``container_of`` from the kernel."""
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 a dump containing an instance of the following struct::
struct pizza {
int price;
};
If you have a pointer to qty, you can get a pointer to price with:
>>> addr = sibling_field_addr(qty, 'struct pizza', 'qty', 'price')
>>> price = dump.read_int(addr)
>>> price
10
"""
return self.container_of(ptr, parent_type, member) + \
self.field_offset(parent_type, sibling)
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.
>>> hex(dump.address_of('linux_banner'))
'0xc0b0006a'
"""
result = self._run_for_one('print /x &{0}'.format(symbol))
return int(result.split(' ')[-1], 16) + self.kaslr_offset
def get_symbol_info(self, address):
"""Returns a GdbSymbol representing the nearest symbol found at
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 given 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.
>>> dump.gdbmi.get_enum_lookup_table('ion_heap_type', 10)
['ION_HEAP_TYPE_SYSTEM',
'ION_HEAP_TYPE_SYSTEM_CONTIG',
'ION_HEAP_TYPE_CARVEOUT',
'ION_HEAP_TYPE_CHUNK',
'ION_HEAP_TYPE_CUSTOM',
'ION_NUM_HEAPS',
'6',
'7',
'8',
'9']
"""
for i in range(0, upperbound):
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.
>>> dump.gdbmi.get_func_info(dump.gdbmi.address_of('panic'))
'Line 78 of \\"kernel/kernel/panic.c\\"'
"""
address = address - self.kaslr_offset
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)
def get_value_of_string(self, symbol):
"""Returns the value of a symbol (as a string)"""
self._run("set print elements 0")
cmd = 'print /s {0}'.format(symbol)
result = self._run(cmd)
if len(result.lines) == 0:
raise GdbMIException(
cmd, '\n'.join(result.lines + result.oob_lines))
match = re.search(r'^[$]\d+ = \\"(.*)(\\\\n\\")', result.lines[0])
match_1 = re.search(r'^[$]\d+ = 0x[0-9a-fA-F]+ .* \\"(.*)(\\\\n\\")', result.lines[0])
match_2 = re.search(r'^[$]\d+ = 0x[0-9a-fA-F]+ \\"(.*)(\\\\n\\")', result.lines[0])
return match.group(1).replace('\\\\n\\"',"")
elif match_1:
return match_1.group(1)
elif match_2:
return match_2.group(1).replace('\\\\n\\"', "")
return None
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)