# Copyright (c) 2013-2017, 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 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.kaslr_offset = kaslr_offset self._cache = {} self._gdbmi = None self.mod_table = 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) 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; int qty; }; 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 ``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 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'] """ table = [] 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\\"' """ 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 256") 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]) if match: 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)