diff --git a/docs/gdbmi.rst b/docs/gdbmi.rst index f2b4726066d8138d205e5bdbd2b7452e9d01e201..4523a5a49de10763d435417a88daad76b485c6da 100644 --- a/docs/gdbmi.rst +++ b/docs/gdbmi.rst @@ -1,6 +1,9 @@ GDBMI ===== +The ``gdbmi`` module provides the low-level interface to gdb's +`gdbmi <https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI.html>`_. + .. automodule:: gdbmi :members: :undoc-members: diff --git a/docs/hacking.rst b/docs/hacking.rst index c207020cbeda1e057659531396dc09f5dc4bc596..8daffe9bfa41cdea6636e8144c523f36e28c18d7 100644 --- a/docs/hacking.rst +++ b/docs/hacking.rst @@ -7,6 +7,9 @@ looking to add their own parser plugin to the LRDP. .. toctree:: :maxdepth: 2 + writing_parsers + ramdump gdbmi - parser_util register + sizes + parser_util diff --git a/docs/ramdump.rst b/docs/ramdump.rst new file mode 100644 index 0000000000000000000000000000000000000000..cde677d5d9d8fab0cafb728a2d81aa015e13e241 --- /dev/null +++ b/docs/ramdump.rst @@ -0,0 +1,7 @@ +.. _`RamDump`: + +RamDump +======= + +.. automodule:: ramdump + :members: diff --git a/docs/register.rst b/docs/register.rst index 222af996bf4afeb218d8ea8bedccfa3fadbc8b31..296c963192514f8d6872b8b54f764afc31f6aedd 100644 --- a/docs/register.rst +++ b/docs/register.rst @@ -1,3 +1,5 @@ +.. _`register`: + register ======== diff --git a/docs/sizes.rst b/docs/sizes.rst new file mode 100644 index 0000000000000000000000000000000000000000..e9d8112bf23731b77bccfb6ebfa784910b1b9829 --- /dev/null +++ b/docs/sizes.rst @@ -0,0 +1,8 @@ +.. _`Sizes`: + +Sizes +===== + +.. automodule:: sizes + :members: + :undoc-members: diff --git a/docs/writing_parsers.rst b/docs/writing_parsers.rst new file mode 100644 index 0000000000000000000000000000000000000000..c71b3557f5c7af1ce27d0aedcdd4ee2a775a8e72 --- /dev/null +++ b/docs/writing_parsers.rst @@ -0,0 +1,7 @@ +.. _`Writing Parsers`: + +Writing Parsers +=============== + +.. autofunction:: parser_util.register_parser + :noindex: diff --git a/linux-ramdump-parser-v2/gdbmi.py b/linux-ramdump-parser-v2/gdbmi.py index 521abac9ba93fed613013b9b693eb07d80949d3d..981d14907c313e10f81fdb771d8774382add50af 100644 --- a/linux-ramdump-parser-v2/gdbmi.py +++ b/linux-ramdump-parser-v2/gdbmi.py @@ -48,6 +48,13 @@ class GdbMIException(Exception): 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): self.gdb_path = gdb_path @@ -56,6 +63,10 @@ class GdbMI(object): 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, @@ -64,6 +75,10 @@ class GdbMI(object): 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): @@ -147,11 +162,13 @@ class GdbMI(object): >>> 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")) + ``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 + ``field`` + the field whose offset we want to return """ cmd = 'print /x (int)&(({0} *)0)->{1}'.format(the_type, field) @@ -159,6 +176,7 @@ class GdbMI(object): 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): @@ -167,35 +185,40 @@ class GdbMI(object): Example: - Given: + Given a dump containing an instance of the following struct:: + struct pizza { int price; - int *qty; + int qty; }; - int quanitity = 42; - struct pizza mypizza = {.price = 10, .qty = &quanitity}; - - qtyp = dump.address_of('quantity') - price = dump.read_int(gdbmi.sibling_field_addr(qtyp, 'struct pizza', 'qty', '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'.""" + """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.""" + """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) def get_symbol_info(self, address): """Returns a GdbSymbol representing the nearest symbol found at - `address'.""" + ``address``.""" result = self._run_for_one('info symbol ' + hex(address)) parts = result.split(' ') if len(parts) < 2: @@ -205,11 +228,25 @@ class GdbMI(object): return GdbSymbol(symbol, section, address) def symbol_at(self, address): - """Get the symbol at the specified address (using `get_symbol_info')""" + """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.""" + """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( @@ -223,8 +260,13 @@ class GdbMI(object): return table def get_func_info(self, address): - """Returns the function info at a particular address, specifically line - and file.""" + """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: diff --git a/linux-ramdump-parser-v2/parser_util.py b/linux-ramdump-parser-v2/parser_util.py index 942f1e5ba3675b89b599610166189e7ddbc39ee9..bebcf1d89f24aa76865d72a480897539753df277 100644 --- a/linux-ramdump-parser-v2/parser_util.py +++ b/linux-ramdump-parser-v2/parser_util.py @@ -1,4 +1,4 @@ -# Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. +# Copyright (c) 2013-2015, 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 @@ -39,35 +39,48 @@ def cleanupString(unclean_str): def register_parser(longopt, desc, shortopt=None, optional=False): """Decorator to register a parser class (a class that inherits from - RamParser) with the parsing framework. By using this decorator + ``RamParser``) with the parsing framework. By using this decorator your parser will automatically be hooked up to the command-line parsing code. This makes it very easy and clean to add a new parser: - o Drop a new file in parsers that defines a class that inherits - from RamParser + 1. Drop a new file in the ``parsers/`` directory that defines a + class that inherits from ``RamParser`` - o Decorate your class with @register_parser + 2. Decorate your class with ``@register_parser`` - o Define a `parse' method for your class + 3. Define a ``parse`` method for your class All of the command line argument handling and invoking the parse method of your parser will then be handled automatically. + Example:: + + # file: parsers/my_banner.py + @register_parser('--banner', 'Print the kernel banner') + class BannerParser(RamParser): + + def parse(self): + print self.ramdump.read_cstring('linux_banner', 256, False) + Required arguments: - - longopt:: The longopt command line switch for this parser + ``longopt`` + The longopt command line switch for this parser - - desc:: A short description of the parser (also shown in the - help-text associated with the longopt) + ``desc`` + A short description of the parser (also shown in the help-text + associated with the longopt) Optional arguments: - - shortopt:: The shortopt command line switch for this parser + ``shortopt`` + The shortopt command line switch for this parser - - optional:: Indicates the parser is optional and should not be run with - --everything + ``optional`` + Indicates the parser is optional and should not be run with + --everything """ def wrapper(cls): @@ -79,11 +92,11 @@ def register_parser(longopt, desc, shortopt=None, optional=False): def get_parsers(): - """Imports everyone under the `parsers' directory. It is expected that + """Imports everyone under the ``parsers`` directory. It is expected that the parsers under the parsers directory will be a collection of classes that subclass RamParser and use the register_parser decorator to register themselves with the parser - framework. Therefore, importing all the modules under `parsers' + framework. Therefore, importing all the modules under ``parsers`` should have the side-effect of populating the (internal to parser_util) _parsers list with the discovered parsers. @@ -111,7 +124,7 @@ def get_parsers(): class RamParser(object): """Base class for implementing ramdump parsers. New parsers should inherit - from this class and define a `parse' method. + from this class and define a ``parse`` method. Interesting properties that will be set for usage in derived classes: @@ -180,7 +193,7 @@ def _xxd_line(addr, data): ) def xxd(address, data, file_object=None): - """Dumps data to `file_object' or stdout, in the format of `xxd'. data + """Dumps data to ``file_object`` or stdout, in the format of ``xxd``. data should be a list of integers. >>> xxd(0x1000, [0xde, 0xad, 0xbe, 0xef, 112, 105, 122, 122, 97, 0, 0, 42, 43, 44, 45, 90]) diff --git a/linux-ramdump-parser-v2/ramdump.py b/linux-ramdump-parser-v2/ramdump.py index 8a16f2dabb6bd28b30c30b2be3c4c8bbdf711234..357ba6dd16687c2bb2d53ff5f42aeaf6973ad6c5 100644 --- a/linux-ramdump-parser-v2/ramdump.py +++ b/linux-ramdump-parser-v2/ramdump.py @@ -46,6 +46,7 @@ extra_mem_file_names = ['EBI1CS1.BIN', 'DDRCS1.BIN', 'ebi1_cs1.bin', 'DDRCS0_1.B class RamDump(): + """The main interface to the RAM dump""" class Unwinder (): @@ -555,6 +556,13 @@ class RamDump(): self.gdbmi.close() def open_file(self, file_name, mode='wb'): + """Open a file in the out directory. + + Example: + + >>> with self.ramdump.open_file('pizza.txt') as p: + p.write('Pizza is the best\\n') + """ file_path = os.path.join(self.outdir, file_name) f = None try: @@ -598,6 +606,13 @@ class RamDump(): return True def get_config_val(self, config): + """Gets the value of a kernel config option. + + Example: + + >>> va_bits = int(dump.get_config_val("CONFIG_ARM64_VA_BITS")) + 39 + """ return self.config_dict.get(config) def is_config_defined(self, config): @@ -982,6 +997,13 @@ class RamDump(): stream.close() def address_of(self, symbol): + """Returns the address of a symbol. + + Example: + + >>> hex(dump.address_of('linux_banner')) + '0xffffffc000c7a0a8L' + """ try: return self.gdbmi.address_of(symbol) except gdbmi.GdbMIException: @@ -1000,40 +1022,52 @@ class RamDump(): pass def array_index(self, addr, the_type, index): - """Index into the array of type `the_type' located at `addr'. - - I.e.: - - Given: + """Index into the array of type ``the_type`` located at ``addr``. - int my_arr[3]; - my_arr[2] = 42; + I.e., given:: + int my_arr[3]; + my_arr[2] = 42; - The following: - - my_arr_addr = dump.address_of("my_arr") - dump.read_word(dump.array_index(my_arr_addr, "int", 2)) - - will return 42. + You could do the following: + >>> addr = dump.address_of("my_arr") + >>> dump.read_word(dump.array_index(addr, "int", 2)) + 42 """ offset = self.gdbmi.sizeof(the_type) * index return addr + offset def field_offset(self, the_type, field): + """Gets the offset of a field from the base of its containing struct. + + This can be useful when reading struct fields, although you should + consider using :func:`~read_structure_field` if + you're reading a word-sized value. + + Example: + + >>> dump.field_offset('struct device', 'bus') + 168 + """ try: return self.gdbmi.field_offset(the_type, field) except gdbmi.GdbMIException: pass def container_of(self, ptr, the_type, member): + """Like ``container_of`` in the kernel.""" try: return self.gdbmi.container_of(ptr, the_type, member) except gdbmi.GdbMIException: pass def sibling_field_addr(self, ptr, parent_type, member, sibling): + """Gets the address of a sibling structure field. + + Given the address of some field within a structure, returns the + address of the requested sibling field. + """ try: return self.gdbmi.sibling_field_addr(ptr, parent_type, member, sibling) except gdbmi.GdbMIException: @@ -1111,10 +1145,12 @@ class RamDump(): return s[0] if s is not None else None def read_byte(self, addr_or_name, virtual=True, cpu=None): + """Reads a single byte.""" s = self.read_string(addr_or_name, '<B', virtual, cpu) return s[0] if s is not None else None def read_bool(self, addr_or_name, virtual=True, cpu=None): + """Reads a bool.""" s = self.read_string(addr_or_name, '<?', virtual, cpu) return s[0] if s is not None else None @@ -1134,7 +1170,7 @@ class RamDump(): return s[0] if s is not None else None def read_int(self, addr_or_name, virtual=True, cpu=None): - """Alias for `read_u32'""" + """Alias for :func:`~read_u32`""" return self.read_u32(addr_or_name, virtual, cpu) def read_u16(self, addr_or_name, virtual=True, cpu=None): @@ -1143,7 +1179,7 @@ class RamDump(): return s[0] if s is not None else None def read_pointer(self, addr_or_name, virtual=True, cpu=None): - """Reads `addr_or_name' as a pointer variable. + """Reads ``addr_or_name`` as a pointer variable. The read length is either 32-bit or 64-bit depending on the architecture. This returns the *value* of the pointer variable @@ -1167,8 +1203,8 @@ class RamDump(): def read_structure_cstring(self, addr_or_name, struct_name, field, max_length=100): """reads a C string from a structure field. The C string field will be - dereferenced before reading, so it should be a `char *', not a - `char []'. + dereferenced before reading, so it should be a ``char *``, not a + ``char []``. """ virt = self.resolve_virt(addr_or_name) cstring_addr = virt + self.field_offset(struct_name, field) @@ -1176,6 +1212,7 @@ class RamDump(): def read_cstring(self, addr_or_name, max_length=100, virtual=True, cpu=None): + """Reads a C string.""" addr = addr_or_name if virtual: if cpu is not None: @@ -1213,9 +1250,9 @@ class RamDump(): return struct.unpack(format_string, s) def hexdump(self, addr_or_name, length, virtual=True, file_object=None): - """Returns a string with a hexdump (in the format of `xxd'). + """Returns a string with a hexdump (in the format of ``xxd``). - `length' is in bytes. + ``length`` is in bytes. Example (intentionally not in doctest format since it would require a specific dump to be loaded to pass as a doctest): @@ -1251,11 +1288,19 @@ class RamDump(): return self.read_word(per_cpu_offset_addr_indexed) def get_num_cpus(self): + """Gets the number of CPUs in the system.""" cpu_present_bits_addr = self.address_of('cpu_present_bits') cpu_present_bits = self.read_word(cpu_present_bits_addr) return bin(cpu_present_bits).count('1') def iter_cpus(self): + """Returns an iterator over all CPUs in the system. + + Example: + + >>> list(dump.iter_cpus()) + [0, 1, 2, 3] + """ return xrange(self.get_num_cpus()) def thread_saved_field_common_32(self, task, reg_offset):