diff --git a/linux-ramdump-parser-v2/.gitignore b/linux-ramdump-parser-v2/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..6a07bffaedfd668ad17fe71a3c5650377b7d9881 --- /dev/null +++ b/linux-ramdump-parser-v2/.gitignore @@ -0,0 +1 @@ +local_settings.py diff --git a/linux-ramdump-parser-v2/README b/linux-ramdump-parser-v2/README new file mode 100644 index 0000000000000000000000000000000000000000..6819a253f1bde68afaaedf74889e55430e345b82 --- /dev/null +++ b/linux-ramdump-parser-v2/README @@ -0,0 +1,72 @@ +Python Linux Ramdump Parser + +What does this tool do? +---------------------------------- +This tool takes as its input a vmlinux symbol file, and files representing +memory from devices that run Linux. It proceeds to dump useful information +such as process stacks, IRQ and workqueue information. + +What does this tool need? +---------------------------------- +1) Python. This tool has been tested with Python 2.6.5 on both Linux and Windows +1) a set of RAM dumps. Ideally, the load location of each dump as well. +2) The corresponding vmlinux file + +How is this tool invoked? +---------------------------------- +python ramparse.py invokes the parser. Options: + +--ram-file <file path> <start> <end> : Add this ram file to be parsed. +At least one of --ram-file and --auto-dump required + +--vmlinux <path> : path for vmlinux to use. This is required + +--auto-dump <path to folder> : Automatically find files for a RAM dump and +detect useful informaton. + +--gdb-path <path> : path for the GNU gdb debugger. If no path is given, the +path will be used from local_settings.py + +--gdb-path <path> : path for the nm tool. If no path is given, the +path will be used from local_settings.py + +--outdir <path> : Output directory to store any files written. If no path is +given, the ramfile directory will be used if given, else the current directory +will be used. + +--out-file <path> : File to write all output to. If no path is given, +linux-parser-output.txt is used + +--stdout : Write to stdout instead of the out-file. This overrides any +--out-file given. + +The list of features parsed is constantly growing. Please use --help option +to see the full list of features that can be parsed. + +Setting up the toolchains +------------------------------- +The parser relies on having access to gdb and nm to work. You will need to +specify the paths to these tools. This can be done in three ways + +1) Using --gdb-path and --nm-path to specify the absolute path +2) Using CROSS_COMPILE to specify the prefix +3) Using local_settings.py as described below + +Just having gdb/nm on the path is not supported as there are too many +variations on names to invoke. + +local_settings.py +------------------------------- +The parser attempts to figure out most of the settings automatically but there +are some settings that are unique to the environment of the running system. +These must be specified in local_settings.py. The current format for the file +is + +<setting name> = <string identifying the feaure> + +Currently supported features: +gdb_path - absolute path to the gdb tool for the ramdumps +nm_path - absolute path to the gdb tool for the ramdumps + +Note that local_settings.py is just a python file so the file may take advantage +of python features. diff --git a/linux-ramdump-parser-v2/bitops.py b/linux-ramdump-parser-v2/bitops.py new file mode 100644 index 0000000000000000000000000000000000000000..dc2d3108425cb1f5ee225f53bed8a7b66d700776 --- /dev/null +++ b/linux-ramdump-parser-v2/bitops.py @@ -0,0 +1,20 @@ +# Copyright (c) 2013, 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. + + +def bm(msb, lsb): + 'Creates a bitmask from msb to lsb' + return int(('1' * (msb - lsb + 1)) + ('0' * lsb), 2) + + +def bvalsel(msb, lsb, val): + 'Masks and returns the bits from msb to lsb in val' + return ((val & bm(msb, lsb)) >> lsb) diff --git a/linux-ramdump-parser-v2/gdbmi.py b/linux-ramdump-parser-v2/gdbmi.py new file mode 100644 index 0000000000000000000000000000000000000000..cdccdd078ea4d0622ca0d35d659522dc7158874a --- /dev/null +++ b/linux-ramdump-parser-v2/gdbmi.py @@ -0,0 +1,231 @@ +# Copyright (c) 2013, 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 + +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 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 xrange(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.""" + 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:', g.field_offset('struct ion_buffer', 'heap') + print 'atomic_t.counter offset:', g.field_offset('atomic_t', 'counter') + print 'sizeof(struct ion_buffer):', 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 diff --git a/linux-ramdump-parser-v2/linux_list.py b/linux-ramdump-parser-v2/linux_list.py new file mode 100644 index 0000000000000000000000000000000000000000..cb526a30a5ba0a3752a538a1f6b5df3619048fda --- /dev/null +++ b/linux-ramdump-parser-v2/linux_list.py @@ -0,0 +1,66 @@ +# Copyright (c) 2013, 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. + +from print_out import print_out_str + +''' +struct list_head { + struct list_head *next, *prev; +}; +''' + + +def get_list_offsets(ram_dump): + next_offset = ram_dump.field_offset('struct list_head', 'next') + prev_offset = ram_dump.field_offset('struct list_head', 'prev') + return next_offset, prev_offset + + +class ListWalker(object): + + ''' + ram_dump: Reference to the ram dump + node_addr: The address of the first element of the list + list_elem_offset: The offset of the list_head in the structure that this list is container for. + next_offset: The offset for the next pointer in the list + prev_offset: The offset for the prev pointer in the list + ''' + + def __init__(self, ram_dump, node_addr, list_elem_offset, next_offset, prev_offset): + self.LIST_OFFSETS = [ + ('((struct list_head *)0x0)', 'next', 0, 0), + ('((struct list_head *)0x0)', 'prev', 0, 0), + ] + self.LIST_NEXT_IDX = 0 + self.LIST_PREV_IDX = 1 + + self.ram_dump = ram_dump + self.next_offset = next_offset + self.prev_offset = prev_offset + self.list_elem_offset = list_elem_offset + + self.last_node = node_addr + self.seen_nodes = [] + + def walk(self, node_addr, func): + if node_addr != 0: + func(node_addr - self.list_elem_offset) + + next_node_addr = node_addr + self.next_offset + next_node = self.ram_dump.read_word(next_node_addr) + + if next_node != self.last_node: + if next_node in self.seen_nodes: + print_out_str( + '[!] WARNING: Cycle found in attach list for IOMMU domain. List is corrupted!') + else: + self.seen_nodes.append(node_addr) + self.walk(next_node, func) diff --git a/linux-ramdump-parser-v2/mm.py b/linux-ramdump-parser-v2/mm.py new file mode 100644 index 0000000000000000000000000000000000000000..3e6f6ff86d1904865760a0d5428e2486e411803a --- /dev/null +++ b/linux-ramdump-parser-v2/mm.py @@ -0,0 +1,216 @@ +# Copyright (c) 2013, 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. + + +def page_buddy(ramdump, page): + mapcount_offset = ramdump.field_offset('struct page', '_mapcount') + val = ramdump.read_word(page + mapcount_offset) + # -128 is the magic for in the buddy allocator + return val == 0xffffff80 + + +def page_zonenum(page_flags): + # save this in a variable somewhere... + return (page_flags >> 26) & 3 + + +def page_to_nid(page_flags): + return 0 + + +def page_zone(ramdump, page): + contig_page_data = ramdump.addr_lookup('contig_page_data') + node_zones_offset = ramdump.field_offset( + 'struct pglist_data', 'node_zones') + page_flags_offset = ramdump.field_offset('struct page', 'flags') + zone_size = ramdump.sizeof('struct zone') + page_flags = ramdump.read_word(page + page_flags_offset) + if page_flags is None: + return None + zone = contig_page_data + node_zones_offset + \ + (page_zonenum(page_flags) * zone_size) + return zone + + +def zone_is_highmem(ramdump, zone): + if zone is None: + return False + # not at all how linux does it but it works for our purposes... + zone_name_offset = ramdump.field_offset('struct zone', 'name') + zone_name_addr = ramdump.read_word(zone + zone_name_offset) + if zone_name_addr is None: + return False + zone_name = ramdump.read_cstring(zone_name_addr, 48) + if zone_name is None: + # XXX do something? + return False + if zone_name == 'HighMem': + return True + else: + return False + + +def hash32(val, bits): + chash = c_uint(val * 0x9e370001).value + return chash >> (32 - bits) + + +def page_slot(ramdump, page): + hashed = hash32(page, 7) + htable = ramdump.addr_lookup('page_address_htable') + htable_size = ramdump.sizeof('page_address_htable[0]') + return htable + htable_size * hashed + + +def page_to_section(page_flags): + # again savefn8n variable + return (page_flags >> 28) & 0xF + + +def nr_to_section(ramdump, sec_num): + memsection_struct_size = ramdump.sizeof('struct mem_section') + sections_per_root = 4096 / memsection_struct_size + sect_nr_to_root = sec_num / sections_per_root + masked = sec_num & (sections_per_root - 1) + mem_section_addr = ramdump.addr_lookup('mem_section') + mem_section = ramdump.read_word(mem_section_addr) + if mem_section is None: + return None + return mem_section + memsection_struct_size * (sect_nr_to_root * sections_per_root + masked) + + +def section_mem_map_addr(ramdump, section): + map_offset = ramdump.field_offset('struct mem_section', 'section_mem_map') + result = ramdump.read_word(section + map_offset) + return result & ~((1 << 2) - 1) + + +def pfn_to_section_nr(pfn): + return pfn >> (28 - 12) + + +def pfn_to_section(ramdump, pfn): + return nr_to_section(ramdump, pfn_to_section_nr(pfn)) + + +def pfn_to_page_sparse(ramdump, pfn): + sec = pfn_to_section(ramdump, pfn) + sizeof_page = ramdump.sizeof('struct page') + return section_mem_map_addr(ramdump, sec) + pfn * sizeof_page + + +def page_to_pfn_sparse(ramdump, page): + page_flags_offset = ramdump.field_offset('struct page', 'flags') + sizeof_page = ramdump.sizeof('struct page') + flags = ramdump.read_word(page + page_flags_offset) + if flags is None: + return 0 + section = page_to_section(flags) + nr = nr_to_section(ramdump, section) + addr = section_mem_map_addr(ramdump, nr) + # divide by struct page size for division fun + return (page - addr) / sizeof_page + + +def page_to_pfn_flat(ramdump, page): + mem_map_addr = ramdump.addr_lookup('mem_map') + mem_map = ramdump.read_word(mem_map_addr) + page_size = ramdump.sizeof('struct page') + # XXX Needs to change for LPAE + pfn_offset = ramdump.phys_offset >> 12 + return ((page - mem_map) / page_size) + pfn_offset + + +def pfn_to_page_flat(ramdump, pfn): + mem_map_addr = ramdump.addr_lookup('mem_map') + mem_map = ramdump.read_word(mem_map_addr) + page_size = ramdump.sizeof('struct page') + # XXX Needs to change for LPAE + pfn_offset = ramdump.phys_offset >> 12 + return mem_map + (pfn * page_size) - pfn_offset + + +def page_to_pfn(ramdump, page): + if ramdump.is_config_defined('CONFIG_SPARSEMEM'): + return page_to_pfn_sparse(ramdump, page) + else: + return page_to_pfn_flat(ramdump, page) + + +def pfn_to_page(ramdump, pfn): + if ramdump.is_config_defined('CONFIG_SPARSEMEM'): + return pfn_to_page_sparse(ramdump, pfn) + else: + return pfn_to_page_flat(ramdump, pfn) + + +def sparsemem_lowmem_page_address(ramdump, page): + membank1_start = ramdump.read_word(ramdump.addr_lookup('membank1_start')) + membank0_size = ramdump.read_word(ramdump.addr_lookup('membank0_size')) + # XXX currently magic + membank0_phys_offset = ramdump.phys_offset + membank0_page_offset = 0xc0000000 + membank1_phys_offset = membank1_start + membank1_page_offset = membank0_page_offset + membank0_size + phys = page_to_pfn(ramdump, page) << 12 + if phys >= membank1_start: + return phys - membank1_phys_offset + membank1_page_offset + else: + return phys - membank0_phys_offset + membank0_page_offset + + +def dont_map_hole_lowmem_page_address(ramdump, page): + phys = page_to_pfn(ramdump, page) << 12 + hole_end_addr = ramdump.addr_lookup('memory_hole_end') + hole_offset_addr = ramdump.addr_lookup('memory_hole_offset') + hole_end = ramdump.read_word(hole_end_addr) + hole_offset = ramdump.read_word(hole_offset_addr) + if hole_end != 0 and phys >= hole_end: + return phys - hole_end + hole_offset + 0xc0000000 + else: + return phys - ramdump.phys_offset + 0xc0000000 + + +def normal_lowmem_page_address(ramdump, page): + phys = page_to_pfn(ramdump, page) << 12 + return phys - ramdump.phys_offset + 0xc0000000 + + +def lowmem_page_address(ramdump, page): + if ramdump.is_config_defined('CONFIG_SPARSEMEM'): + return sparsemem_lowmem_page_address(ramdump, page) + elif ramdump.is_config_defined('CONFIG_DONT_MAP_HOLE_AFTER_MEMBANK0'): + return dont_map_hole_lowmem_page_address(ramdump, page) + else: + return normal_lowmem_page_address(ramdump, page) + + +def page_address(ramdump, page): + if not zone_is_highmem(ramdump, page_zone(ramdump, page)): + return lowmem_page_address(ramdump, page) + + pas = page_slot(ramdump, page) + lh_offset = ramdump.field_offset('struct page_address_slot', 'lh') + start = pas + lh_offset + pam = start + while True: + pam = pam - lh_offset + pam_page_offset = ramdump.field_offset( + 'struct page_address_map', 'page') + pam_virtual_offset = ramdump.field_offset( + 'struct page_address_map', 'virtual') + pam_page = ramdump.read_word(pam + pam_page_offset) + if pam_page == page: + ret = ramdump.read_word(pam + pam_virtual_offset) + return ret + pam = ramdump.read_word(pam + lh_offset) + if pam == start: + return None diff --git a/linux-ramdump-parser-v2/mmu.py b/linux-ramdump-parser-v2/mmu.py new file mode 100644 index 0000000000000000000000000000000000000000..dc18400d43f18580d0a2113ab994fd7731c1f2a8 --- /dev/null +++ b/linux-ramdump-parser-v2/mmu.py @@ -0,0 +1,332 @@ +# Copyright (c) 2013, 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. + +from bitops import bm, bvalsel +from register import Register + + +class MMU(object): + + """Represents an MMU. Does virtual-to-physical address lookups, + caching the results in a TLB. + + This is an abstract class that should not be used + directly. Concrete subclasses should override the following + methods: + + - load_page_tables() + + - page_table_walk(addr) + + - dump_page_tables(file_object) + + + Interesting properties that will be set for usage in derived + classes: + + - ramdump:: The RamDump instance being parsed + + """ + + def __init__(self, ramdump): + self._tlb = {} + self.ramdump = ramdump + self.ttbr = None + self.load_page_tables() + + def virt_to_phys(self, addr, skip_tlb=False, save_in_tlb=True): + if addr is None: + return None + + if not skip_tlb: + if addr in self._tlb: + return self._tlb[addr] + + phys_addr = self.page_table_walk(addr) + + if save_in_tlb: + self._tlb[addr] = phys_addr + + return phys_addr + + def load_page_tables(self): + raise NotImplementedError + + def page_table_walk(self, virt): + raise NotImplementedError + + def dump_page_tables(self, f): + raise NotImplementedError + + +class Armv7MMU(MMU): + + """An MMU for ARMv7 (no LPAE).""" + + def load_page_tables(self): + self.global_page_table = [0 for i in range(4096)] + self.secondary_page_tables = [ + [0 for col in range(256)] for row in range(4096)] + + msm_ttbr0 = self.ramdump.phys_offset + 0x4000 + self.ttbr = msm_ttbr0 + virt_address = 0x0 + gb_i = 0 + se_i = 0 + for l1_pte_ptr in range(msm_ttbr0, msm_ttbr0 + (4096 * 4), 4): + l1_pte = self.ramdump.read_word(l1_pte_ptr, False) + self.global_page_table[gb_i] = l1_pte + if l1_pte is None: + gb_i += 1 + continue + if (l1_pte & 3) == 0 or (l1_pte & 3) == 3: + for k in range(0, 256): + virt_address += 0x1000 + elif (l1_pte & 3) == 2: + if ((l1_pte & 0x40000) == 0): + l1_pte_counter = l1_pte & 0xFFF00000 + for k in range(0, 256): + virt_address += 0x1000 + l1_pte_counter += 0x1000 + else: + gb_i += 1 + continue + elif (l1_pte & 3) == 1: + l2_pt_desc = l1_pte + l2_pt_base = l2_pt_desc & (~0x3ff) + for l2_pte_ptr in range(l2_pt_base, l2_pt_base + (256 * 4), 4): + virt_address += 0x1000 + l2_pt_entry = self.ramdump.read_word(l2_pte_ptr, False) + self.secondary_page_tables[gb_i][se_i] = l2_pt_entry + se_i += 1 + se_i = 0 + gb_i += 1 + + def page_table_walk(self, virt): + global_offset = bvalsel(31, 20, virt) + l1_pte = self.global_page_table[global_offset] + bit18 = (l1_pte & 0x40000) >> 18 + if (bvalsel(1, 0, l1_pte) == 1): + l2_offset = bvalsel(19, 12, virt) + l2_pte = self.secondary_page_tables[global_offset][l2_offset] + if l2_pte is None: + return None + if (bvalsel(1, 0, l2_pte) == 2) or (bvalsel(1, 0, l2_pte) == 3): + entry4kb = (l2_pte & bm(31, 12)) + bvalsel(11, 0, virt) + return entry4kb + elif (bvalsel(1, 0, l2_pte) == 1): + entry64kb = (l2_pte & bm(31, 16)) + bvalsel(15, 0, virt) + return entry64kb + if (bvalsel(1, 0, l1_pte) == 2): + onemb_entry = bm(31, 20) & l1_pte + onemb_entry += bvalsel(19, 0, virt) + return onemb_entry + + return 0 + + def dump_page_tables(self, f): + f.write( + 'Dumping page tables is not currently supported for Armv7MMU\n') + f.flush() + + +class Armv7LPAEMMU(MMU): + + """An MMU for ARMv7 (with LPAE)""" + # Descriptor types + DESCRIPTOR_INVALID = 0x0 + DESCRIPTOR_BLOCK = 0x1 + DESCRIPTOR_TABLE = 0x3 + TL_DESCRIPTOR_RESERVED = 0x1 + TL_DESCRIPTOR_PAGE = 0x3 + + def do_fl_sl_level_lookup(self, table_base_address, table_index, + input_addr_split, block_split): + descriptor, addr = self.do_level_lookup( + table_base_address, table_index, + input_addr_split) + if descriptor.dtype == Armv7LPAEMMU.DESCRIPTOR_BLOCK: + descriptor.add_field('output_address', (39, block_split)) + elif descriptor.dtype == Armv7LPAEMMU.DESCRIPTOR_TABLE: + # we have bits 39:12 of the next-level table in + # next_level_base_addr_upper + descriptor.add_field('next_level_base_addr_upper', (39, 12)) + else: + raise Exception( + 'Invalid stage 1 first- or second-level translation\ndescriptor: (%s)\naddr: (%s)' + % (str(descriptor), str(addr)) + ) + return descriptor + + def do_fl_level_lookup(self, table_base_address, table_index, + input_addr_split): + return do_fl_sl_level_lookup(table_base_address, table_index, + input_addr_split, 30) + + def do_sl_level_lookup(self, table_base_address, table_index): + return do_fl_sl_level_lookup(table_base_address, table_index, + 12, 21) + + def do_tl_level_lookup(self, table_base_address, table_index): + descriptor, addr = self.do_level_lookup( + table_base_address, table_index, 12) + if descriptor.dtype == Armv7LPAEMMU.TL_DESCRIPTOR_PAGE: + descriptor.add_field('output_address', (39, 12)) + else: + raise Exception( + 'Invalid stage 1 third-level translation\ndescriptor: (%s)\naddr: (%s)' + % (str(descriptor), str(addr)) + ) + return descriptor + + def do_level_lookup(self, table_base_address, table_index, + input_addr_split): + """Does a base + index descriptor lookup. + + Returns a tuple with the Register object representing the found + descriptor and a Register object representing the the computed + descriptor address. + + """ + n = input_addr_split + # these Registers are overkill but nice documentation:). + table_base = Register(table_base_address, base=(39, n)) + descriptor_addr = Register(base=(39, n), + offset=(n - 1, 3)) + descriptor_addr.base = table_base.base + descriptor_addr.offset = table_index + descriptor_val = self.read_phys_dword(descriptor_addr.value) + descriptor = Register(descriptor_val, + dtype=(1, 0)) + return descriptor, descriptor_addr + + def block_or_page_desc_2_phys(self, desc, virt_r, n): + phys = Register(output_address=(39, n), + page_offset=(n - 1, 0)) + phys.output_address = desc.output_address + virt_r.add_field('rest', (n - 1, 0)) + phys.page_offset |= virt_r.rest + return phys.value + + def fl_block_desc_2_phys(self, desc, virt_r): + """Block descriptor to physical address.""" + return self.block_or_page_desc_2_phys(desc, virt_r, 30) + + def sl_block_desc_2_phys(self, desc, virt_r): + """Block descriptor to physical address.""" + return self.block_or_page_desc_2_phys(desc, virt_r, 21) + + def tl_page_desc_2_phys(self, desc, virt_r): + """Page descriptor to physical address.""" + return self.block_or_page_desc_2_phys(desc, virt_r, 12) + + def read_phys_dword(self, physaddr): + return self.ramdump.read_dword(physaddr, virtual=False) + + def load_page_tables(self): + pass + + def page_table_walk(self, virt): + text_offset = 0x8000 + pg_dir_size = 0x5000 # 0x4000 for non-LPAE + swapper_pg_dir_addr = self.ramdump.phys_offset + \ + text_offset - pg_dir_size + + # We deduce ttbr1 and ttbcr.t1sz based on the value of + # PAGE_OFFSET. This is based on v7_ttb_setup in + # arch/arm/mm/proc-v7-3level.S: + + # * TTBR0/TTBR1 split (PAGE_OFFSET): + # * 0x40000000: T0SZ = 2, T1SZ = 0 (not used) + # * 0x80000000: T0SZ = 0, T1SZ = 1 + # * 0xc0000000: T0SZ = 0, T1SZ = 2 + ttbr = swapper_pg_dir_addr + self.ttbr = ttbr + if self.ramdump.page_offset == 0x40000000: + t1sz = 0 + initial_lkup_level = 1 + elif self.ramdump.page_offset == 0x80000000: + t1sz = 1 + initial_lkup_level = 1 + elif self.ramdump.page_offset == 0xc0000000: + t1sz = 2 + # need to fixup ttbr1 since we'll be skipping the + # first-level lookup (see v7_ttb_setup): + # /* PAGE_OFFSET == 0xc0000000, T1SZ == 2 */ + # add \ttbr1, \ttbr1, #4096 * (1 + 3) @ only L2 used, skip + # pgd+3*pmd + ttbr += (4096 * (1 + 3)) + initial_lkup_level = 2 + else: + raise Exception( + 'Invalid phys_offset for page_table_walk: 0x%x' + % self.ramdump.page_offset) + + if initial_lkup_level == 1: + # see the ARMv7 ARM B3.6.6 (rev 0406C.b): + input_addr_split = 5 - t1sz + if input_addr_split not in [4, 5]: + raise Exception("Invalid stage 1 first-level `n' value: 0x%x" + % input_addr_split) + virt_r = Register(virt, + fl_index=(input_addr_split + 26, 30), + sl_index=(29, 21), + tl_index=(20, 12), + page_index=(11, 0)) + fl_desc = self.do_fl_level_lookup( + ttbr, virt_r.fl_index, input_addr_split) + + # if we got a block descriptor we're done: + if fl_desc.dtype == Armv7LPAEMMU.DESCRIPTOR_BLOCK: + return self.fl_block_desc_2_phys(fl_desc, virt_r) + + base = Register(base=(39, 12)) + base.base = fl_desc.next_level_base_addr_upper + sl_desc = self.do_sl_level_lookup( + base.value, virt_r.sl_index) + + elif initial_lkup_level == 2: + # see the ARMv7 ARM B3.6.6 (rev 0406C.b): + input_addr_split = 14 - t1sz + if input_addr_split not in range(7, 13): + raise Exception("Invalid stage 1 second-level (initial) `n' value: 0x%x" + % input_addr_split) + virt_r = Register(virt, + sl_index=(input_addr_split + 17, 21), + tl_index=(20, 12), + page_index=(11, 0)) + try: + sl_desc = self.do_fl_sl_level_lookup( + ttbr, virt_r.sl_index, input_addr_split, 21) + except: + return None + else: + raise Exception('Invalid initial lookup level (0x%x)' % + initial_lkup_level) + + # if we got a block descriptor we're done: + if sl_desc.dtype == Armv7LPAEMMU.DESCRIPTOR_BLOCK: + return self.sl_block_desc_2_phys(sl_desc, virt_r) + + base = Register(base=(39, 12)) + base.base = sl_desc.next_level_base_addr_upper + try: + tl_desc = self.do_tl_level_lookup( + base.value, virt_r.tl_index) + except: + return None + + return self.tl_page_desc_2_phys(tl_desc, virt_r) + + def dump_page_tables(self, f): + f.write( + 'Dumping page tables is not currently supported for Armv7LPAEMMU\n') + f.flush() diff --git a/linux-ramdump-parser-v2/parser_util.py b/linux-ramdump-parser-v2/parser_util.py new file mode 100644 index 0000000000000000000000000000000000000000..2b03cba10a1bff24837cb8a658a8ded7be7b165a --- /dev/null +++ b/linux-ramdump-parser-v2/parser_util.py @@ -0,0 +1,156 @@ +# Copyright (c) 2013, 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 os +import platform +import glob +import re + +_parsers = [] + + +class ParserConfig(object): + + """Class to encapsulate a RamParser its desired setup (command-line + options, etc).""" + + def __init__(self, cls, longopt, desc, shortopt, optional): + self.cls = cls + self.longopt = longopt + self.desc = desc + self.shortopt = shortopt + self.optional = optional + + +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 + 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 + + o Decorate your class with @register_parser + + o 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. + + Required arguments: + + - 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) + + Optional arguments: + + - shortopt:: The shortopt command line switch for this parser + + - optional:: Indicates the parser is optional and should not be run with + --everything + + """ + def wrapper(cls): + if cls in [p.cls for p in _parsers]: + raise Exception(cls + ' is already registered!') + _parsers.append(ParserConfig(cls, longopt, desc, shortopt, optional)) + return cls + return wrapper + + +def get_parsers(): + """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' + should have the side-effect of populating the (internal to + parser_util) _parsers list with the discovered parsers. + + Returns the list of ParserConfig instances built as a side-effect + of the importing. + + """ + parsers_dir = os.path.join(os.path.dirname(__file__), 'parsers') + for f in glob.glob(os.path.join(parsers_dir, '*.py')): + modname_ext = os.path.basename(f) + if modname_ext == '__init__.py': + continue + modname = 'parsers.' + os.path.splitext(modname_ext)[0] + # if the module contains a class (or classes) that are + # decorated with `register_parser' then the following import + # will have the side-effect of adding that class (encapsulated + # in a ParserConfig object) to the _parsers list. Note that + # this import is effectively a noop if the module has already + # been imported, so there's no harm in calling get_parsers + # multiple times. + __import__(modname) + return _parsers + + +class RamParser(object): + + """Base class for implementing ramdump parsers. New parsers should inherit + from this class and define a `parse' method. + + Interesting properties that will be set for usage in derived + classes: + + - ramdump:: The RamDump instance being parsed + + """ + + def __init__(self, ramdump): + self.ramdump = ramdump + + def parse(self): + raise NotImplementedError + + +def which(program): + """Just like which(1). + + Searches the PATH environment variable for a directory containing + program. + + """ + for path in os.environ['PATH'].split(os.pathsep): + exe_file = os.path.join(path, program) + if os.access(exe_file, os.X_OK): + return exe_file + + return None + + +def get_system_type(): + """Returns a "normalized" version of platform.system (transforming CYGWIN + to Windows, for example). + + Returns None if not a supported platform. + + """ + plat = platform.system() + if plat == 'Windows': + return 'Windows' + if re.search('CYGWIN', plat) is not None: + # On certain installs, the default windows shell + # runs cygwin. Treat cygwin as windows for this + # purpose + return 'Windows' + if plat == 'Linux': + return 'Linux' + if plat == 'Darwin': + return 'Darwin' diff --git a/linux-ramdump-parser-v2/parsers/__init__.py b/linux-ramdump-parser-v2/parsers/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/linux-ramdump-parser-v2/parsers/cachedump.py b/linux-ramdump-parser-v2/parsers/cachedump.py new file mode 100644 index 0000000000000000000000000000000000000000..76570005cc558edfe03dae95e6e73eeefa52a761 --- /dev/null +++ b/linux-ramdump-parser-v2/parsers/cachedump.py @@ -0,0 +1,124 @@ +# Copyright (c) 2012-2013, 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 struct + +from parser_util import register_parser, RamParser +from print_out import print_out_str + +# assuming cache way size of 8, fix this for badger probably +cache_way = 8 + + +def save_l1_dump(ram_dump, cache_base, size): + with ram_dump.open_file('l1_cache_dump.bin') as cache_file: + + for i in range(0, size): + val = ram_dump.read_byte(cache_base + i, False) + cache_file.write(struct.pack('<B', val)) + print_out_str('--- Wrote cache dump to l1_cache_dump.bin') + + +def parse_cache_dump(ram_dump, cache_base): + + magic_num_offset = ram_dump.field_offset( + 'struct l2_cache_dump', 'magic_number') + version_offset = ram_dump.field_offset('struct l2_cache_dump', 'version') + line_size_offset = ram_dump.field_offset( + 'struct l2_cache_dump', 'line_size') + total_lines_offset = ram_dump.field_offset( + 'struct l2_cache_dump', 'total_lines') + cache_offset_struct = ram_dump.field_offset( + 'struct l2_cache_dump', 'cache') + l2dcrtr0_offset_struct = ram_dump.field_offset( + 'struct l2_cache_line_dump', 'l2dcrtr0_val') + l2dcrtr1_offset_struct = ram_dump.field_offset( + 'struct l2_cache_line_dump', 'l2dcrtr1_val') + cache_line_data_offset_struct = ram_dump.field_offset( + 'struct l2_cache_line_dump', 'cache_line_data') + cache_line_struct_size = ram_dump.sizeof('struct l2_cache_line_dump') + + magic = ram_dump.read_word(cache_base + magic_num_offset, False) + version = ram_dump.read_word(cache_base + version_offset, False) + line_size = ram_dump.read_word(cache_base + line_size_offset, False) + total_lines = ram_dump.read_word(cache_base + total_lines_offset, False) + cache = ram_dump.read_word(cache_base + cache_offset_struct, False) + + cache_file = ram_dump.open_file('l2_cache_dump.txt') + + cache_file.write('Magic = {0:x}\n'.format(magic)) + cache_file.write('version = {0:x}\n'.format(version)) + cache_file.write('line size = {0:x}\n'.format(line_size)) + + select = 0 + lines = total_lines / cache_way + + header_str = '({0:4},{1:1}) {2:5} {3:8} '.format( + 'Set', 'Way', 'valid', 'Address') + # currently assumes 32 bit word like everything else... + for i in range(0, 32): + header_str = header_str + '{0:8} '.format('Word{0}'.format(i)) + + header_str = header_str + '{0:8} {1:8}\n'.format('L2DCRTR0', 'L2DCRTR0') + + cache_ptr = cache_base + cache_offset_struct + + for i in range(0, lines): + + cache_file.write(header_str) + + for j in range(0, cache_way): + cache_line_ptr = cache_ptr + (i * cache_way + j) * line_size + + l2dcrtr0_val = ram_dump.read_word( + cache_line_ptr + l2dcrtr0_offset_struct, False) + l2dcrtr1_val = ram_dump.read_word( + cache_line_ptr + l2dcrtr1_offset_struct, False) + + # this is valid for krait, will probably need to be more generic + + addr = l2dcrtr1_val & 0xFFFE0000 + addr = addr | (select & 0x0001ff80) + valid = (l2dcrtr0_val >> 14) & 0x3 + + out_str = '({0:4},{1:1}) {2:5} {3:8x} '.format(i, j, valid, addr) + + cache_line_data_ptr = cache_line_ptr + \ + cache_line_data_offset_struct + + for k in range(0, 32): + out_str = out_str + \ + '{0:0=8x} '.format( + ram_dump.read_word(cache_line_data_ptr + 4 * k, False)) + + out_str = out_str + \ + '{0:0=8x} {1:0=8x}\n'.format(l2dcrtr0_val, l2dcrtr1_val) + + cache_file.write(out_str) + select = select + 0x10 + + cache_file.close() + print_out_str('--- Wrote cache dump to l2_cache_dump.txt') + + +@register_parser('--print-cache-dump', 'Print L2 cache dump', optional=True) +class CacheDump(RamParser): + + def parse(self): + if not self.ramdump.is_config_defined('CONFIG_MSM_CACHE_DUMP'): + print_out_str( + '!!! Cache dumping was not enabled. No cache will be dumped') + return + + cache_base_addr = self.ramdump.addr_lookup('l2_dump') + cache_base = self.ramdump.read_word(cache_base_addr) + + parse_cache_dump(self.ramdump, cache_base) diff --git a/linux-ramdump-parser-v2/parsers/cpu_state.py b/linux-ramdump-parser-v2/parsers/cpu_state.py new file mode 100644 index 0000000000000000000000000000000000000000..97be12ff0f339f02603f9e5db8fa33ad63895f01 --- /dev/null +++ b/linux-ramdump-parser-v2/parsers/cpu_state.py @@ -0,0 +1,65 @@ +# Copyright (c) 2013, 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. + +from itertools import cycle + +from parser_util import register_parser, RamParser +from print_out import print_out_str + + +@register_parser('--cpu-state', "Reads register values of non-panic'ing CPUs") +class CpuState(RamParser): + + def parse(self): + regs_before_stop_addr = self.ramdump.addr_lookup('regs_before_stop') + if regs_before_stop_addr is None: + print_out_str('regs_before_stop not found. Nothing to do.') + return + + # see pt_regs and associated #defines in + # arch/arm/include/asm/ptrace.h + regs = ( + 'r0', + 'r1', + 'r2', + 'r3', + 'r4', + 'r5', + 'r6', + 'r7', + 'r8', + 'r9', + 'r10', + 'fp', + 'ip', + 'sp', + 'lr', + 'pc', + 'cpsr', + ) + + max_len = max([len(s) for s in regs]) + + for cpu in self.ramdump.iter_cpus(): + print_out_str('CPU %d' % cpu) + lines = [] + for index, reg in enumerate(regs): + reg_addr = self.ramdump.array_index( + regs_before_stop_addr, 'unsigned long', index) + reg_val = self.ramdump.read_word(reg_addr, cpu=cpu) + lines.append( + ' {0:{width}} = 0x{1:x}'.format(reg, reg_val, width=max_len)) + + c = cycle([', ', ', ', ', ', '\n']) + output = '' + for line in lines: + output += line + next(c) + print_out_str(output) diff --git a/linux-ramdump-parser-v2/parsers/debug_image.py b/linux-ramdump-parser-v2/parsers/debug_image.py new file mode 100644 index 0000000000000000000000000000000000000000..89576fe28a0b2e008a763bab0a466caf3a3af1c4 --- /dev/null +++ b/linux-ramdump-parser-v2/parsers/debug_image.py @@ -0,0 +1,190 @@ +# Copyright (c) 2012-2013, 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 struct + +from parser_util import register_parser, RamParser +from print_out import print_out_str +from qdss import QDSSDump +from cachedump import save_l1_dump, parse_cache_dump + +QDSS_MAGIC = 0x5D1DB1Bf + +print_table = { + 'MSM_CPU_CTXT': 'parse_cpu_ctx', + 'MSM_L1_CACHE': 'parse_l1_cache', + 'MSM_L2_CACHE': 'parse_l2_cache', + 'MSM_OCMEM': 'parse_ocmem', + 'MSM_TMC0_REG': 'parse_qdss_common', + 'MSM_TMC_ETFETB': 'parse_qdss_common', + 'MSM_TMC1_REG': 'parse_qdss_common', + 'MSM_ETM0_REG': 'parse_qdss_common', + 'MSM_ETM1_REG': 'parse_qdss_common', + 'MSM_ETM2_REG': 'parse_qdss_common', + 'MSM_ETM3_REG': 'parse_qdss_common', +} + +tag_to_field_name = { + 'MSM_TMC0_REG': 'tmc_etr_start', + 'MSM_TMC_ETFETB': 'etf_start', + 'MSM_TMC1_REG': 'tmc_etf_start', + 'MSM_ETM0_REG': 'etm_regs0', + 'MSM_ETM1_REG': 'etm_regs1', + 'MSM_ETM2_REG': 'etm_regs2', + 'MSM_ETM3_REG': 'etm_regs3', +} + + +@register_parser('--parse-debug-image', 'Parse the debug image and associated information') +class DebugImage(RamParser): + + def __init__(self, *args): + super(DebugImage, self).__init__(*args) + self.qdss = QDSSDump() + self.name_lookup_table = [] + + def parse_cpu_ctx(self, start, end, tag): + print_out_str( + 'Parsing CPU context start {0:x} end {1:x}'.format(start, end)) + # For historical reasons, we can't rely on the magic number to indicate if there + # is context dumped. Check the magic number here instead + magic = self.ramdump.read_word(start, False) + if magic is None: + print_out_str( + "!!! Address {0:x} is bogus! Can't parse!".format(start)) + return + + if magic != 0x44434151: + print_out_str( + "!!! Magic {0:x} doesn't match! No context was dumped!".format(magic)) + return + + regs = TZRegDump(self.ramdump) + regs.init_regs(start) + for i in range(regs.ncores): + regs.dump_core_pc(i) + regs.dump_all_regs() + + def parse_l2_cache(self, start, end, tag): + print_out_str( + 'Parsing L2 cache context start {0:x} end {1:x}'.format(start, end)) + magic = self.ramdump.read_word(start, False) + if magic is None: + print_out_str( + "!!! Address {0:x} is bogus! Can't parse!".format(start)) + return + + if magic != 0xcac1ecac: + print_out_str( + "!!! Magic {0:x} doesn't match! No cache was dumped!".format(magic)) + return + + parse_cache_dump(self.ramdump, start) + + def parse_l1_cache(self, start, end, tag): + print_out_str( + 'Parsing L1 cache context start {0:x} end {1:x}'.format(start, end)) + magic = self.ramdump.read_word(start, False) + if magic is None: + print_out_str( + "!!! Address {0:x} is bogus! Can't parse!".format(start)) + return + + if magic != 0x314C4151: + print_out_str( + "!!! Magic {0:X} doesn't match! No cache was dumped!".format(magic)) + return + print_out_str('Saving L1 cache') + save_l1_dump(self.ramdump, start, end - start) + + def parse_ocmem(self, start, end, tag): + print_out_str( + '[!!!] Parsing not implemented yet start {0:x} end {1:x}'.format(start, end)) + + def parse_qdss_common(self, start, end, tag): + print_out_str( + 'Parsing {0} context start {1:x} end {2:x}'.format(tag, start, end)) + magic = self.ramdump.read_word(start, False) + if magic is None: + print_out_str( + "!!! Address {0:x} is bogus! Can't parse!".format(start)) + return + + if magic != QDSS_MAGIC: + print_out_str( + "!!! Magic {0:X} doesn't match! Tracing was not dumped!".format(magic)) + return + + setattr(self.qdss, tag_to_field_name[tag], start + 4096) + + def parse(self): + if not self.ramdump.is_config_defined('CONFIG_MSM_MEMORY_DUMP'): + print_out_str( + '!!! Debug image was not enabled. No debug dump will be provided') + return + + out_dir = self.ramdump.outdir + self.name_lookup_table = self.ramdump.gdbmi.get_enum_lookup_table( + 'dump_client_type', 32) + dump_table_ptr_offset = self.ramdump.field_offset( + 'struct msm_memory_dump', 'dump_table_ptr') + version_offset = self.ramdump.field_offset( + 'struct msm_dump_table', 'version') + num_entries_offset = self.ramdump.field_offset( + 'struct msm_dump_table', 'num_entries') + client_entries_offset = self.ramdump.field_offset( + 'struct msm_dump_table', 'client_entries') + id_offset = self.ramdump.field_offset('struct msm_client_dump', 'id') + start_addr_offset = self.ramdump.field_offset( + 'struct msm_client_dump', 'start_addr') + end_addr_offset = self.ramdump.field_offset( + 'struct msm_client_dump', 'end_addr') + client_dump_entry_size = self.ramdump.sizeof('struct msm_client_dump') + + mem_dump_data = self.ramdump.addr_lookup('mem_dump_data') + + dump_table = self.ramdump.read_word( + mem_dump_data + dump_table_ptr_offset) + + version = self.ramdump.read_word(dump_table + version_offset) + num_entries = self.ramdump.read_word(dump_table + num_entries_offset) + + print_out_str('\nDebug image version: {0}.{1} Number of entries {2}'.format( + version >> 20, version & 0xFFFFF, num_entries)) + print_out_str('--------') + + for i in range(0, num_entries): + this_client = dump_table + client_entries_offset + \ + i * client_dump_entry_size + client_id = self.ramdump.read_word(this_client + id_offset) + client_start = self.ramdump.read_word( + this_client + start_addr_offset) + client_end = self.ramdump.read_word(this_client + end_addr_offset) + + if client_id < 0 or client_id > len(self.name_lookup_table): + print_out_str( + '!!! Invalid client id found {0:x}'.format(client_id)) + continue + + client_name = self.name_lookup_table[client_id] + + if client_name not in print_table: + print_out_str( + '!!! {0} Does not have an associated function. The parser needs to be updated!'.format(client_name)) + else: + print_out_str( + 'Parsing debug information for {0}'.format(client_name)) + func = print_table[client_name] + getattr(DebugImage, func)(self, client_start, + client_end, client_name) + print_out_str('--------') + + self.qdss.dump_all(self.ramdump) diff --git a/linux-ramdump-parser-v2/parsers/dmesg.py b/linux-ramdump-parser-v2/parsers/dmesg.py new file mode 100644 index 0000000000000000000000000000000000000000..44745549405df9911731f1394af22beab064520e --- /dev/null +++ b/linux-ramdump-parser-v2/parsers/dmesg.py @@ -0,0 +1,89 @@ +# Copyright (c) 2013, 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 re +import string + +from print_out import print_out_str +from parser_util import register_parser, RamParser + + +@register_parser('--dmesg', 'Print the dmesg', shortopt='-d') +class Dmesg(RamParser): + + def __init__(self, *args): + super(Dmesg, self).__init__(*args) + self.wrap_cnt = 0 + + def cleanupString(self, unclean_str): + if unclean_str is None: + return str + else: + return ''.join([c for c in unclean_str if c in string.printable]) + + def extract_dmesg_flat(self, ramdump): + addr = ramdump.addr_lookup('__log_buf') + size = ramdump.sizeof('__log_buf') + dmesg = ramdump.read_physical(ramdump.virt_to_phys(addr), size) + print_out_str(self.cleanupString(dmesg.decode('ascii', 'ignore'))) + + def log_from_idx(self, ramdump, idx, logbuf): + len_offset = ramdump.field_offset('struct log', 'len') + + msg = logbuf + idx + msg_len = ramdump.read_word(msg + len_offset) + if (msg_len == 0): + return logbuf + else: + return msg + + def log_next(self, ramdump, idx, logbuf): + len_offset = ramdump.field_offset('struct log', 'len') + msg = idx + + msg_len = ramdump.read_halfword(msg + len_offset) + if (msg_len == 0): + self.wrap_cnt += 1 + return logbuf + else: + return idx + msg_len + + def extract_dmesg_binary(self, ramdump): + first_idx_addr = ramdump.addr_lookup('log_first_idx') + last_idx_addr = ramdump.addr_lookup('log_next_idx') + logbuf_addr = ramdump.addr_lookup('__log_buf') + time_offset = ramdump.field_offset('struct log', 'ts_nsec') + len_offset = ramdump.field_offset('struct log', 'len') + text_len_offset = ramdump.field_offset('struct log', 'text_len') + log_size = ramdump.sizeof('struct log') + + first_idx = ramdump.read_word(first_idx_addr) + last_idx = ramdump.read_word(last_idx_addr) + + curr_idx = logbuf_addr + first_idx + + while curr_idx != logbuf_addr + last_idx and self.wrap_cnt < 2: + timestamp = ramdump.read_dword(curr_idx + time_offset) + text_len = ramdump.read_halfword(curr_idx + text_len_offset) + text_str = ramdump.read_cstring(curr_idx + log_size, text_len) + for partial in text_str.split('\n'): + f = '[{0:>5}.{1:0>6d}] {2}'.format( + timestamp / 1000000000, (timestamp % 1000000000) / 1000, partial) + print_out_str(f) + curr_idx = self.log_next(ramdump, curr_idx, logbuf_addr) + + def parse(self): + if re.search('3.7.\d', self.ramdump.version) is not None: + self.extract_dmesg_binary(self.ramdump) + elif re.search('3\.10\.\d', self.ramdump.version) is not None: + self.extract_dmesg_binary(self.ramdump) + else: + self.extract_dmesg_flat(self.ramdump) diff --git a/linux-ramdump-parser-v2/parsers/gpuinfo.py b/linux-ramdump-parser-v2/parsers/gpuinfo.py new file mode 100644 index 0000000000000000000000000000000000000000..78a159f63731b6c0fb0d3d2ac4e72771ccfca3ef --- /dev/null +++ b/linux-ramdump-parser-v2/parsers/gpuinfo.py @@ -0,0 +1,78 @@ +# Copyright (c) 2013, 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. + +from print_out import print_out_str +from parser_util import register_parser, RamParser + + +@register_parser('--print-gpuinfo', 'print gpu info like ringbuffer,snapshot and pointer addresses', optional=True) +class GPUinfo(RamParser): + + def parse(self): + if not self.ramdump.is_config_defined('CONFIG_MSM_KGSL'): + print_out_str( + 'No GPU support detected... Skipping GPUinfo parser.') + return + adreno_dev_addr = self.ramdump.addr_lookup('device_3d0') + kgsl_dev_addr = adreno_dev_addr + self.ramdump.field_offset( + 'struct adreno_device', 'dev') + snapshot = self.ramdump.read_word(kgsl_dev_addr + self.ramdump.field_offset( + 'struct kgsl_device', 'snapshot')) + snapshot_size = self.ramdump.read_word(kgsl_dev_addr + + self.ramdump.field_offset( + 'struct kgsl_device', 'snapshot_size')) + snapshot_timestamp = self.ramdump.read_word(kgsl_dev_addr + + self.ramdump.field_offset( + 'struct kgsl_device', + 'snapshot_timestamp')) + ringbuffer_offset = self.ramdump.field_offset( + 'struct adreno_device', 'ringbuffer') + ringbuffer_addr = self.ramdump.read_word(adreno_dev_addr + + ringbuffer_offset + + self.ramdump.field_offset( + 'struct adreno_ringbuffer', 'buffer_desc') + + self.ramdump.field_offset( + 'struct kgsl_memdesc', 'physaddr')) + memptrs_addr = self.ramdump.read_word(adreno_dev_addr + ringbuffer_offset + + self.ramdump.field_offset( + 'struct adreno_ringbuffer', 'memptrs_desc') + + self.ramdump.field_offset( + 'struct kgsl_memdesc', 'physaddr')) + memstore_addr = self.ramdump.read_word(kgsl_dev_addr + + self.ramdump.field_offset( + 'struct kgsl_device', 'memstore') + + self.ramdump.field_offset( + 'struct kgsl_memdesc', 'physaddr')) + ringbuffer_size = self.ramdump.read_word(adreno_dev_addr + + ringbuffer_offset + + self.ramdump.field_offset( + 'struct adreno_ringbuffer', 'sizedwords')) + print_out_str('Ringbuffer address: {0:x}, Ringbuffer sizedwords: ' + '{1:x}, Memstore address: {2:x}, Memptrs address: ' + '{3:x}'.format(ringbuffer_addr, ringbuffer_size, + memstore_addr, memptrs_addr)) + print_out_str('Sanpshot addr: {0:x}, Snapshot size: {1:x}, ' + 'Snapshot timestamp:{2:x}'.format(snapshot, + snapshot_size, snapshot_timestamp)) + current_context = self.ramdump.read_word( + int(memstore_addr) + 32, False) + retired_timestamp = self.ramdump.read_word( + int(memstore_addr) + 8, False) + i = 0 + for i in range(0, int(ringbuffer_size)): + data = self.ramdump.read_word( + int(ringbuffer_addr) + (i * 4), False) + if int(data) == int(retired_timestamp): + break + i = i * 4 + print_out_str('Current context: {0:x}, Global eoptimestamp: {1:x} ' + 'found at Ringbuffer[{2:x}]'.format(current_context, + retired_timestamp, i)) diff --git a/linux-ramdump-parser-v2/parsers/iommu.py b/linux-ramdump-parser-v2/parsers/iommu.py new file mode 100644 index 0000000000000000000000000000000000000000..6eb9dacf9f6e2fe84d6c9cae6915def0aeea3f1d --- /dev/null +++ b/linux-ramdump-parser-v2/parsers/iommu.py @@ -0,0 +1,475 @@ +# Copyright (c) 2013, 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 math + +import rb_tree +import linux_list as llist +from print_out import print_out_str +from parser_util import register_parser, RamParser + +IOMMU_DOMAIN_VAR = 'domain_root' + +SZ_4K = 0x1000 +SZ_64K = 0x10000 +SZ_1M = 0x100000 +SZ_16M = 0x1000000 + +MAP_SIZE_STR = ['4K', '8K', '16K', '32K', '64K', + '128K', '256K', '512K', '1M', '2M', + '4M', '8M', '16M'] + + +def get_order(size): + order = math.log(size, 2) + if (order % 1.0) != 0.0: + print 'ERROR: Number is not a power of 2: %x' % (size) + order = 0 + else: + order -= math.log(SZ_4K, 2) + return int(order) + + +@register_parser('--print-iommu-pg-tables', 'Print IOMMU page tables') +class IOMMU(RamParser): + + class Domain(object): + + def __init__(self): + self.domain_num = -1 + self.pg_table = 0 + self.redirect = 0 + self.ctx_name = '' + self.client_name = '' + + class FlatMapping(object): + + def __init__(self, virt, phys=-1, type='[]', size=SZ_4K, mapped=False): + self.virt = virt + self.phys = phys + self.mapping_type = type + self.mapping_size = size + self.mapped = mapped + + class CollapsedMapping(object): + + def __init__(self, virt_start, virt_end, phys_start=-1, phys_end=-1, type='[]', size=SZ_4K, mapped=False): + self.virt_start = virt_start + self.virt_end = virt_end - 1 + self.phys_start = phys_start + self.phys_end = phys_end - 1 + self.mapping_type = type + self.mapping_size = size + self.mapped = mapped + + def phys_size(self): + return (self.phys_end - self.phys_start + 1) + + def virt_size(self): + return (self.virt_end - self.virt_start + 1) + + def __init__(self, *args): + super(IOMMU, self).__init__(*args) + self.out_file = None + self.domain_list = [] + self.NUM_FL_PTE = 4096 + self.NUM_SL_PTE = 256 + + self.FL_BASE_MASK = 0xFFFFFC00 + self.FL_TYPE_TABLE = (1 << 0) + self.FL_TYPE_SECT = (1 << 1) + self.FL_SUPERSECTION = (1 << 18) + self.FL_AP0 = (1 << 10) + self.FL_AP1 = (1 << 11) + self.FL_AP2 = (1 << 15) + self.FL_SHARED = (1 << 16) + self.FL_BUFFERABLE = (1 << 2) + self.FL_CACHEABLE = (1 << 3) + self.FL_TEX0 = (1 << 12) + self.FL_NG = (1 << 17) + + self.SL_BASE_MASK_LARGE = 0xFFFF0000 + self.SL_BASE_MASK_SMALL = 0xFFFFF000 + self.SL_TYPE_LARGE = (1 << 0) + self.SL_TYPE_SMALL = (2 << 0) + self.SL_AP0 = (1 << 4) + self.SL_AP1 = (2 << 4) + self.SL_AP2 = (1 << 9) + self.SL_SHARED = (1 << 10) + self.SL_BUFFERABLE = (1 << 2) + self.SL_CACHEABLE = (1 << 3) + self.SL_TEX0 = (1 << 6) + self.SL_NG = (1 << 11) + self.ctxdrvdata_name_offset = 0 + self.ctxdrvdata_num_offset = 0 + self.ctx_list = [] + + self.node_offset = self.ramdump.field_offset( + 'struct msm_iova_data', 'node') + self.domain_num_offset = self.ramdump.field_offset( + 'struct msm_iova_data', 'domain_num') + self.domain_offset = self.ramdump.field_offset( + 'struct msm_iova_data', 'domain') + self.priv_offset = self.ramdump.field_offset( + 'struct iommu_domain', 'priv') + self.ctxdrvdata_attached_offset = self.ramdump.field_offset( + 'struct msm_iommu_ctx_drvdata', 'attached_elm') + self.ctxdrvdata_name_offset = self.ramdump.field_offset( + 'struct msm_iommu_ctx_drvdata', 'name') + self.ctxdrvdata_num_offset = self.ramdump.field_offset( + 'struct msm_iommu_ctx_drvdata', 'num') + self.priv_pt_offset = self.ramdump.field_offset( + 'struct msm_iommu_priv', 'pt') + self.list_attached_offset = self.ramdump.field_offset( + 'struct msm_iommu_priv', 'list_attached') + self.client_name_offset = self.ramdump.field_offset( + 'struct msm_iommu_priv', 'client_name') + self.pgtable_offset = self.ramdump.field_offset( + 'struct msm_iommu_pt', 'fl_table') + self.redirect_offset = self.ramdump.field_offset( + 'struct msm_iommu_pt', 'redirect') + + self.list_next_offset, self.list_prev_offset = llist.get_list_offsets( + self.ramdump) + + def fl_offset(va): + return (((va) & 0xFFF00000) >> 20) + + def sl_offset(va): + return (((va) & 0xFF000) >> 12) + + def list_func(self, node): + ctx_drvdata_name_ptr = self.ramdump.read_word( + node + self.ctxdrvdata_name_offset) + num = self.ramdump.read_word(node + self.ctxdrvdata_num_offset) + + if ctx_drvdata_name_ptr != 0: + name = self.ramdump.read_cstring(ctx_drvdata_name_ptr, 100) + self.ctx_list.append((num, name)) + + def iommu_domain_func(self, node): + + domain_num_addr = (node - self.node_offset) + self.domain_num_offset + domain_num = self.ramdump.read_word(domain_num_addr) + + domain_addr = (node - self.node_offset) + self.domain_offset + domain = self.ramdump.read_word(domain_addr) + + priv_ptr = self.ramdump.read_word(domain + self.priv_offset) + + if self.client_name_offset is not None: + client_name_ptr = self.ramdump.read_word( + priv_ptr + self.client_name_offset) + if client_name_ptr != 0: + client_name = self.ramdump.read_cstring(client_name_ptr, 100) + else: + client_name = '(null)' + else: + client_name = 'unknown' + + if self.list_attached_offset is not None: + list_attached = self.ramdump.read_word( + priv_ptr + self.list_attached_offset) + else: + list_attached = None + + if self.priv_pt_offset is not None: + pg_table = self.ramdump.read_word( + priv_ptr + self.priv_pt_offset + self.pgtable_offset) + redirect = self.ramdump.read_word( + priv_ptr + self.priv_pt_offset + self.redirect_offset) + else: + # On some builds we are unable to look up the offsets so hardcode + # the offsets. + pg_table = self.ramdump.read_word(priv_ptr + 0) + redirect = self.ramdump.read_word(priv_ptr + 4) + + # Note: On some code bases we don't have this pg_table and redirect in the priv structure (see msm_iommu_sec.c). It only + # contains list_attached. If this is the case we can detect that by checking whether + # pg_table == redirect (prev == next pointers of the attached + # list). + if pg_table == redirect: + # This is a secure domain. We don't have access to the page + # tables. + pg_table = 0 + redirect = None + + if list_attached is not None and list_attached != 0: + list_walker = llist.ListWalker( + self.ramdump, list_attached, self.ctxdrvdata_attached_offset, self.list_next_offset, self.list_prev_offset) + list_walker.walk(list_attached, self.list_func) + + dom = self.Domain() + dom.domain_num = domain_num + dom.pg_table = pg_table + dom.redirect = redirect + dom.ctx_list = self.ctx_list + dom.client_name = client_name + self.ctx_list = [] + self.domain_list.append(dom) + + def print_sl_page_table(self, pg_table): + sl_pte = pg_table + for i in range(0, self.NUM_SL_PTE): + phy_addr = self.ramdump.read_word(sl_pte, False) + if phy_addr is not None: # and phy_addr & self.SL_TYPE_SMALL: + read_write = '[R/W]' + if phy_addr & self.SL_AP2: + read_write = '[R]' + + if phy_addr & self.SL_TYPE_SMALL: + self.out_file.write('SL_PTE[%d] = %x %s\n' % + (i, phy_addr & self.SL_BASE_MASK_SMALL, read_write)) + elif phy_addr & self.SL_TYPE_LARGE: + self.out_file.write('SL_PTE[%d] = %x %s\n' % + (i, phy_addr & self.SL_BASE_MASK_LARGE, read_write)) + elif phy_addr != 0: + self.out_file.write( + 'SL_PTE[%d] = %x NOTE: ERROR [Do not understand page table bits]\n' % (i, phy_addr)) + sl_pte += 4 + + def print_page_table(self, pg_table): + fl_pte = pg_table + for i in range(0, self.NUM_FL_PTE): + # for i in range(0,5): + sl_pg_table_phy_addr = self.ramdump.read_word(fl_pte) + if sl_pg_table_phy_addr is not None: + if sl_pg_table_phy_addr & self.FL_TYPE_TABLE: + self.out_file.write('FL_PTE[%d] = %x [4K/64K]\n' % + (i, sl_pg_table_phy_addr & self.FL_BASE_MASK)) + self.print_sl_page_table( + sl_pg_table_phy_addr & self.FL_BASE_MASK) + elif sl_pg_table_phy_addr & self.FL_SUPERSECTION: + self.out_file.write('FL_PTE[%d] = %x [16M]\n' % + (i, sl_pg_table_phy_addr & 0xFF000000)) + elif sl_pg_table_phy_addr & self.FL_TYPE_SECT: + self.out_file.write('FL_PTE[%d] = %x [1M]\n' % + (i, sl_pg_table_phy_addr & 0xFFF00000)) + elif sl_pg_table_phy_addr != 0: + self.out_file.write( + 'FL_PTE[%d] = %x NOTE: ERROR [Cannot understand first level page table entry]\n' % (i, sl_pg_table_phy_addr)) + else: + self.out_file.write( + 'FL_PTE[%d] NOTE: ERROR [Cannot understand first level page table entry]\n' % (i)) + fl_pte += 4 + + def get_mapping_info(self, pg_table, index): + sl_pte = pg_table + (index * 4) + phy_addr = self.ramdump.read_word(sl_pte, False) + current_phy_addr = -1 + current_page_size = SZ_4K + current_map_type = 0 + status = True + if phy_addr is not None: + if phy_addr & self.SL_AP2: + current_map_type = self.SL_AP2 + if phy_addr & self.SL_TYPE_SMALL: + current_phy_addr = phy_addr & self.SL_BASE_MASK_SMALL + current_page_size = SZ_4K + elif phy_addr & self.SL_TYPE_LARGE: + current_phy_addr = phy_addr & self.SL_BASE_MASK_LARGE + current_page_size = SZ_64K + elif phy_addr != 0: + current_phy_addr = phy_addr + status = False + + return (current_phy_addr, current_page_size, current_map_type, status) + + def get_sect_mapping_info(self, addr): + current_phy_addr = -1 + current_page_size = SZ_4K + current_map_type = 0 + status = True + if addr is not None: + if addr & self.SL_AP2: + current_map_type = self.SL_AP2 + if addr & self.FL_SUPERSECTION: + current_phy_addr = addr & 0xFF000000 + current_page_size = SZ_16M + elif addr & self.FL_TYPE_SECT: + current_phy_addr = addr & 0xFFF00000 + current_page_size = SZ_1M + elif addr != 0: + current_phy_addr = addr + status = False + + return (current_phy_addr, current_page_size, current_map_type, status) + + def add_flat_mapping(self, mappings, fl_idx, sl_idx, phy_adr, map_type, page_size, mapped): + virt = (fl_idx << 20) | (sl_idx << 12) + map_type_str = '[R/W]' + if map_type == self.SL_AP2: + map_type_str = '[R]' + map = self.FlatMapping(virt, phy_adr, map_type_str, page_size, mapped) + if not mappings.has_key(virt): + mappings[virt] = map + else: + self.out_file.write( + '[!] WARNING: FL_PTE[%d] SL_PTE[%d] ERROR [Duplicate mapping?]\n' % (fl_idx, sl_idx)) + return mappings + + def add_collapsed_mapping(self, mappings, virt_start, virt_end, phys_start, phys_end, map_type, page_size, mapped): + map = self.CollapsedMapping( + virt_start, virt_end, phys_start, phys_end, map_type, page_size, mapped) + if not mappings.has_key(virt_start): + mappings[virt_start] = map + else: + self.out_file.write( + '[!] WARNING: ERROR [Duplicate mapping at virtual address 0x%08x?]\n' % (virt_start)) + return mappings + + def create_flat_mapping(self, pg_table): + tmp_mapping = {} + fl_pte = pg_table + for fl_index in range(0, self.NUM_FL_PTE): + fl_pg_table_entry = self.ramdump.read_word(fl_pte) + + if fl_pg_table_entry is not None: + if fl_pg_table_entry & self.FL_TYPE_SECT: + (phy_addr, page_size, map_type, + status) = self.get_sect_mapping_info(fl_pg_table_entry) + if status: + if phy_addr != -1: + tmp_mapping = self.add_flat_mapping( + tmp_mapping, fl_index, 0, phy_addr, map_type, page_size, True) + else: + # no mapping + tmp_mapping = self.add_flat_mapping( + tmp_mapping, fl_index, 0, -1, 0, 0, False) + elif fl_pg_table_entry & self.FL_TYPE_TABLE: + sl_pte = fl_pg_table_entry & self.FL_BASE_MASK + + for sl_index in range(0, self.NUM_SL_PTE): + (phy_addr, page_size, map_type, + status) = self.get_mapping_info(sl_pte, sl_index) + if status: + if phy_addr != -1: + tmp_mapping = self.add_flat_mapping( + tmp_mapping, fl_index, sl_index, phy_addr, map_type, page_size, True) + else: + # no mapping + tmp_mapping = self.add_flat_mapping( + tmp_mapping, fl_index, sl_index, -1, 0, 0, False) + else: + self.out_file.write( + '[!] WARNING: FL_PTE[%d] SL_PTE[%d] ERROR [Unknown error]\n' % (fl_index, sl_index)) + elif fl_pg_table_entry != 0: + self.out_file.write( + '[!] WARNING: FL_PTE[%d] = %x NOTE: ERROR [Cannot understand first level page table entry]\n' % + (fl_index, fl_pg_table_entry)) + else: + tmp_mapping = self.add_flat_mapping( + tmp_mapping, fl_index, 0, -1, 0, 0, False) + else: + self.out_file.write( + '[!] WARNING: FL_PTE[%d] NOTE: ERROR [Cannot understand first level page table entry]\n' % (fl_index)) + fl_pte += 4 + return tmp_mapping + + def create_collapsed_mapping(self, flat_mapping): + collapsed_mapping = {} + if len(flat_mapping.keys()) > 0: + virt_addrs = sorted(flat_mapping.keys()) + start_map = prev_map = flat_mapping[virt_addrs[0]] + last_mapping = False + for virt in virt_addrs[1:]: + map = flat_mapping[virt] + new_mapping = False + if map.mapping_size == prev_map.mapping_size and map.mapping_type == prev_map.mapping_type and map.mapped == prev_map.mapped: + if prev_map.mapping_size == SZ_4K: + if (map.phys - SZ_4K) != prev_map.phys and map.phys != prev_map.phys: + new_mapping = True + elif prev_map.mapping_size == SZ_64K: + if (map.phys - SZ_64K) != prev_map.phys and map.phys != prev_map.phys: + new_mapping = True + elif prev_map.mapping_size == SZ_1M: + if (map.phys - SZ_1M) != prev_map.phys and map.phys != prev_map.phys: + new_mapping = True + elif prev_map.mapping_size == SZ_16M: + if (map.phys - SZ_16M) != prev_map.phys and map.phys != prev_map.phys: + new_mapping = True + elif virt == virt_addrs[-1]: + # Last one + last_mapping = True + else: + new_mapping = True + if new_mapping: + collapsed_mapping = self.add_collapsed_mapping( + collapsed_mapping, start_map.virt, map.virt, + start_map.phys, prev_map.phys + + prev_map.mapping_size, + prev_map.mapping_type, prev_map.mapping_size, prev_map.mapped) + start_map = map + elif last_mapping: + collapsed_mapping = self.add_collapsed_mapping( + collapsed_mapping, start_map.virt, 0xFFFFFFFF + 1, + start_map.phys, prev_map.phys + + prev_map.mapping_size, + prev_map.mapping_type, prev_map.mapping_size, prev_map.mapped) + prev_map = map + return collapsed_mapping + + def print_page_table_pretty(self, pg_table): + flat_mapping = self.create_flat_mapping(pg_table) + collapsed_mapping = self.create_collapsed_mapping(flat_mapping) + + for virt in sorted(collapsed_mapping.keys()): + mapping = collapsed_mapping[virt] + if mapping.mapped: + self.out_file.write( + '0x%08x--0x%08x [0x%08x] A:0x%08x--0x%08x [0x%08x] %s[%s]\n' % (mapping.virt_start, mapping.virt_end, mapping.virt_size(), + mapping.phys_start, mapping.phys_end, + mapping.phys_size(), mapping.mapping_type, MAP_SIZE_STR[get_order(mapping.mapping_size)])) + else: + self.out_file.write('0x%08x--0x%08x [0x%08x] [UNMAPPED]\n' % + (mapping.virt_start, mapping.virt_end, mapping.virt_size())) + + def parse(self): + iommu_domains_rb_root = self.ramdump.addr_lookup(IOMMU_DOMAIN_VAR) + if iommu_domains_rb_root is None: + print_out_str( + '[!] WARNING: IOMMU domains was not found in this build. No IOMMU page tables will be generated') + return + + out_dir = self.ramdump.outdir + + iommu_domains_rb_root_addr = self.ramdump.read_word( + iommu_domains_rb_root) + rb_walker = rb_tree.RbTreeWalker(self.ramdump) + rb_walker.walk(iommu_domains_rb_root_addr, self.iommu_domain_func) + + for d in self.domain_list: + self.out_file = self.ramdump.open_file( + 'msm_iommu_domain_%02d.txt' % (d.domain_num)) + redirect = 'OFF' + if d.redirect is None: + redirect = 'UNKNOWN' + elif d.redirect > 0: + redirect = 'ON' + iommu_context = 'None attached' + if len(d.ctx_list) > 0: + iommu_context = '' + for (num, name) in d.ctx_list: + iommu_context += '%s (%d) ' % (name, num) + iommu_context = iommu_context.strip() + + self.out_file.write('IOMMU Context: %s. Domain: %s (%d) [L2 cache redirect for page tables is %s]\n' % ( + iommu_context, d.client_name, d.domain_num, redirect)) + self.out_file.write( + '[VA Start -- VA End ] [Size ] [PA Start -- PA End ] [Size ] [Read/Write][Page Table Entry Size]\n') + if d.pg_table == 0: + self.out_file.write( + 'No Page Table Found. (Probably a secure domain)\n') + else: + self.print_page_table_pretty(d.pg_table) + self.out_file.write('\n-------------\nRAW Dump\n') + self.print_page_table(d.pg_table) + self.out_file.close() diff --git a/linux-ramdump-parser-v2/parsers/irqstate.py b/linux-ramdump-parser-v2/parsers/irqstate.py new file mode 100644 index 0000000000000000000000000000000000000000..e5a4bc498898e3c27e552056d67007eba2ccb7b2 --- /dev/null +++ b/linux-ramdump-parser-v2/parsers/irqstate.py @@ -0,0 +1,168 @@ +# Copyright (c) 2012-2013, 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. + +from print_out import print_out_str +from parser_util import register_parser, RamParser + + +@register_parser('--print-irqs', 'Print all the irq information', shortopt='-i') +class IrqParse(RamParser): + + def print_irq_state_3_0(self, ram_dump): + print_out_str( + '=========================== IRQ STATE ===============================') + per_cpu_offset_addr = ram_dump.addr_lookup('__per_cpu_offset') + cpu_present_bits_addr = ram_dump.addr_lookup('cpu_present_bits') + cpu_present_bits = ram_dump.read_word(cpu_present_bits_addr) + cpus = bin(cpu_present_bits).count('1') + irq_desc = ram_dump.addr_lookup('irq_desc') + foo, irq_desc_size = ram_dump.unwind_lookup(irq_desc, 1) + h_irq_offset = ram_dump.field_offset('struct irq_desc', 'handle_irq') + irq_num_offset = ram_dump.field_offset('struct irq_data', 'irq') + irq_data_offset = ram_dump.field_offset('struct irq_desc', 'irq_data') + irq_count_offset = ram_dump.field_offset( + 'struct irq_desc', 'irq_count') + irq_chip_offset = ram_dump.field_offset('struct irq_data', 'chip') + irq_action_offset = ram_dump.field_offset('struct irq_desc', 'action') + action_name_offset = ram_dump.field_offset('struct irqaction', 'name') + kstat_irqs_offset = ram_dump.field_offset( + 'struct irq_desc', 'kstat_irqs') + chip_name_offset = ram_dump.field_offset('struct irq_chip', 'name') + irq_desc_entry_size = ram_dump.sizeof('irq_desc[0]') + cpu_str = '' + + for i in range(0, cpus): + cpu_str = cpu_str + '{0:10} '.format('CPU{0}'.format(i)) + + print_out_str( + '{0:4} {1} {2:30} {3:10}'.format('IRQ', cpu_str, 'Name', 'Chip')) + for i in range(0, irq_desc_size, irq_desc_entry_size): + irqnum = ram_dump.read_word(irq_desc + i + irq_num_offset) + irqcount = ram_dump.read_word(irq_desc + i + irq_count_offset) + action = ram_dump.read_word(irq_desc + i + irq_action_offset) + kstat_irqs_addr = ram_dump.read_word( + irq_desc + i + kstat_irqs_offset) + irq_stats_str = '' + + for j in range(0, cpus): + if per_cpu_offset_addr is None: + offset = 0 + else: + offset = ram_dump.read_word(per_cpu_offset_addr + 4 * j) + irq_statsn = ram_dump.read_word(kstat_irqs_addr + offset) + irq_stats_str = irq_stats_str + \ + '{0:10} '.format('{0}'.format(irq_statsn)) + + chip = ram_dump.read_word( + irq_desc + i + irq_data_offset + irq_chip_offset) + chip_name_addr = ram_dump.read_word(chip + chip_name_offset) + chip_name = ram_dump.read_cstring(chip_name_addr, 48) + + if action != 0: + name_addr = ram_dump.read_word(action + action_name_offset) + name = ram_dump.read_cstring(name_addr, 48) + print_out_str( + '{0:4} {1} {2:30} {3:10}'.format(irqnum, irq_stats_str, name, chip_name)) + + def radix_tree_lookup_element(self, ram_dump, root_addr, index): + rnode_offset = ram_dump.field_offset('struct radix_tree_root', 'rnode') + rnode_height_offset = ram_dump.field_offset( + 'struct radix_tree_node', 'height') + slots_offset = ram_dump.field_offset('struct radix_tree_node', 'slots') + + # if CONFIG_BASE_SMALL=0: radix_tree_map_shift = 6 + radix_tree_map_shift = 6 + radix_tree_map_mask = 0x3f + height_to_maxindex = [0x0, 0x3F, 0x0FFF, + 0x0003FFFF, 0x00FFFFFF, 0x3FFFFFFF, 0xFFFFFFFF] + + if ram_dump.read_word(root_addr + rnode_offset) & 1 == 0: + if index > 0: + return None + return (ram_dump.read_word(root_addr + rnode_offset) & 0xfffffffe) + + node_addr = ram_dump.read_word(root_addr + rnode_offset) & 0xfffffffe + height = ram_dump.read_word(node_addr + rnode_height_offset) + + if index > height_to_maxindex[height]: + return None + + shift = (height - 1) * radix_tree_map_shift + for h in range(height, 0, -1): + node_addr = ram_dump.read_word( + node_addr + slots_offset + ((index >> shift) & radix_tree_map_mask) * 4) + if node_addr == 0: + return None + shift -= radix_tree_map_shift + return (node_addr & 0xfffffffe) + + def print_irq_state_sparse_irq(self, ram_dump): + h_irq_offset = ram_dump.field_offset('struct irq_desc', 'handle_irq') + irq_num_offset = ram_dump.field_offset('struct irq_data', 'irq') + irq_data_offset = ram_dump.field_offset('struct irq_desc', 'irq_data') + irq_count_offset = ram_dump.field_offset( + 'struct irq_desc', 'irq_count') + irq_chip_offset = ram_dump.field_offset('struct irq_data', 'chip') + irq_action_offset = ram_dump.field_offset('struct irq_desc', 'action') + action_name_offset = ram_dump.field_offset('struct irqaction', 'name') + kstat_irqs_offset = ram_dump.field_offset( + 'struct irq_desc', 'kstat_irqs') + chip_name_offset = ram_dump.field_offset('struct irq_chip', 'name') + cpu_str = '' + + irq_desc_tree = ram_dump.addr_lookup('irq_desc_tree') + nr_irqs = ram_dump.read_word(ram_dump.addr_lookup('nr_irqs')) + + for i in ram_dump.iter_cpus(): + cpu_str = cpu_str + '{0:10} '.format('CPU{0}'.format(i)) + + print_out_str( + '{0:4} {1} {2:30} {3:10}'.format('IRQ', cpu_str, 'Name', 'Chip')) + for i in range(0, nr_irqs): + irq_desc = self.radix_tree_lookup_element( + ram_dump, irq_desc_tree, i) + if irq_desc is None: + continue + irqnum = ram_dump.read_word(irq_desc + irq_num_offset) + irqcount = ram_dump.read_word(irq_desc + irq_count_offset) + action = ram_dump.read_word(irq_desc + irq_action_offset) + kstat_irqs_addr = ram_dump.read_word(irq_desc + kstat_irqs_offset) + irq_stats_str = '' + + for j in ram_dump.iter_cpus(): + irq_statsn = ram_dump.read_word(kstat_irqs_addr, cpu=j) + irq_stats_str = irq_stats_str + \ + '{0:10} '.format('{0}'.format(irq_statsn)) + + chip = ram_dump.read_word( + irq_desc + irq_data_offset + irq_chip_offset) + chip_name_addr = ram_dump.read_word(chip + chip_name_offset) + chip_name = ram_dump.read_cstring(chip_name_addr, 48) + + if action != 0: + name_addr = ram_dump.read_word(action + action_name_offset) + name = ram_dump.read_cstring(name_addr, 48) + print_out_str( + '{0:4} {1} {2:30} {3:10}'.format(irqnum, irq_stats_str, name, chip_name)) + + def parse(self): + irq_desc = self.ramdump.addr_lookup('irq_desc') + if self.ramdump.is_config_defined('CONFIG_SPARSE_IRQ'): + self.print_irq_state_sparse_irq(self.ramdump) + + if irq_desc is None: + return + + ver = self.ramdump.version + if re.search('3.0.\d', ver) is not None: + self.print_irq_state_3_0(self.ramdump) + if re.search('3.4.\d', ver) is not None: + self.print_irq_state_3_0(self.ramdump) diff --git a/linux-ramdump-parser-v2/parsers/kconfig.py b/linux-ramdump-parser-v2/parsers/kconfig.py new file mode 100644 index 0000000000000000000000000000000000000000..c586f741cf40b1736f10f5e2e5517c2a6f036a3a --- /dev/null +++ b/linux-ramdump-parser-v2/parsers/kconfig.py @@ -0,0 +1,26 @@ +# Copyright (c) 2013, 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. + +from parser_util import register_parser, RamParser +from print_out import print_out_str + + +@register_parser('--print-kconfig', 'Print saved kernel configuration', shortopt='-c') +class Kconfig(RamParser): + + def parse(self): + saved_config = self.ramdump.open_file('kconfig.txt') + + for l in self.ramdump.config: + saved_config.write(l + '\n') + + saved_config.close() + print_out_str('---wrote saved kernel config to kconfig.txt') diff --git a/linux-ramdump-parser-v2/parsers/page_table_dump.py b/linux-ramdump-parser-v2/parsers/page_table_dump.py new file mode 100644 index 0000000000000000000000000000000000000000..c63c8ca00c0b6d138d1263eb70e1e6389450621b --- /dev/null +++ b/linux-ramdump-parser-v2/parsers/page_table_dump.py @@ -0,0 +1,22 @@ +# Copyright (c) 2013, 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. + +from parser_util import register_parser, RamParser +from print_out import print_out_str + + +@register_parser('--dump-page-tables', 'Dumps page tables') +class PageTableDump(RamParser): + + def parse(self): + with self.ramdump.open_file('page_tables.txt') as f: + self.ramdump.mmu.dump_page_tables(f) + print_out_str('Page tables dumped to page_tables.txt') diff --git a/linux-ramdump-parser-v2/parsers/pagetracking.py b/linux-ramdump-parser-v2/parsers/pagetracking.py new file mode 100644 index 0000000000000000000000000000000000000000..f18a27b066e3a09822c63656868ed0ffe7f5823e --- /dev/null +++ b/linux-ramdump-parser-v2/parsers/pagetracking.py @@ -0,0 +1,95 @@ +# Copyright (c) 2012, 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. + +from print_out import print_out_str +from parser_util import register_parser, RamParser + + +@register_parser('--print-pagetracking', 'print page tracking information (if available)') +class PageTracking(RamParser): + + def parse(self): + if not self.ramdump.is_config_defined('CONFIG_PAGE_OWNER'): + return + + min_pfn_addr = self.ramdump.addr_lookup('min_low_pfn') + max_pfn_addr = self.ramdump.addr_lookup('max_pfn') + min_pfn = self.ramdump.read_word( + min_pfn_addr) + (self.ramdump.phys_offset >> 12) + max_pfn = self.ramdump.read_word( + max_pfn_addr) + (self.ramdump.phys_offset >> 12) + + order_offset = self.ramdump.field_offset('struct page', 'order') + flags_offset = self.ramdump.field_offset('struct page', 'flags') + trace_offset = self.ramdump.field_offset('struct page', 'trace') + nr_entries_offset = self.ramdump.field_offset( + 'struct stack_trace', 'nr_entries') + trace_entries_offset = self.ramdump.field_offset( + 'struct page', 'trace_entries') + + out_tracking = self.ramdump.open_file('page_tracking.txt') + out_frequency = self.ramdump.open_file('page_frequency.txt') + sorted_pages = {} + + print 'min {0:x} max {1:x}'.format(min_pfn, max_pfn) + + for pfn in range(min_pfn, max_pfn): + page = pfn_to_page(self.ramdump, pfn) + + # validate this page is free + if page_buddy(self.ramdump, page): + continue + + nr_trace_entries = self.ramdump.read_word( + page + trace_offset + nr_entries_offset) + + if nr_trace_entries <= 0 or nr_trace_entries > 16: + continue + + out_tracking.write('PFN 0x{0:x} page 0x{1:x}\n'.format(pfn, page)) + + alloc_str = '' + for i in range(0, nr_trace_entries): + addr = self.ramdump.read_word( + page + trace_entries_offset + i * 4) + + if addr == 0: + break + look = self.ramdump.unwind_lookup(addr) + if look is None: + break + symname, offset = look + unwind_dat = ' [<{0:x}>] {1}+0x{2:x}\n'.format(addr, + symname, offset) + out_tracking.write(unwind_dat) + alloc_str = alloc_str + unwind_dat + + if alloc_str in sorted_pages: + sorted_pages[alloc_str] = sorted_pages[alloc_str] + 1 + else: + sorted_pages[alloc_str] = 1 + + out_tracking.write('\n') + + sortlist = sorted(sorted_pages.iteritems(), + key=lambda(k, v): (v), reverse=True) + + for k, v in sortlist: + out_frequency.write('Allocated {0} times\n'.format(v)) + out_frequency.write(k) + out_frequency.write('\n') + + out_tracking.close() + out_frequency.close() + print_out_str( + '---wrote page tracking information to page_tracking.txt') + print_out_str( + '---wrote page frequency information to page_frequency.txt') diff --git a/linux-ramdump-parser-v2/parsers/pagetypeinfo.py b/linux-ramdump-parser-v2/parsers/pagetypeinfo.py new file mode 100644 index 0000000000000000000000000000000000000000..2a633f0432a938976e18acbb3fd9004ba7be4de0 --- /dev/null +++ b/linux-ramdump-parser-v2/parsers/pagetypeinfo.py @@ -0,0 +1,88 @@ +# Copyright (c) 2012-2013, 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. + +from print_out import print_out_str +from parser_util import register_parser, RamParser + + +@register_parser('--print-pagetypeinfo', 'Print the pagetypeinfo') +class Pagetypeinfo(RamParser): + + def print_pagetype_info_per_zone(self, ramdump, zone, migrate_types): + + free_area_offset = ramdump.field_offset('struct zone', 'free_area') + free_area_size = ramdump.sizeof('struct free_area') + free_list_offset = ramdump.field_offset( + 'struct free_area', 'free_list') + migratetype_names = ramdump.addr_lookup('migratetype_names') + zone_name_offset = ramdump.field_offset('struct zone', 'name') + zname_addr = ramdump.read_word(zone + zone_name_offset) + zname = ramdump.read_cstring(zname_addr, 12) + is_corrupt = False + total_bytes = 0 + + for mtype in range(0, migrate_types): + mname_addr = ramdump.read_word(migratetype_names + mtype * 4) + mname = ramdump.read_cstring(mname_addr, 12) + pageinfo = ('zone {0:8} type {1:12} '.format(zname, mname)) + nums = '' + total_type_bytes = 0 + for order in range(0, 11): + + area = zone + free_area_offset + order * free_area_size + + orig_free_list = area + free_list_offset + 8 * mtype + curr = orig_free_list + pg_count = -1 + first = True + while True: + pg_count = pg_count + 1 + next_p = ramdump.read_word(curr) + if next_p == curr: + if not first: + is_corrupt = True + break + first = False + curr = next_p + if curr == orig_free_list: + break + nums = nums + ('{0:6}'.format(pg_count)) + total_type_bytes = total_type_bytes + \ + pg_count * 4096 * (2 ** order) + print_out_str(pageinfo + nums + + ' = {0} MB'.format(total_type_bytes / (1024 * 1024))) + total_bytes = total_bytes + total_type_bytes + + print_out_str('Approximate total for zone {0}: {1} MB\n'.format( + zname, total_bytes / (1024 * 1024))) + if is_corrupt: + print_out_str( + '!!! Numbers may not be accurate due to list corruption!') + + def parse(self): + migrate_types = self.ramdump.gdbmi.get_value_of('MIGRATE_TYPES') + max_nr_zones = self.ramdump.gdbmi.get_value_of('__MAX_NR_ZONES') + + contig_page_data = self.ramdump.addr_lookup('contig_page_data') + node_zones_offset = self.ramdump.field_offset( + 'struct pglist_data', 'node_zones') + present_pages_offset = self.ramdump.field_offset( + 'struct zone', 'present_pages') + sizeofzone = self.ramdump.sizeof('struct zone') + zone = contig_page_data + node_zones_offset + + while zone < (contig_page_data + node_zones_offset + max_nr_zones * sizeofzone): + present_pages = self.ramdump.read_word(zone + present_pages_offset) + if not not present_pages: + self.print_pagetype_info_per_zone( + self.ramdump, zone, migrate_types) + + zone = zone + sizeofzone diff --git a/linux-ramdump-parser-v2/parsers/roareadiff.py b/linux-ramdump-parser-v2/parsers/roareadiff.py new file mode 100644 index 0000000000000000000000000000000000000000..ab0e805b848e90f7d68555b366d9356cee189bed --- /dev/null +++ b/linux-ramdump-parser-v2/parsers/roareadiff.py @@ -0,0 +1,98 @@ +# Copyright (c) 2013, 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 struct + +from print_out import print_out_str +from collections import namedtuple +from parser_util import register_parser, RamParser + +ELF32HEADERFORMAT = '<16sHHIIIIIHHHHHH' +ELF32HEADERSIZE = struct.calcsize(ELF32HEADERFORMAT) +PRG32HEADERFORMAT = 'IIIIIIII' +PRG32HEADERSIZE = struct.calcsize(PRG32HEADERFORMAT) +PF_W = 2 + + +@register_parser('--check-rodata', 'check rodata in dump against the static image') +class ROData(RamParser): + + def parse(self): + stext = self.ramdump.addr_lookup('stext') + etext = self.ramdump.addr_lookup('_etext') + + with self.ramdump.open_file('roareadiff.txt') as roarea_out: + + fd = open(self.ramdump.vmlinux, 'rb') + if not fd: + print_out_str('Could not open {0}.'.format(file_path)) + return + + ElfHeader = namedtuple( + 'ElfHeader', 'ident type machine version entry phoff shoff flags ehsize phentsize phnum shentsize shnum shstrndx') + raw_elfheader = fd.read(ELF32HEADERSIZE) + elfheader = ElfHeader._make( + struct.unpack(ELF32HEADERFORMAT, raw_elfheader)) + + PrgHeader = namedtuple( + 'Prgheader', 'type offset vaddr paddr filesz memsz flags align') + for i in range(elfheader.phnum): + fd.seek(elfheader.phoff + i * PRG32HEADERSIZE) + raw_prgheader = fd.read(PRG32HEADERSIZE) + prgheader = PrgHeader._make( + struct.unpack(PRG32HEADERFORMAT, raw_prgheader)) + + if not prgheader.flags & PF_W: + count = prgheader.vaddr + detect = 0 + while count < prgheader.vaddr + prgheader.memsz: + fd.seek(prgheader.offset + (count - prgheader.vaddr)) + ram_value = self.ramdump.read_word(count) + vm_value = struct.unpack('I', fd.read(4))[0] + + if detect == 0 and vm_value != ram_value: + print_out_str( + 'Differences found! Differences written to roareadiff.txt') + ddr_str = 'detect RO area differences between vmlinux and DDR at 0x{0:0>8x}\n'.format( + count) + ddr_str += 'from DDR:\n' + ddr_str += '{0:0>8x} *{1:0>8x}'.format(count, + ram_value) + vmlinux_str = 'from vmlinux:\n' + vmlinux_str += '{0:0>8x} *{1:0>8x}'.format(count, + vm_value) + detect += 1 + elif 0 < detect and detect < 64: + if detect % 8 == 0: + ddr_str += '\n{0:0>8x} '.format(count) + vmlinux_str += '\n{0:0>8x} '.format(count) + ddr_str += ' ' + vmlinux_str += ' ' + if vm_value != ram_value: + ddr_str += '*' + vmlinux_str += '*' + else: + ddr_str += ' ' + vmlinux_str += ' ' + ddr_str += '{0:0>8x}'.format(ram_value) + vmlinux_str += '{0:0>8x}'.format(vm_value) + detect += 1 + elif detect == 64: + ddr_str += '\n\n' + vmlinux_str += '\n\n' + roarea_out.write(ddr_str) + roarea_out.write(vmlinux_str) + detect = 0 + continue + + count += 4 + + fd.close() diff --git a/linux-ramdump-parser-v2/parsers/rtb.py b/linux-ramdump-parser-v2/parsers/rtb.py new file mode 100644 index 0000000000000000000000000000000000000000..f2517f1bbbc0acdddfb6f9226f5ad54028c8446b --- /dev/null +++ b/linux-ramdump-parser-v2/parsers/rtb.py @@ -0,0 +1,180 @@ +# Copyright (c) 2012-2013, 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. + +from tempfile import NamedTemporaryFile + +from print_out import print_out_str +from parser_util import register_parser, RamParser + +# struct msm_rtb_layout { +# unsigned char sentinel[3]; +# unsigned char log_type; +# void *caller; +# unsigned long idx; +# void *data; +#} __attribute__ ((__packed__)); + +print_table = { + 'LOGK_NONE': 'print_none', + 'LOGK_READL': 'print_readlwritel', + 'LOGK_WRITEL': 'print_readlwritel', + 'LOGK_LOGBUF': 'print_logbuf', + 'LOGK_HOTPLUG': 'print_hotplug', + 'LOGK_CTXID': 'print_ctxid', + 'LOGK_TIMESTAMP': 'print_timestamp', +} + + +@register_parser('--print-rtb', 'Print RTB (if enabled)', shortopt='-r') +class RTB(RamParser): + + def __init__(self, *args): + super(RTB, self).__init__(*args) + self.name_lookup_table = [] + + def get_caller(self, caller): + return self.ramdump.gdbmi.get_func_info(caller) + + def get_fun_name(self, addr): + l = self.ramdump.unwind_lookup(addr) + if l is not None: + symname, offset = l + else: + symname = 'Unknown function' + return symname + + def print_none(self, rtbout, rtb_ptr, logtype, data_offset, caller_offset): + rtbout.write('{0} No data\n'.format(logtype).encode('ascii', 'ignore')) + + def print_readlwritel(self, rtbout, rtb_ptr, logtype, data_offset, caller_offset): + data = self.ramdump.read_word(rtb_ptr + data_offset) + caller = self.ramdump.read_word(rtb_ptr + caller_offset) + func = self.get_fun_name(caller) + line = self.get_caller(caller) + rtbout.write('{0} from address {1:x} called from addr {2:x} {3} {4}\n'.format( + logtype, data, caller, func, line).encode('ascii', 'ignore')) + + def print_logbuf(self, rtbout, rtb_ptr, logtype, data_offset, caller_offset): + data = self.ramdump.read_word(rtb_ptr + data_offset) + caller = self.ramdump.read_word(rtb_ptr + caller_offset) + func = self.get_fun_name(caller) + line = self.get_caller(caller) + rtbout.write('{0} log end {1:x} called from addr {2:x} {3} {4}\n'.format( + logtype, data, caller, func, line).encode('ascii', 'ignore')) + + def print_hotplug(self, rtbout, rtb_ptr, logtype, data_offset, caller_offset): + data = self.ramdump.read_word(rtb_ptr + data_offset) + caller = self.ramdump.read_word(rtb_ptr + caller_offset) + func = self.get_fun_name(caller) + line = self.get_caller(caller) + rtbout.write('{0} cpu data {1:x} called from addr {2:x} {3} {4}\n'.format( + logtype, data, caller, func, line).encode('ascii', 'ignore')) + + def print_ctxid(self, rtbout, rtb_ptr, logtype, data_offset, caller_offset): + data = self.ramdump.read_word(rtb_ptr + data_offset) + caller = self.ramdump.read_word(rtb_ptr + caller_offset) + func = self.get_fun_name(caller) + line = self.get_caller(caller) + rtbout.write('{0} context id {1:x} called from addr {2:x} {3} {4}\n'.format( + logtype, data, caller, func, line).encode('ascii', 'ignore')) + + def print_timestamp(self, rtbout, rtb_ptr, logtype, data_offset, caller_offset): + data = self.ramdump.read_word(rtb_ptr + data_offset) + caller = self.ramdump.read_word(rtb_ptr + caller_offset) + rtbout.write('{0} Timestamp: {1:x}{2:x}\n'.format( + logtype, data, caller).encode('ascii', 'ignore')) + + def parse(self): + rtb = self.ramdump.addr_lookup('msm_rtb') + if rtb is None: + print_out_str( + '[!] RTB was not enabled in this build. No RTB files will be generated') + return + self.name_lookup_table = self.ramdump.gdbmi.get_enum_lookup_table( + 'logk_event_type', 32) + step_size_offset = self.ramdump.field_offset( + 'struct msm_rtb_state', 'step_size') + nentries_offset = self.ramdump.field_offset( + 'struct msm_rtb_state', 'nentries') + rtb_entry_offset = self.ramdump.field_offset( + 'struct msm_rtb_state', 'rtb') + idx_offset = self.ramdump.field_offset('struct msm_rtb_layout', 'idx') + caller_offset = self.ramdump.field_offset( + 'struct msm_rtb_layout', 'caller') + log_type_offset = self.ramdump.field_offset( + 'struct msm_rtb_layout', 'log_type') + data_offset = self.ramdump.field_offset( + 'struct msm_rtb_layout', 'data') + rtb_entry_size = self.ramdump.sizeof('struct msm_rtb_layout') + step_size = self.ramdump.read_word(rtb + step_size_offset) + total_entries = self.ramdump.read_word(rtb + nentries_offset) + rtb_read_ptr = self.ramdump.read_word(rtb + rtb_entry_offset) + for i in range(0, step_size): + rtb_out = self.ramdump.open_file('msm_rtb{0}.txt'.format(i)) + gdb_cmd = NamedTemporaryFile(mode='w+t', delete=False) + gdb_out = NamedTemporaryFile(mode='w+t', delete=False) + mask = self.ramdump.read_word(rtb + nentries_offset) - 1 + if step_size == 1: + last = self.ramdump.read_word( + self.ramdump.addr_lookup('msm_rtb_idx')) + else: + last = self.ramdump.read_word(self.ramdump.addr_lookup( + 'msm_rtb_idx_cpu') + self.ramdump.read_word(self.ramdump.addr_lookup('__per_cpu_offset') + 4 * i)) + last = last & mask + last_ptr = 0 + next_ptr = 0 + next_entry = 0 + while True: + next_entry = (last + step_size) & mask + last_ptr = rtb_read_ptr + last * rtb_entry_size + idx_offset + next_ptr = rtb_read_ptr + next_entry * \ + rtb_entry_size + idx_offset + a = self.ramdump.read_word(last_ptr) + b = self.ramdump.read_word(next_ptr) + if a < b: + last = next_entry + if next_entry != last: + break + stop = 0 + rtb_logtype_offset = self.ramdump.field_offset( + 'struct msm_rtb_layout', 'log_type') + rtb_idx_offset = self.ramdump.field_offset( + 'struct msm_rtb_layout', 'idx') + rtb_data_offset = self.ramdump.field_offset( + 'struct msm_rtb_layout', 'data') + rtb_caller_offset = self.ramdump.field_offset( + 'struct msm_rtb_layout', 'caller') + while True: + ptr = rtb_read_ptr + next_entry * rtb_entry_size + stamp = self.ramdump.read_word(ptr + rtb_idx_offset) + rtb_out.write('{0:x} '.format(stamp).encode('ascii', 'ignore')) + item = self.ramdump.read_byte(ptr + rtb_logtype_offset) + item = item & 0x7F + name_str = '(unknown)' + if item >= len(self.name_lookup_table) or item < 0: + self.print_none(rtb_out, ptr, name_str, + rtb_data_offset, rtb_caller_offset) + else: + name_str = self.name_lookup_table[item] + if name_str not in print_table: + self.print_none(rtb_out, ptr, name_str, + rtb_data_offset, rtb_caller_offset) + else: + func = print_table[name_str] + getattr(RTB, func)(self, rtb_out, ptr, name_str, + rtb_data_offset, rtb_caller_offset) + if next_entry == last: + stop = 1 + next_entry = (next_entry + step_size) & mask + if (stop == 1): + break + print_out_str('Wrote RTB to msm_rtb{0}.txt'.format(i)) + rtb_out.close() diff --git a/linux-ramdump-parser-v2/parsers/runqueue.py b/linux-ramdump-parser-v2/parsers/runqueue.py new file mode 100644 index 0000000000000000000000000000000000000000..64cd82d7b589fed9b2ece83fa2e1f46e7ceec2c7 --- /dev/null +++ b/linux-ramdump-parser-v2/parsers/runqueue.py @@ -0,0 +1,205 @@ +# Copyright (c) 2013, 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 rb_tree +from print_out import print_out_str +from parser_util import register_parser, RamParser + + +@register_parser('--print-runqueues', 'Print the runqueue status') +class RunQueues(RamParser): + + def __init__(self, *args): + super(RunQueues, self).__init__(*args) + self.domain_list = [] + self.tab_offset = 0 + + def ffs(self, num): + # Check there is at least one bit set. + if num == 0: + return None + # Right-shift until we have the first set bit in the LSB position. + i = 0 + while (num % 2) == 0: + i += 1 + num = num >> 1 + return i + + def print_out_str_with_tab(self, string): + string = self.tab_offset * ' | ' + ' |--' + string + print_out_str(string) + + def print_task_state(self, status, task_addr): + pid_offset = self.ramdump.field_offset('struct task_struct', 'pid') + comm_offset = self.ramdump.field_offset('struct task_struct', 'comm') + + if 0 < task_addr: + pid = self.ramdump.read_word(task_addr + pid_offset) + taskname = self.ramdump.read_cstring(task_addr + comm_offset, 16) + self.print_out_str_with_tab( + '{0}: {1}({2})'.format(status, taskname, pid)) + else: + self.print_out_str_with_tab('{0}: None(0)'.format(status)) + + def print_cgroup_state(self, status, se_addr): + se_offset = self.ramdump.field_offset('struct task_struct', 'se') + cfs_nr_running_offset = self.ramdump.field_offset( + 'struct cfs_rq', 'nr_running') + my_q_offset = self.ramdump.field_offset('struct sched_entity', 'my_q') + + if se_addr == 0: + self.print_task_state(status, se_addr) + else: + my_q_addr = self.ramdump.read_word(se_addr + my_q_offset) + if my_q_addr == 0: + self.print_task_state(status, se_addr - se_offset) + else: + cfs_nr_running = self.ramdump.read_word( + my_q_addr + cfs_nr_running_offset) + self.print_out_str_with_tab( + '{0}: {1} process is grouping'.format(status, cfs_nr_running)) + self.tab_offset += 1 + self.print_cfs_state(my_q_addr) + self.tab_offset -= 1 + + def cfs_node_func(self, node): + run_node_offset = self.ramdump.field_offset( + 'struct sched_entity', 'run_node') + + task_se = node - run_node_offset + self.print_cgroup_state('pend', task_se) + + def print_cfs_state(self, cfs_rq_addr): + tasks_timeline_offset = self.ramdump.field_offset( + 'struct cfs_rq', 'tasks_timeline') + curr_offset = self.ramdump.field_offset('struct cfs_rq', 'curr') + next_offset = self.ramdump.field_offset('struct cfs_rq', 'next') + last_offset = self.ramdump.field_offset('struct cfs_rq', 'last') + skip_offset = self.ramdump.field_offset('struct cfs_rq', 'skip') + + tasks_timeline_addr = self.ramdump.read_word( + cfs_rq_addr + tasks_timeline_offset) + + curr_se = self.ramdump.read_word(cfs_rq_addr + curr_offset) + self.print_cgroup_state('curr', curr_se) + next_se = self.ramdump.read_word(cfs_rq_addr + next_offset) + self.print_cgroup_state('next', next_se) + last_se = self.ramdump.read_word(cfs_rq_addr + last_offset) + self.print_cgroup_state('last', last_se) + skip_se = self.ramdump.read_word(cfs_rq_addr + skip_offset) + self.print_cgroup_state('skip', skip_se) + + rb_walker = rb_tree.RbTreeWalker(self.ramdump) + rb_walker.walk(tasks_timeline_addr, self.cfs_node_func) + + def print_rt_cgroup_state(self, status, rt_addr): + rt_offset = self.ramdump.field_offset('struct task_struct', 'rt') + rt_nr_running_offset = self.ramdump.field_offset( + 'struct rt_rq', 'nr_running') + my_q_offset = self.ramdump.field_offset( + 'struct sched_rt_entity', 'my_q') + + if rt_addr == 0: + self.print_task_state(status, se_addr) + else: + my_q_addr = self.ramdump.read_word(rt_addr + my_q_offset) + if my_q_addr == 0: + self.print_task_state(status, rt_addr - rt_offset) + else: + rt_nr_running = self.ramdump.read_word( + my_q_addr + rt_nr_running_offset) + self.print_out_str_with_tab( + '{0}: {1} process is grouping'.format(status, rt_nr_running)) + self.tab_offset += 1 + self.print_rt_state(my_q_addr) + self.tab_offset -= 1 + + def print_rt_state(self, rt_rq_addr): + active_offset = self.ramdump.field_offset('struct rt_rq', 'active') + queue_offset = self.ramdump.field_offset( + 'struct rt_prio_array', 'queue') + rt_offset = self.ramdump.field_offset('struct task_struct', 'rt') + + array_addr = rt_rq_addr + active_offset + + for i in range(0, 4): + bitmap = self.ramdump.read_word(array_addr + i * 4) + while True: + idx = self.ffs(bitmap) + if idx is not None and idx + i * 32 < 100: + bitmap &= ~(1 << idx) + idx = (idx + i * 32) * 4 * 2 + queue_addr = self.ramdump.read_word( + array_addr + queue_offset + idx) + while queue_addr != array_addr + queue_offset + idx: + task_addr = queue_addr - rt_offset + self.print_task_state('pend', task_addr) + queue_addr = self.ramdump.read_word(queue_addr) + else: + break + + def print_latest_callstack_maybe(self, task_addr): + text_start_addr = self.ramdump.addr_lookup('_text') + text_end_addr = self.ramdump.addr_lookup('_etext') + stack_offset = self.ramdump.field_offset('struct task_struct', 'stack') + + stack_addr = self.ramdump.read_word(task_addr + stack_offset) + print_out_str('current callstack is maybe:') + + for i in range(stack_addr, stack_addr + 0x2000, 4): + callstack_addr = self.ramdump.read_word(i) + if text_start_addr <= callstack_addr and callstack_addr < text_end_addr: + wname = self.ramdump.unwind_lookup(callstack_addr) + if wname is not None: + print_out_str('0x{0:x}:{1}'.format(i, wname)) + + def parse(self): + print_out_str( + '======================= RUNQUEUE STATE ============================') + runqueues_addr = self.ramdump.addr_lookup('runqueues') + nr_running_offset = self.ramdump.field_offset( + 'struct rq', 'nr_running') + curr_offset = self.ramdump.field_offset('struct rq', 'curr') + idle_offset = self.ramdump.field_offset('struct rq', 'idle') + stop_offset = self.ramdump.field_offset('struct rq', 'stop') + cfs_rq_offset = self.ramdump.field_offset('struct rq', 'cfs') + rt_rq_offset = self.ramdump.field_offset('struct rq', 'rt') + cfs_nr_running_offset = self.ramdump.field_offset( + 'struct cfs_rq', 'nr_running') + rt_nr_running_offset = self.ramdump.field_offset( + 'struct rt_rq', 'rt_nr_running') + + for i in self.ramdump.iter_cpus(): + rq_addr = runqueues_addr + self.ramdump.per_cpu_offset(i) + nr_running = self.ramdump.read_word(rq_addr + nr_running_offset) + print_out_str( + 'CPU{0} {1} process is running'.format(i, nr_running)) + curr_addr = self.ramdump.read_word(rq_addr + curr_offset) + self.print_task_state('curr', curr_addr) + idle_addr = self.ramdump.read_word(rq_addr + idle_offset) + self.print_task_state('idle', idle_addr) + stop_addr = self.ramdump.read_word(rq_addr + stop_offset) + self.print_task_state('stop', stop_addr) + + cfs_rq_addr = rq_addr + cfs_rq_offset + cfs_nr_running = self.ramdump.read_word( + cfs_rq_addr + cfs_nr_running_offset) + print_out_str('CFS {0} process is pending'.format(cfs_nr_running)) + self.print_cfs_state(cfs_rq_addr) + + rt_rq_addr = rq_addr + rt_rq_offset + rt_nr_running = self.ramdump.read_word( + rt_rq_addr + rt_nr_running_offset) + print_out_str('RT {0} process is pending'.format(rt_nr_running)) + self.print_rt_state(rt_rq_addr) + + self.print_latest_callstack_maybe(curr_addr) + print_out_str('') diff --git a/linux-ramdump-parser-v2/parsers/slabinfo.py b/linux-ramdump-parser-v2/parsers/slabinfo.py new file mode 100644 index 0000000000000000000000000000000000000000..40d7f2249fb5cbb61d1d35969e866e26b5d1eff6 --- /dev/null +++ b/linux-ramdump-parser-v2/parsers/slabinfo.py @@ -0,0 +1,213 @@ +# Copyright (c) 2012-2013, 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 re + +from mm import page_address +from print_out import print_out_str +from parser_util import register_parser, RamParser + + +@register_parser('--slabinfo', 'print information about slabs', optional=True) +class Slabinfo(RamParser): + + def get_free_pointer(self, ramdump, s, obj): + # just like validate_slab_slab! + slab_offset_offset = self.ramdump.field_offset( + 'struct kmem_cache', 'offset') + slab_offset = self.ramdump.read_word(s + slab_offset_offset) + return self.ramdump.read_word(obj + slab_offset) + + def slab_index(self, ramdump, p, addr, slab): + slab_size_offset = self.ramdump.field_offset( + 'struct kmem_cache', 'size') + slab_size = self.ramdump.read_word(slab + slab_size_offset) + if slab_size is None: + return -1 + return (p - addr) / slab_size + + def get_map(self, ramdump, slab, page, bitarray): + freelist_offset = self.ramdump.field_offset('struct page', 'freelist') + freelist = self.ramdump.read_word(page + freelist_offset) + p = freelist + addr = page_address(self.ramdump, page) + if addr is None: + return + while p != 0 and p is not None: + idx = self.slab_index(self.ramdump, p, addr, slab) + if idx >= len(bitarray) or idx < 0: + return + bitarray[idx] = 1 + p = self.get_free_pointer(self.ramdump, slab, p) + + def get_track(self, ramdump, slab, obj, track_type): + track_size = self.ramdump.sizeof('struct track') + slab_offset_offset = self.ramdump.field_offset( + 'struct kmem_cache', 'offset') + slab_inuse_offset = self.ramdump.field_offset( + 'struct kmem_cache', 'inuse') + slab_offset = self.ramdump.read_word(slab + slab_offset_offset) + slab_inuse = self.ramdump.read_word(slab + slab_inuse_offset) + if slab_offset != 0: + p = obj + slab_offset + 4 + else: + p = obj + slab_inuse + return p + track_type * track_size + + def print_track(self, ramdump, slab, obj, track_type, out_file): + p = self.get_track(self.ramdump, slab, obj, track_type) + track_addrs_offset = self.ramdump.field_offset('struct track', 'addrs') + start = p + track_addrs_offset + if track_type == 0: + out_file.write(' ALLOC STACK\n') + else: + out_file.write(' FREE STACK\n') + for i in range(0, 16): + a = self.ramdump.read_word(start + 4 * i) + if a == 0: + break + look = self.ramdump.unwind_lookup(a) + if look is None: + return + symname, offset = look + out_file.write( + ' [<{0:x}>] {1}+0x{2:x}\n'.format(a, symname, offset)) + out_file.write('\n') + + def get_nobjects(self, ramdump, page): + if re.search('3\.0\.\d', self.ramdump.version) is not None: + n_objects_offset = self.ramdump.field_offset( + 'struct page', 'objects') + n_objects = self.ramdump.read_halfword(page + n_objects_offset) + return n_objects + else: + # The objects field is now a bit field. This confuses GDB as it thinks the + # offset is always 0. Work around this for now + map_count_offset = self.ramdump.field_offset( + 'struct page', '_mapcount') + count = self.ramdump.read_word(page + map_count_offset) + if count is None: + return None + n_objects = (count >> 16) & 0xFFFF + return n_objects + + def print_slab(self, ramdump, slab_start, slab, page, out_file): + p = slab_start + if page is None: + return + n_objects = self.get_nobjects(self.ramdump, page) + if n_objects is None: + return + slab_size_offset = self.ramdump.field_offset( + 'struct kmem_cache', 'size') + slab_size = self.ramdump.read_word(slab + slab_size_offset) + if slab_size is None: + return + slab_max_offset = self.ramdump.field_offset('struct kmem_cache', 'max') + slab_max = self.ramdump.read_word(slab + slab_max_offset) + if slab_max is None: + return + bitarray = [0] * slab_max + addr = page_address(self.ramdump, page) + self.get_map(self.ramdump, slab, page, bitarray) + while p < slab_start + n_objects * slab_size: + idx = self.slab_index(self.ramdump, p, addr, slab) + bitidx = self.slab_index(self.ramdump, p, addr, slab) + if bitidx >= len(bitarray) or bitidx < 0: + return + if bitarray[bitidx] == 1: + out_file.write( + ' Object {0:x}-{1:x} FREE\n'.format(p, p + slab_size)) + else: + out_file.write( + ' Object {0:x}-{1:x} ALLOCATED\n'.format(p, p + slab_size)) + if self.ramdump.is_config_defined('CONFIG_SLUB_DEBUG_ON'): + self.print_track(self.ramdump, slab, p, 0, out_file) + self.print_track(self.ramdump, slab, p, 1, out_file) + p = p + slab_size + + def print_slab_page_info(self, ramdump, slab, slab_node, start, out_file): + page = self.ramdump.read_word(start) + if page == 0: + return + slab_lru_offset = self.ramdump.field_offset('struct page', 'lru') + page_flags_offset = self.ramdump.field_offset('struct page', 'flags') + slab_node_offset = self.ramdump.field_offset( + 'struct kmem_cache', 'size') + while page != start: + if page is None: + return + page = page - slab_lru_offset + page_flags = self.ramdump.read_word(page + page_flags_offset) + page_addr = page_address(self.ramdump, page) + self.print_slab(self.ramdump, page_addr, slab, page, out_file) + page = self.ramdump.read_word(page + slab_lru_offset) + + def print_per_cpu_slab_info(self, ramdump, slab, slab_node, start, out_file): + page = self.ramdump.read_word(start) + if page == 0: + return + page_flags_offset = self.ramdump.field_offset('struct page', 'flags') + if page is None: + return + page_flags = self.ramdump.read_word(page + page_flags_offset) + page_addr = page_address(self.ramdump, page) + self.print_slab(self.ramdump, page_addr, slab, page, out_file) + + # based on validate_slab_cache. Currently assuming there is only one numa node + # in the system because the code to do that correctly is a big pain. This will + # need to be changed if we ever do NUMA properly. + def parse(self): + slab_out = self.ramdump.open_file('slabs.txt') + original_slab = self.ramdump.addr_lookup('slab_caches') + per_cpu_offset = self.ramdump.addr_lookup('__per_cpu_offset') + cpu_present_bits_addr = self.ramdump.addr_lookup('cpu_present_bits') + cpu_present_bits = self.ramdump.read_word(cpu_present_bits_addr) + cpus = bin(cpu_present_bits).count('1') + slab_list_offset = self.ramdump.field_offset( + 'struct kmem_cache', 'list') + slab_name_offset = self.ramdump.field_offset( + 'struct kmem_cache', 'name') + slab_node_offset = self.ramdump.field_offset( + 'struct kmem_cache', 'node') + cpu_cache_page_offset = self.ramdump.field_offset( + 'struct kmem_cache_cpu', 'page') + cpu_slab_offset = self.ramdump.field_offset( + 'struct kmem_cache', 'cpu_slab') + slab_partial_offset = self.ramdump.field_offset( + 'struct kmem_cache_node', 'partial') + slab_full_offset = self.ramdump.field_offset( + 'struct kmem_cache_node', 'full') + slab = self.ramdump.read_word(original_slab) + while slab != original_slab: + slab = slab - slab_list_offset + slab_name_addr = self.ramdump.read_word(slab + slab_name_offset) + # actually an array but again, no numa + slab_node_addr = self.ramdump.read_word(slab + slab_node_offset) + slab_node = self.ramdump.read_word(slab_node_addr) + slab_name = self.ramdump.read_cstring(slab_name_addr, 48) + cpu_slab_addr = self.ramdump.read_word(slab + cpu_slab_offset) + print_out_str('Parsing slab {0}'.format(slab_name)) + slab_out.write( + '{0:x} slab {1} {2:x}\n'.format(slab, slab_name, slab_node_addr)) + self.print_slab_page_info( + self.ramdump, slab, slab_node, slab_node_addr + slab_partial_offset, slab_out) + self.print_slab_page_info( + self.ramdump, slab, slab_node, slab_node_addr + slab_full_offset, slab_out) + + for i in range(0, cpus): + cpu_slabn_addr = cpu_slab_addr + \ + self.ramdump.read_word(per_cpu_offset + 4 * i) + self.print_per_cpu_slab_info( + self.ramdump, slab, slab_node, cpu_slabn_addr + cpu_cache_page_offset, slab_out) + + slab = self.ramdump.read_word(slab + slab_list_offset) + print_out_str('---wrote slab information to slabs.txt') diff --git a/linux-ramdump-parser-v2/parsers/taskdump.py b/linux-ramdump-parser-v2/parsers/taskdump.py new file mode 100644 index 0000000000000000000000000000000000000000..7de493c346180715f829a1c7576c76a8298066f2 --- /dev/null +++ b/linux-ramdump-parser-v2/parsers/taskdump.py @@ -0,0 +1,217 @@ +# Copyright (c) 2012-2013, 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 string +from print_out import print_out_str +from parser_util import register_parser, RamParser + + +def cleanupString(str): + if str is None: + return str + else: + return ''.join([c for c in str if c in string.printable]) + +cpu_context_save_str = ''.join([ + 'I', # __u32 r4 + 'I', # __u32 r5 + 'I', # __u32 r6 + 'I', # __u32 r7 + 'I', # __u32 r8 + 'I', # __u32 r9 + 'I', # __u32 sl + 'I', # __u32 fp 14 + 'I', # __u32 sp 15 + 'I', # __u32 pc 16 + 'II', # __u32 extra[2] /* Xscale 'acc' register, etc */ +]) + +thread_info_str = ''.join([ # struct thread_info { + 'I', # flags /* low level flags */ + # int preempt_count /* 0 => preemptable, <0 => bug */ + 'I', + 'I', # addr_limit /* address limit */ + 'I', # task + 'I', # exec_domain /* execution domain */ + 'I', # 5 cpu /* cpu */ + 'I', # cpu_domain /* cpu domain */ + # struct cpu_context_save cpu_context /* cpu context */ + cpu_context_save_str, + 'I', # syscall /* syscall number */ + # unsigned char used_cp[16] /* thread used copro */ + 'I', + 'I', # tp_value +]) + +thread_info_cpu_idx = 5 +thread_info_fp_idx = 14 +thread_info_sp_idx = 15 +thread_info_pc_idx = 16 + + +def find_panic(ramdump, addr_stack, thread_task_name): + for i in range(addr_stack, addr_stack + 0x2000, 4): + pc = ramdump.read_word(i) + lr = ramdump.read_word(i + 4) + spx = ramdump.read_word(i + 8) + l = ramdump.unwind_lookup(pc) + if l is not None: + s, offset = l + if s == 'panic': + print_out_str('Faulting process found! Name {0}. Attempting to retrieve stack (sp = {1:x} pc = {2:x})'.format( + thread_task_name, i + 4, pc)) + ramdump.unwind.unwind_backtrace(i + 4, 0, pc, lr, '') + regspanic = ramdump.open_file('regs_panic.cmm') + regspanic.write('r.s pc 0x{0:x}\n'.format(pc)) + regspanic.write('r.s r13 0x{0:x}\n'.format(i + 4)) + regspanic.close() + return True + return False + + +def dump_thread_group(ramdump, thread_group, task_out, check_for_panic=0): + offset_thread_group = ramdump.field_offset( + 'struct task_struct', 'thread_group') + offset_comm = ramdump.field_offset('struct task_struct', 'comm') + offset_pid = ramdump.field_offset('struct task_struct', 'pid') + offset_stack = ramdump.field_offset('struct task_struct', 'stack') + offset_state = ramdump.field_offset('struct task_struct', 'state') + offset_exit_state = ramdump.field_offset( + 'struct task_struct', 'exit_state') + orig_thread_group = thread_group + first = 0 + seen_threads = [] + while True: + next_thread_start = thread_group - offset_thread_group + next_thread_comm = next_thread_start + offset_comm + next_thread_pid = next_thread_start + offset_pid + next_thread_stack = next_thread_start + offset_stack + next_thread_state = next_thread_start + offset_state + next_thread_exit_state = next_thread_start + offset_exit_state + thread_task_name = cleanupString( + ramdump.read_cstring(next_thread_comm, 16)) + if thread_task_name is None: + return + thread_task_pid = ramdump.read_word(next_thread_pid) + if thread_task_pid is None: + return + task_state = ramdump.read_word(next_thread_state) + if task_state is None: + return + task_exit_state = ramdump.read_word(next_thread_exit_state) + if task_exit_state is None: + return + addr_stack = ramdump.read_word(next_thread_stack) + if addr_stack is None: + return + threadinfo = ramdump.read_string(addr_stack, thread_info_str) + if threadinfo is None: + return + if not check_for_panic: + if not first: + task_out.write('Process: {0}, cpu: {1} pid: {2} start: 0x{3:x}\n'.format( + thread_task_name, threadinfo[thread_info_cpu_idx], thread_task_pid, next_thread_start)) + task_out.write( + '=====================================================\n') + first = 1 + task_out.write(' Task name: {0} pid: {1} cpu: {2}\n state: 0x{3:x} exit_state: 0x{4:x} stack base: 0x{5:x}\n'.format( + thread_task_name, thread_task_pid, threadinfo[thread_info_cpu_idx], task_state, task_exit_state, addr_stack)) + task_out.write(' Stack:') + ramdump.unwind.unwind_backtrace(threadinfo[thread_info_sp_idx], threadinfo[ + thread_info_fp_idx], threadinfo[thread_info_pc_idx], 0, ' ', task_out) + task_out.write( + '=======================================================\n') + else: + find_panic(ramdump, addr_stack, thread_task_name) + + next_thr = ramdump.read_word(thread_group) + if (next_thr == thread_group) and (next_thr != orig_thread_group): + if not check_for_panic: + task_out.write( + '!!!! Cycle in thread group! The list is corrupt!\n') + break + if (next_thr in seen_threads): + break + + seen_threads.append(next_thr) + thread_group = next_thr + if thread_group == orig_thread_group: + break + + +def do_dump_stacks(ramdump, check_for_panic=0): + offset_tasks = ramdump.field_offset('struct task_struct', 'tasks') + offset_comm = ramdump.field_offset('struct task_struct', 'comm') + offset_stack = ramdump.field_offset('struct task_struct', 'stack') + offset_thread_group = ramdump.field_offset( + 'struct task_struct', 'thread_group') + offset_pid = ramdump.field_offset('struct task_struct', 'pid') + offset_state = ramdump.field_offset('struct task_struct', 'state') + offset_exit_state = ramdump.field_offset( + 'struct task_struct', 'exit_state') + init_addr = ramdump.addr_lookup('init_task') + init_next_task = init_addr + offset_tasks + orig_init_next_task = init_next_task + init_thread_group = init_addr + offset_thread_group + seen_tasks = [] + if check_for_panic == 0: + task_out = ramdump.open_file('tasks.txt') + else: + task_out = None + while True: + dump_thread_group(ramdump, init_thread_group, + task_out, check_for_panic) + next_task = ramdump.read_word(init_next_task) + if next_task is None: + return + + if (next_task == init_next_task) and (next_task != orig_init_next_task): + if not check_for_panic: + task_out.write( + '!!!! Cycle in task list! The list is corrupt!\n') + break + + if (next_task in seen_tasks): + break + + seen_tasks.append(next_task) + + init_next_task = next_task + init_thread_group = init_next_task - offset_tasks + offset_thread_group + if init_next_task == orig_init_next_task: + break + if check_for_panic == 0: + task_out.close() + print_out_str('---wrote tasks to tasks.txt') + + +@register_parser('--print-tasks', 'Print all the task information', shortopt='-t') +class DumpTasks(RamParser): + + def parse(self): + do_dump_stacks(self.ramdump, 0) + + +@register_parser('--check-for-panic', 'Check if a kernel panic occured', shortopt='-p') +class CheckForPanic(RamParser): + + def parse(self): + addr = self.ramdump.addr_lookup('in_panic') + + result = self.ramdump.read_word(addr) + + if result == 1: + print_out_str('-------------------------------------------------') + print_out_str('[!] KERNEL PANIC detected!') + print_out_str('-------------------------------------------------') + do_dump_stacks(self.ramdump, 1) + else: + print_out_str('No kernel panic detected') diff --git a/linux-ramdump-parser-v2/parsers/vmalloc.py b/linux-ramdump-parser-v2/parsers/vmalloc.py new file mode 100644 index 0000000000000000000000000000000000000000..832c6e6f114bfd9337ea8debb0fee621b6d531f8 --- /dev/null +++ b/linux-ramdump-parser-v2/parsers/vmalloc.py @@ -0,0 +1,91 @@ +# Copyright (c) 2012-2013, 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. + +from print_out import print_out_str +from parser_util import register_parser, RamParser + +VM_IOREMAP = 0x00000001 +VM_ALLOC = 0x00000002 +VM_MAP = 0x00000004 +VM_USERMAP = 0x00000008 +VM_VPAGES = 0x00000010 +VM_UNLIST = 0x00000020 + + +@register_parser('--print-vmalloc', 'print vmalloc information') +class Vmalloc(RamParser): + + def print_vmalloc_info(self, ram_dump, out_path): + vmlist_addr = ram_dump.addr_lookup('vmlist') + vmlist = ram_dump.read_word(vmlist_addr) + + vmalloc_out = ram_dump.open_file('vmalloc.txt') + + next_offset = ram_dump.field_offset('struct vm_struct', 'next') + addr_offset = ram_dump.field_offset('struct vm_struct', 'addr') + size_offset = ram_dump.field_offset('struct vm_struct', 'size') + flags_offset = ram_dump.field_offset('struct vm_struct', 'flags') + pages_offset = ram_dump.field_offset('struct vm_struct', 'pages') + nr_pages_offset = ram_dump.field_offset('struct vm_struct', 'nr_pages') + phys_addr_offset = ram_dump.field_offset( + 'struct vm_struct', 'phys_addr') + caller_offset = ram_dump.field_offset('struct vm_struct', 'caller') + + while (vmlist is not None) and (vmlist != 0): + addr = ram_dump.read_word(vmlist + addr_offset) + caller = ram_dump.read_word(vmlist + caller_offset) + nr_pages = ram_dump.read_word(vmlist + nr_pages_offset) + phys_addr = ram_dump.read_word(vmlist + phys_addr_offset) + flags = ram_dump.read_word(vmlist + flags_offset) + size = ram_dump.read_word(vmlist + size_offset) + + vmalloc_str = '{0:x}-{1:x} {2:x}'.format(addr, addr + size, size) + + if (caller != 0): + a = ram_dump.unwind_lookup(caller) + if a is not None: + symname, offset = a + vmalloc_str = vmalloc_str + \ + ' {0}+0x{1:x}'.format(symname, offset) + + if (nr_pages != 0): + vmalloc_str = vmalloc_str + ' pages={0}'.format(nr_pages) + + if (phys_addr != 0): + vmalloc_str = vmalloc_str + ' phys={0:x}'.format(phys_addr) + + if (flags & VM_IOREMAP) != 0: + vmalloc_str = vmalloc_str + ' ioremap' + + if (flags & VM_ALLOC) != 0: + vmalloc_str = vmalloc_str + ' vmalloc' + + if (flags & VM_MAP) != 0: + vmalloc_str = vmalloc_str + ' vmap' + + if (flags & VM_USERMAP) != 0: + vmalloc_str = vmalloc_str + ' user' + + if (flags & VM_VPAGES) != 0: + vmalloc_str = vmalloc_str + ' vpages' + + vmalloc_str = vmalloc_str + '\n' + vmalloc_out.write(vmalloc_str) + + vmlist = ram_dump.read_word(vmlist + next_offset) + + print_out_str('---wrote vmalloc to vmalloc.txt') + vmalloc_out.close() + + def parse(self): + out_path = self.ramdump.outdir + ver = self.ramdump.version + self.print_vmalloc_info(self.ramdump, out_path) diff --git a/linux-ramdump-parser-v2/parsers/vmstat.py b/linux-ramdump-parser-v2/parsers/vmstat.py new file mode 100644 index 0000000000000000000000000000000000000000..49cf501388b726b3a3be0ba226496d890b5b0177 --- /dev/null +++ b/linux-ramdump-parser-v2/parsers/vmstat.py @@ -0,0 +1,68 @@ +# Copyright (c) 2013, 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. + +from print_out import print_out_str +from parser_util import register_parser, RamParser + + +@register_parser('--print-vmstats', 'Print the information similar to /proc/zoneinfo and /proc/vmstat') +class ZoneInfo(RamParser): + + def print_zone_stats(self, zone, vmstat_names, max_zone_stats): + nr_watermark = self.ramdump.gdbmi.get_value_of('NR_WMARK') + wmark_names = self.ramdump.gdbmi.get_enum_lookup_table( + 'zone_watermarks', nr_watermark) + + zone_name_offset = self.ramdump.field_offset('struct zone', 'name') + zname_addr = self.ramdump.read_word(zone + zone_name_offset) + zname = self.ramdump.read_cstring(zname_addr, 12) + + zstats_addr = zone + \ + self.ramdump.field_offset('struct zone', 'vm_stat') + zwatermark_addr = zone + \ + self.ramdump.field_offset('struct zone', 'watermark') + + print_out_str('\nZone {0:8}'.format(zname)) + for i in xrange(0, max_zone_stats): + print_out_str('{0:30}: {1:8}'.format(vmstat_names[i], self.ramdump.read_word( + self.ramdump.array_index(zstats_addr, 'atomic_long_t', i)))) + + for i in xrange(0, nr_watermark): + print_out_str('{0:30}: {1:8}'.format(wmark_names[i], self.ramdump.read_word( + self.ramdump.array_index(zwatermark_addr, 'unsigned long', i)))) + + def parse(self): + max_zone_stats = self.ramdump.gdbmi.get_value_of( + 'NR_VM_ZONE_STAT_ITEMS') + vmstat_names = self.ramdump.gdbmi.get_enum_lookup_table( + 'zone_stat_item', max_zone_stats) + max_nr_zones = self.ramdump.gdbmi.get_value_of('__MAX_NR_ZONES') + + contig_page_data = self.ramdump.addr_lookup('contig_page_data') + node_zones_offset = self.ramdump.field_offset( + 'struct pglist_data', 'node_zones') + present_pages_offset = self.ramdump.field_offset( + 'struct zone', 'present_pages') + sizeofzone = self.ramdump.sizeof('struct zone') + zone = contig_page_data + node_zones_offset + + while zone < (contig_page_data + node_zones_offset + max_nr_zones * sizeofzone): + present_pages = self.ramdump.read_word(zone + present_pages_offset) + if not not present_pages: + self.print_zone_stats(zone, vmstat_names, max_zone_stats) + + zone = zone + sizeofzone + + print_out_str('\nGlobal Stats') + vmstats_addr = self.ramdump.addr_lookup('vm_stat') + for i in xrange(0, max_zone_stats): + print_out_str('{0:30}: {1:8}'.format(vmstat_names[i], self.ramdump.read_word( + self.ramdump.array_index(vmstats_addr, 'atomic_long_t', i)))) diff --git a/linux-ramdump-parser-v2/parsers/watchdog.py b/linux-ramdump-parser-v2/parsers/watchdog.py new file mode 100644 index 0000000000000000000000000000000000000000..4372170a0588a7a9a0f90034eee566ef76eec6cf --- /dev/null +++ b/linux-ramdump-parser-v2/parsers/watchdog.py @@ -0,0 +1,391 @@ +# Copyright (c) 2012-2013, 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. + +from print_out import print_out_str +from parser_util import register_parser, RamParser + +# (name from tz dump, corresponding T32 register, whether or not to print_out_str (the function name)) +tzbsp_register_names = [ + ('mon_lr', 'pc', True), + ('mon_spsr', None, False), + ('usr_r0', 'r0', False), + ('usr_r1', 'r1', False), + ('usr_r2', 'r2', False), + ('usr_r3', 'r3', False), + ('usr_r4', 'r4', False), + ('usr_r5', 'r5', False), + ('usr_r6', 'r6', False), + ('usr_r7', 'r7', False), + ('usr_r8', 'r8', False), + ('usr_r9', 'r9', False), + ('usr_r10', 'r10', False), + ('usr_r11', 'r11', False), + ('usr_r12', 'r12', False), + ('usr_r13', 'r13_usr', False), + ('usr_r14', 'r14_usr', False), + ('irq_spsr', 'spsr_irq', False), + ('irq_r13', 'r13_irq', False), + ('irq_r14', 'r14_irq', True), + ('svc_spsr', 'spsr', False), + ('svc_r13', 'r13_svc', False), + ('svc_r14', 'r14_svc', True), + ('abt_spsr', 'spsr_abt', False), + ('abt_r13', 'r13_abt', False), + ('abt_r14', 'r14_abt', True), + ('und_spsr', 'spsr_und', False), + ('und_r13', 'r13_und', False), + ('und_r14', 'r14_und', True), + ('fiq_spsr', 'spsr_fiq', False), + ('fiq_r8', 'r8_fiq', False), + ('fiq_r9', 'r9_fiq', False), + ('fiq_r10', 'r10_fiq', False), + ('fiq_r11', 'r11_fiq', False), + ('fiq_r12', 'r12_fiq', False), + ('fiq_r13', 'r13_fiq', False), + ('fiq_r14', 'r14_fiq', False), +] + + +tzbsp_dump_cpu_ctx_t = ''.join([ + 'I', # mon_lr + 'I', # mon_spsr + 'I', # usr_r0 + 'I', # usr_r1 + 'I', # usr_r2 + 'I', # usr_r3 + 'I', # usr_r4 + 'I', # usr_r5 + 'I', # usr_r6 + 'I', # usr_r7 + 'I', # usr_r8 + 'I', # usr_r9 + 'I', # usr_r10 + 'I', # usr_r11 + 'I', # usr_r12 + 'I', # usr_r13 + 'I', # usr_r14 + 'I', # irq_spsr + 'I', # irq_r13 + 'I', # irq_r14 + 'I', # svc_spsr + 'I', # svc_r13 + 'I', # svc_r14 + 'I', # abt_spsr + 'I', # abt_r13 + 'I', # abt_r14 + 'I', # und_spsr + 'I', # und_r13 + 'I', # und_r14 + 'I', # fiq_spsr + 'I', # fiq_r8 + 'I', # fiq_r9 + 'I', # fiq_r10 + 'I', # fiq_r11 + 'I', # fiq_r12 + 'I', # fiq_r13 + 'I', # fiq_r14 +]) + +tzbsp_dump_buf_t_v2 = ''.join([ + 'I', # magic + 'I', # version + 'I', # cpu count +]) + +tz_sc_status_ns = 1 +tz_sc_status_wdt = 2 +tz_sc_status_sgi = 4 + +v2tzbsp_sc_status_ns_bit = 1 +v2tzbsp_sc_status_wdt = 2 +v2tzbsp_sc_status_sgi = 4 +v2tzbsp_sc_status_warm_boot = 8 + +tz_sc_ignore_status = 0x10 + + +v3tzbsp_cpu_dump_status = 0x20 +v3sdi_cpu_dump_status = 0x10 + + +class TZCpuCtx(): + + def __init__(self, regs_t, version, alt_pc): + i = 0 + self.regs = {} + for r in regs_t: + # in version 2, mon_lr is not actually useful for HLOS + # debugging. Swap it with the alternate if necessary + if (version > 1) and i == 0 and alt_pc is not None: + r = alt_pc + self.regs[tzbsp_register_names[i][0]] = r + i += 1 + + def print_regs(self, outfile, ramdump): + for reg_name, t32_name, print_pc in tzbsp_register_names: + if print_pc: + a = ramdump.unwind_lookup(self.regs[reg_name]) + if a is not None: + symname, offset = ramdump.unwind_lookup( + self.regs[reg_name]) + pc_string = '[{0}+0x{1:x}]'.format(symname, offset) + else: + pc_string = '' + else: + pc_string = '' + print_out_str(' {0} = 0x{1:08x} {2}'.format( + reg_name, self.regs[reg_name], pc_string)) + if t32_name is not None: + outfile.write( + 'r.s {0} 0x{1:x}\n'.format(t32_name, self.regs[reg_name])) + + +@register_parser('--check-for-watchdog', 'Check for an FIQ watchdog', shortopt='-w') +class TZRegDump(RamParser): + + def __init__(self, *args): + super(TZRegDump, self).__init__(*args) + self.sc_status = [] + self.core_regs = [] + self.wdt0_status = [] + self.core_regs = [] + self.sec_regs = None + self.ncores = 0 + self.version = 0 + self.mon_sp = [] + self.wdog_pc = [] + self.sc_regs = [] + + def dump_all_regs(self): + for i in range(0, self.ncores): + coren_regs = self.ramdump.open_file('core{0}_regs.cmm'.format(i)) + + if (self.sc_status[i] & tz_sc_ignore_status) == 0: + if self.version == 0: + if (self.sc_status[i] & tz_sc_status_ns): + print_out_str( + 'Core {0} was in the non-secure world'.format(i)) + + if (self.sc_status[i] & tz_sc_status_wdt): + print_out_str( + 'Core {0} experienced a watchdog timeout'.format(i)) + + if (self.sc_status[i] & tz_sc_status_sgi): + print_out_str( + 'Core {0} did not experience a watchdog timeout but some other core did'.format(i)) + else: + if (self.sc_status[i] & v2tzbsp_sc_status_ns_bit): + print_out_str( + 'Core {0} was in the non-secure world'.format(i)) + else: + print_out_str( + 'Core {0} was in the secure world'.format(i)) + + if (self.sc_status[i] & v2tzbsp_sc_status_sgi): + print_out_str( + 'Core {0} received an SGI interrupt'.format(i)) + + if (self.sc_status[i] & v2tzbsp_sc_status_wdt): + print_out_str( + 'Core {0} recieved the watchdog interrupt'.format(i)) + + if (self.sc_status[i] & v2tzbsp_sc_status_warm_boot): + print_out_str( + 'core {0} has the warm boot flag set'.format(i)) + + print_out_str( + 'Core {0} WDT status: {1:x}'.format(i, self.wdt0_status[i])) + + if (self.version >= 3): + print_out_str('status:{0}'.format(hex(self.sc_status[i]))) + if (self.sc_status[i] & v3sdi_cpu_dump_status): + print_out_str( + 'SDI dumped CPU context for core {0}'.format(i)) + elif (self.sc_status[i] & v3tzbsp_cpu_dump_status): + print_out_str( + 'TZ dumped CPU context for core {0}'.format(i)) + + print_out_str('core{0} regs:'.format(i)) + self.core_regs[i].print_regs(coren_regs, self.ramdump) + coren_regs.close() + + secure_regs = self.ramdump.open_file('secure_world_regs.cmm') + print_out_str('\n=============== secure contex ===========') + self.sec_regs.print_regs(secure_regs, self.ramdump) + print_out_str('============ end secure context ===========') + secure_regs.close() + + def dump_core_pc(self, core): + if self.version > 1: + pc = self.wdog_pc[core] + else: + pc = self.core_regs[core].regs['mon_lr'] + lr = self.core_regs[core].regs['svc_r14'] + a = self.ramdump.unwind_lookup(pc) + if a is not None: + symname, offset = a + else: + symname = 'UNKNOWN' + offset = 0 + print_out_str( + 'Core {3} PC: {0}+{1:x} <{2:x}>'.format(symname, offset, pc, core)) + a = self.ramdump.unwind_lookup(lr) + if a is not None: + symname, offset = a + else: + symname = 'UNKNOWN' + offset = 0 + print_out_str( + 'Core {3} LR: {0}+{1:x} <{2:x}>'.format(symname, offset, lr, core)) + print_out_str('') + self.ramdump.unwind.unwind_backtrace( + self.core_regs[core].regs['svc_r13'], 0, pc, lr, '') + print_out_str('') + + def init_regs(self, ebi_addr): + cpu_count = 0 + status = self.ramdump.read_string(ebi_addr, tzbsp_dump_buf_t_v2, False) + + if status is None: + print_out_str("!!! Couldn't read from {0:x}!".format(ebi_addr)) + print_out_str('!!! No FIQ information will be parsed') + print_out_str( + '!!! Do you need to specify a different offset for TZ?') + return False + + if status[0] == 0x44434151: + cpu_count = status[2] + version = status[1] + ebi_addr += struct.calcsize(tzbsp_dump_buf_t_v2) + else: + cpu_count = 2 + version = 0 + + print_out_str('running dump version {0}'.format(version)) + for i in range(0, cpu_count): + self.sc_status.append(self.ramdump.read_word(ebi_addr, False)) + ebi_addr += 4 + + for i in range(0, cpu_count): + self.sc_regs.append( + self.ramdump.read_string(ebi_addr, tzbsp_dump_cpu_ctx_t, False)) + ebi_addr += struct.calcsize(tzbsp_dump_cpu_ctx_t) + # new versions have extra data + if version > 1: + self.mon_sp.append(self.ramdump.read_word(ebi_addr, False)) + self.wdog_pc.append( + self.ramdump.read_word(ebi_addr + 4, False)) + ebi_addr += 8 + + sc_secure = self.ramdump.read_string( + ebi_addr, tzbsp_dump_cpu_ctx_t, False) + ebi_addr += struct.calcsize(tzbsp_dump_cpu_ctx_t) + + for i in range(0, cpu_count): + self.wdt0_status.append(self.ramdump.read_word(ebi_addr, False)) + ebi_addr += 4 + + if version > 1: + for regs, p in zip(self.sc_regs, self.wdog_pc): + self.core_regs.append(TZCpuCtx(regs, version, p)) + else: + for regs in self.sc_regs: + self.core_regs.append(TZCpuCtx(regs, version, None)) + self.sec_regs = TZCpuCtx(sc_secure, version, None) + self.ncores = cpu_count + self.version = version + return True + + def parse(self): + ebi_addr = self.ramdump.read_tz_offset() + + if ebi_addr is None: + print_out_str( + '!!! Could not read from IMEM at address {0:x}'.format(self.ramdump.tz_addr)) + return None + + if (ebi_addr == 0): + print_out_str( + '!!! No EBI address at IMEM location {0:x}.'.format(self.ramdump.tz_addr)) + print_out_str('!!! No FIQ occured on this system') + return None + + print_out_str( + '[!!!!] Read {0:x} from IMEM successfully!'.format(ebi_addr)) + print_out_str('[!!!!] An FIQ occured on the system!') + + # The debug image will be responsible for printing out the register + # information, no need to print it twice + if self.ramdump.is_config_defined('CONFIG_MSM_DEBUG_IMAGE'): + print_out_str( + '[!!!!] Debug image was enabled, the contexts will be printed there') + return + + regs = self.init_regs(ebi_addr) + if regs is False: + print_out_str('!!! Could not get registers from TZ dump') + return + + for i in range(self.ncores): + self.dump_core_pc(i) + self.dump_all_regs() + + +def get_wdog_timing(ramdump): + jiffies_addr = ramdump.addr_lookup('jiffies') + last_ns_addr = ramdump.addr_lookup('last_ns') + last_pet_addr = ramdump.addr_lookup('last_pet') + pet_delay_time_addr = ramdump.addr_lookup('delay_time') + dogstruct_addr = ramdump.addr_lookup('dogwork_struct') + + timer_offset = ramdump.field_offset('struct delayed_work', 'timer') + timer_expires_offset = ramdump.field_offset( + 'struct timer_list', 'expires') + + timer_expires = ramdump.read_word( + dogstruct_addr + timer_offset + timer_expires_offset) + jiffies = ramdump.read_word(jiffies_addr) + if (timer_expires > jiffies): + print_out_str('Pet function to be scheduled in {0} seconds'.format( + (timer_expires - jiffies) * 0.01)) + else: + print_out_str('Pet function may have been blocked.') + print_out_str('Jiffies is {0} seconds after when it was supposed to be scheduled'.format( + (jiffies - timer_expires) * 0.01)) + + if (last_ns_addr != 0) and (last_pet_addr != 0) and (pet_delay_time_addr != 0): + last_ns = ramdump.read_dword(last_ns_addr) + last_pet = ramdump.read_dword(last_pet_addr) + pet_delay_time = ramdump.read_word(pet_delay_time_addr) + + diff = last_ns - last_pet + + print_out_str('Most recent time that the watchdog was pet: {0}.{1:6}'.format( + last_pet / 1000000000, last_pet % 1000000000)) + print_out_str('Most recent timestamp read from the timer hardware: {0}.{1:6}'.format( + last_ns / 1000000000, last_ns % 1000000000)) + if (last_ns > last_pet): + print_out_str( + 'Pet delay time in this build is {0} seconds'.format((pet_delay_time * 10) / 1000)) + print_out_str('The watchdog was not pet for at least {0}.{1:6} seconds'.format( + diff / 1000000000, diff % 1000000000)) + + print_out_str( + '!!! The difference between the two timestamps above is NOT') + print_out_str( + '!!! to be used to determine whether a watchdog bite occured') + + elif (last_pet_addr): + last_pet = ramdump.read_dword(last_pet_addr) + print_out_str('Most recent time that the watchdgo was pet: {0}.{1:6}'.format( + last_pet / 1000000000, last_pet % 1000000000)) + print_out_str('The watchdog was not pet for at least {0}.{1:6} seconds'.format( + pet_delay_diff / 1000000000, pet_delay_diff % 1000000000)) diff --git a/linux-ramdump-parser-v2/parsers/workqueue.py b/linux-ramdump-parser-v2/parsers/workqueue.py new file mode 100644 index 0000000000000000000000000000000000000000..b1170a560c4e6193e4606893af559c71b03adfb1 --- /dev/null +++ b/linux-ramdump-parser-v2/parsers/workqueue.py @@ -0,0 +1,500 @@ +# Copyright (c) 2012-2013, 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 re +from print_out import print_out_str +from parser_util import register_parser, RamParser + + +@register_parser('--print-workqueues', 'Print the state of the workqueues', shortopt='-q') +class Workqueues(RamParser): + + def print_workqueue_state_3_0(self, ram_dump): + per_cpu_offset_addr = ram_dump.addr_lookup('__per_cpu_offset') + global_cwq_sym_addr = ram_dump.addr_lookup('global_cwq') + system_wq_addr = ram_dump.addr_lookup('system_long_wq') + + idle_list_offset = ram_dump.field_offset( + 'struct global_cwq', 'idle_list') + worklist_offset = ram_dump.field_offset( + 'struct global_cwq', 'worklist') + busy_hash_offset = ram_dump.field_offset( + 'struct global_cwq', 'busy_hash') + scheduled_offset = ram_dump.field_offset('struct worker', 'scheduled') + worker_task_offset = ram_dump.field_offset('struct worker', 'task') + worker_entry_offset = ram_dump.field_offset('struct worker', 'entry') + offset_comm = ram_dump.field_offset('struct task_struct', 'comm') + work_entry_offset = ram_dump.field_offset( + 'struct work_struct', 'entry') + work_hentry_offset = ram_dump.field_offset('struct worker', 'hentry') + work_func_offset = ram_dump.field_offset('struct work_struct', 'func') + current_work_offset = ram_dump.field_offset( + 'struct worker', 'current_work') + cpu_wq_offset = ram_dump.field_offset( + 'struct workqueue_struct', 'cpu_wq') + unbound_gcwq_addr = ram_dump.addr_lookup('unbound_global_cwq') + + if per_cpu_offset_addr is None: + per_cpu_offset0 = 0 + per_cpu_offset1 = 0 + else: + per_cpu_offset0 = ram_dump.read_word(per_cpu_offset_addr) + per_cpu_offset1 = ram_dump.read_word(per_cpu_offset_addr + 4) + + global_cwq_cpu0_addr = global_cwq_sym_addr + per_cpu_offset0 + + idle_list_addr0 = ram_dump.read_word( + global_cwq_cpu0_addr + idle_list_offset) + idle_list_addr1 = ram_dump.read_word( + unbound_gcwq_addr + idle_list_offset) + + worklist_addr0 = ram_dump.read_word( + global_cwq_cpu0_addr + worklist_offset) + worklist_addr1 = ram_dump.read_word( + unbound_gcwq_addr + worklist_offset) + + s = '<' + for a in range(0, 64): + s = s + 'I' + + busy_hash0 = ram_dump.read_string( + global_cwq_cpu0_addr + busy_hash_offset, s) + busy_hash1 = ram_dump.read_string( + unbound_gcwq_addr + busy_hash_offset, s) + busy_hash = [] + for a in busy_hash0: + busy_hash.append(a) + + for a in busy_hash1: + busy_hash.append(a) + + for k in range(0, 128): + next_busy_worker = busy_hash[k] + if busy_hash[k] != 0: + cnt = 0 + while True: + worker_addr = next_busy_worker - work_hentry_offset + worker_task_addr = ram_dump.read_word( + worker_addr + worker_task_offset) + if worker_task_addr is None or worker_task_addr == 0: + break + taskname = ram_dump.read_cstring( + worker_task_addr + offset_comm, 16) + scheduled_addr = ram_dump.read_word( + worker_addr + scheduled_offset) + current_work_addr = ram_dump.read_word( + worker_addr + current_work_offset) + current_work_func = ram_dump.read_word( + current_work_addr + work_func_offset) + wname = ram_dump.unwind_lookup(current_work_func) + if wname is not None: + worker_name, a = wname + else: + worker_name = 'Worker at 0x{0:x}'.format( + current_work_func) + print_out_str( + 'BUSY Workqueue worker: {0} current_work: {1}'.format(taskname, worker_name)) + if cnt > 200: + break + cnt += 1 + next_busy_worker = ram_dump.read_word( + worker_addr + work_hentry_offset) + if next_busy_worker == 0: + break + + for i in (0, 1): + if i == 0: + idle_list_addr = idle_list_addr0 + else: + idle_list_addr = idle_list_addr1 + next_entry = idle_list_addr + while True: + worker_addr = next_entry - worker_entry_offset + worker_task_addr = ram_dump.read_word( + next_entry - worker_entry_offset + worker_task_offset) + if worker_task_addr is None or worker_task_addr == 0: + break + + taskname = ram_dump.read_cstring( + (worker_task_addr + offset_comm), 16) + scheduled_addr = ram_dump.read_word( + worker_addr + scheduled_offset) + current_work_addr = ram_dump.read_word( + worker_addr + current_work_offset) + next_entry = ram_dump.read_word(next_entry) + if current_work_addr != 0: + current_work_func = ram_dump.read_word( + current_work_addr + work_func_offset) + wname = ram_dump.unwind_lookup(current_work_func) + if wname is not None: + current_work_name, foo = wname + else: + current_work_name = 'worker at 0x{0:x}'.format( + current_work_func) + else: + current_work_func = 0 + current_work_name = '(null)' + + if next_entry == idle_list_addr: + break + + print_out_str('IDLE Workqueue worker: {0} current_work: {1}'.format( + taskname, current_work_name)) + if scheduled_addr == (worker_addr + scheduled_offset): + continue + + if (next_entry == idle_list_addr): + break + + print_out_str('Pending workqueue info') + for i in (0, 1): + if i == 0: + worklist_addr = worklist_addr0 + else: + worklist_addr = worklist_addr1 + next_work_entry = worklist_addr + while True: + work_func_addr = ram_dump.read_word( + next_work_entry - work_entry_offset + work_func_offset) + next_work_temp = ram_dump.read_word(next_work_entry) + if next_work_temp == next_work_entry: + print_out_str('!!! Cycle in workqueue!') + break + next_work_entry = next_work_temp + + if ram_dump.virt_to_phys(work_func_addr) != 0: + wname = ram_dump.unwind_lookup(work_func_addr) + if wname is not None: + work_func_name, foo = wname + else: + work_func_name = 'worker at 0x{0:x}'.format( + work_func_addr) + if i == 0: + print_out_str( + 'Pending unbound entry: {0}'.format(work_func_name)) + else: + print_out_str( + 'Pending bound entry: {0}'.format(work_func_name)) + if next_work_entry == worklist_addr: + break + + def print_workqueue_state_3_7(self, ram_dump): + per_cpu_offset_addr = ram_dump.addr_lookup('__per_cpu_offset') + global_cwq_sym_addr = ram_dump.addr_lookup('global_cwq') + + pools_offset = ram_dump.field_offset('struct global_cwq', 'pools') + worklist_offset = ram_dump.field_offset( + 'struct global_cwq', 'worklist') + busy_hash_offset = ram_dump.field_offset( + 'struct global_cwq', 'busy_hash') + scheduled_offset = ram_dump.field_offset('struct worker', 'scheduled') + worker_task_offset = ram_dump.field_offset('struct worker', 'task') + worker_entry_offset = ram_dump.field_offset('struct worker', 'entry') + offset_comm = ram_dump.field_offset('struct task_struct', 'comm') + work_entry_offset = ram_dump.field_offset( + 'struct work_struct', 'entry') + work_hentry_offset = ram_dump.field_offset('struct worker', 'hentry') + work_func_offset = ram_dump.field_offset('struct work_struct', 'func') + current_work_offset = ram_dump.field_offset( + 'struct worker', 'current_work') + cpu_wq_offset = ram_dump.field_offset( + 'struct workqueue_struct', 'cpu_wq') + pool_idle_offset = ram_dump.field_offset( + 'struct worker_pool', 'idle_list') + worker_pool_size = ram_dump.sizeof('struct worker_pool') + pending_work_offset = ram_dump.field_offset( + 'struct worker_pool', 'worklist') + unbound_gcwq_addr = ram_dump.addr_lookup('unbound_global_cwq') + cpu_present_bits_addr = ram_dump.addr_lookup('cpu_present_bits') + cpu_present_bits = ram_dump.read_word(cpu_present_bits_addr) + cpus = bin(cpu_present_bits).count('1') + + s = '<' + for a in range(0, 64): + s = s + 'I' + + for i in range(0, cpus): + busy_hash = [] + if per_cpu_offset_addr is None: + offset = 0 + else: + offset = ram_dump.read_word(per_cpu_offset_addr + 4 * i) + workqueue_i = global_cwq_sym_addr + offset + busy_hashi = ram_dump.read_string( + workqueue_i + busy_hash_offset, s) + for a in busy_hashi: + busy_hash.append(a) + + for k in range(0, 64): + next_busy_worker = busy_hash[k] + if busy_hash[k] != 0: + cnt = 0 + + while True: + worker_addr = next_busy_worker - work_hentry_offset + worker_task_addr = ram_dump.read_word( + worker_addr + worker_task_offset) + if worker_task_addr is None or worker_task_addr == 0: + break + taskname = ram_dump.read_cstring( + worker_task_addr + offset_comm, 16) + scheduled_addr = ram_dump.read_word( + worker_addr + scheduled_offset) + current_work_addr = ram_dump.read_word( + worker_addr + current_work_offset) + current_work_func = ram_dump.read_word( + current_work_addr + work_func_offset) + wname = ram_dump.unwind_lookup(current_work_func) + if wname is not None: + worker_name, a = wname + else: + worker_name = 'Worker at 0x{0:x}'.format( + current_work_func) + print_out_str( + 'BUSY Workqueue worker: {0} current_work: {1}'.format(taskname, worker_name)) + if cnt > 200: + break + cnt += 1 + next_busy_worker = ram_dump.read_word( + worker_addr + work_hentry_offset) + if next_busy_worker == 0: + break + + worker_pool = workqueue_i + pools_offset + # Need better way to ge the number of pools... + for k in range(0, 2): + worker_pool_i = worker_pool + k * worker_pool_size + + idle_list_addr = worker_pool_i + pool_idle_offset + next_entry = ram_dump.read_word(idle_list_addr) + while True: + worker_addr = next_entry - worker_entry_offset + worker_task_addr = ram_dump.read_word( + next_entry - worker_entry_offset + worker_task_offset) + if worker_task_addr is None or worker_task_addr == 0: + break + + taskname = ram_dump.read_cstring( + (worker_task_addr + offset_comm), 16) + scheduled_addr = ram_dump.read_word( + worker_addr + scheduled_offset) + current_work_addr = ram_dump.read_word( + worker_addr + current_work_offset) + next_entry = ram_dump.read_word(next_entry) + if current_work_addr != 0: + current_work_func = ram_dump.read_word( + current_work_addr + work_func_offset) + wname = ram_dump.unwind_lookup(current_work_func) + if wname is not None: + current_work_name, foo = wname + else: + current_work_name = 'worker at 0x{0:x}'.format( + current_work_func) + else: + current_work_func = 0 + current_work_name = '(null)' + + if next_entry == idle_list_addr: + break + + print_out_str( + 'IDLE Workqueue worker: {0} current_work: {1}'.format(taskname, current_work_name)) + if scheduled_addr == (worker_addr + scheduled_offset): + continue + + if (next_entry == idle_list_addr): + break + + worklist_addr = worker_pool_i + pending_work_offset + next_work_entry = worklist_addr + while ram_dump.read_word(next_work_entry) != next_work_entry: + work_func_addr = ram_dump.read_word( + next_work_entry - work_entry_offset + work_func_offset) + next_work_temp = ram_dump.read_word(next_work_entry) + if next_work_temp == next_work_entry: + print_out_str('!!! Cycle in workqueue!') + break + next_work_entry = next_work_temp + + if ram_dump.virt_to_phys(work_func_addr) != 0: + work_func_name, foo = ram_dump.unwind_lookup( + work_func_addr) + if i == 0: + print_out_str( + 'Pending unbound entry: {0}'.format(work_func_name)) + else: + print_out_str( + 'Pending bound entry: {0}'.format(work_func_name)) + if next_work_entry == worklist_addr: + break + + def print_workqueue_state_3_10(self, ram_dump): + print_out_str( + '======================= WORKQUEUE STATE ============================') + per_cpu_offset_addr = ram_dump.addr_lookup('__per_cpu_offset') + cpu_worker_pools_addr = ram_dump.addr_lookup('cpu_worker_pools') + + busy_hash_offset = ram_dump.field_offset( + 'struct worker_pool', 'busy_hash') + scheduled_offset = ram_dump.field_offset('struct worker', 'scheduled') + worker_task_offset = ram_dump.field_offset('struct worker', 'task') + worker_entry_offset = ram_dump.field_offset('struct worker', 'entry') + offset_comm = ram_dump.field_offset('struct task_struct', 'comm') + work_entry_offset = ram_dump.field_offset( + 'struct work_struct', 'entry') + work_hentry_offset = ram_dump.field_offset('struct worker', 'hentry') + work_func_offset = ram_dump.field_offset('struct work_struct', 'func') + current_work_offset = ram_dump.field_offset( + 'struct worker', 'current_work') + pool_idle_offset = ram_dump.field_offset( + 'struct worker_pool', 'idle_list') + worker_pool_size = ram_dump.sizeof('struct worker_pool') + pending_work_offset = ram_dump.field_offset( + 'struct worker_pool', 'worklist') + cpu_present_bits_addr = ram_dump.addr_lookup('cpu_present_bits') + cpu_present_bits = ram_dump.read_word(cpu_present_bits_addr) + cpus = bin(cpu_present_bits).count('1') + + s = '<' + for a in range(0, 64): + s = s + 'I' + + for i in range(0, cpus): + busy_hash = [] + if per_cpu_offset_addr is None: + offset = 0 + else: + offset = ram_dump.read_word(per_cpu_offset_addr + 4 * i) + + worker_pool = cpu_worker_pools_addr + offset + # Need better way to ge the number of pools... + for k in range(0, 2): + worker_pool_i = worker_pool + k * worker_pool_size + busy_hashi = ram_dump.read_string( + worker_pool_i + busy_hash_offset, s) + for a in busy_hashi: + busy_hash.append(a) + + for k in range(0, 64): + next_busy_worker = busy_hash[k] + if busy_hash[k] != 0: + cnt = 0 + + while True: + worker_addr = next_busy_worker - work_hentry_offset + worker_task_addr = ram_dump.read_word( + worker_addr + worker_task_offset) + if worker_task_addr is None or worker_task_addr == 0: + break + taskname = ram_dump.read_cstring( + worker_task_addr + offset_comm, 16) + scheduled_addr = ram_dump.read_word( + worker_addr + scheduled_offset) + current_work_addr = ram_dump.read_word( + worker_addr + current_work_offset) + current_work_func = ram_dump.read_word( + current_work_addr + work_func_offset) + wname = ram_dump.unwind_lookup(current_work_func) + if wname is not None: + worker_name, a = wname + else: + worker_name = 'Worker at 0x{0:x}'.format( + current_work_func) + print_out_str( + 'BUSY Workqueue worker: {0} current_work: {1}'.format(taskname, worker_name)) + if cnt > 200: + break + cnt += 1 + next_busy_worker = ram_dump.read_word( + worker_addr + work_hentry_offset) + if next_busy_worker == 0: + break + + idle_list_addr = worker_pool_i + pool_idle_offset + next_entry = ram_dump.read_word(idle_list_addr) + while True: + worker_addr = next_entry - worker_entry_offset + worker_task_addr = ram_dump.read_word( + next_entry - worker_entry_offset + worker_task_offset) + if worker_task_addr is None or worker_task_addr == 0: + break + + taskname = ram_dump.read_cstring( + (worker_task_addr + offset_comm), 16) + scheduled_addr = ram_dump.read_word( + worker_addr + scheduled_offset) + current_work_addr = ram_dump.read_word( + worker_addr + current_work_offset) + next_entry = ram_dump.read_word(next_entry) + if current_work_addr != 0: + current_work_func = ram_dump.read_word( + current_work_addr + work_func_offset) + wname = ram_dump.unwind_lookup(current_work_func) + if wname is not None: + current_work_name, foo = wname + else: + current_work_name = 'worker at 0x{0:x}'.format( + current_work_func) + else: + current_work_func = 0 + current_work_name = '(null)' + + if next_entry == idle_list_addr: + break + + print_out_str( + 'IDLE Workqueue worker: {0} current_work: {1}'.format(taskname, current_work_name)) + if scheduled_addr == (worker_addr + scheduled_offset): + continue + + if (next_entry == idle_list_addr): + break + + worklist_addr = worker_pool_i + pending_work_offset + next_work_entry = worklist_addr + while ram_dump.read_word(next_work_entry) != next_work_entry: + work_func_addr = ram_dump.read_word( + next_work_entry - work_entry_offset + work_func_offset) + next_work_temp = ram_dump.read_word(next_work_entry) + if next_work_temp == next_work_entry: + print_out_str('!!! Cycle in workqueue!') + break + next_work_entry = next_work_temp + + if ram_dump.virt_to_phys(work_func_addr) != 0: + work_func_name, foo = ram_dump.unwind_lookup( + work_func_addr) + if i == 0: + print_out_str( + 'Pending unbound entry: {0}'.format(work_func_name)) + else: + print_out_str( + 'Pending bound entry: {0}'.format(work_func_name)) + if next_work_entry == worklist_addr: + break + + def parse(self): + ver = self.ramdump.version + if re.search('3.0.\d', ver) is not None: + print_workqueue_state_3_0(self.ramdump) + if re.search('3.4.\d', ver) is not None: + # somebody did a backport of 3.7 workqueue patches to msm so + # need to detect new vs. old versions + idle_list_offset = self.ramdump.field_offset( + 'struct global_cwq', 'idle_list') + if idle_list_offset is None: + self.print_workqueue_state_3_7(self.ramdump) + else: + self.print_workqueue_state_3_4(self.ramdump) + if re.search('3.7.\d', ver) is not None: + self.print_workqueue_state_3_7(self.ramdump) + if re.search('3.10.\d', ver) is not None: + self.print_workqueue_state_3_10(self.ramdump) diff --git a/linux-ramdump-parser-v2/print_out.py b/linux-ramdump-parser-v2/print_out.py new file mode 100644 index 0000000000000000000000000000000000000000..51370a7aca61a3c6ba55255323e48f9d15de74e2 --- /dev/null +++ b/linux-ramdump-parser-v2/print_out.py @@ -0,0 +1,46 @@ +# Copyright (c) 2012-2013, 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 +from contextlib import contextmanager + +out_file = None + + +def set_outfile(path): + global out_file + try: + out_file = open(path, 'wb') + except: + print_out_str("could not open path {0}".format(path)) + print_out_str("Do you have write/read permissions on the path?") + sys.exit(1) + + +def print_out_str(string): + if out_file is None: + print (string) + else: + out_file.write((string + '\n').encode('ascii', 'ignore')) + + +@contextmanager +def print_out_section(header): + begin_header_string = '{0}begin {1}{0}'.format( + 10 * '-', header + ) + end_header_string = '{0}end {1}{2}'.format( + 12 * '-', + header, + 10 * '-', + ) + print_out_str('\n' + begin_header_string) + yield + print_out_str(end_header_string + '\n') diff --git a/linux-ramdump-parser-v2/qdss.py b/linux-ramdump-parser-v2/qdss.py new file mode 100644 index 0000000000000000000000000000000000000000..a100d2f140c42b28e239bd34444548b317638aa1 --- /dev/null +++ b/linux-ramdump-parser-v2/qdss.py @@ -0,0 +1,343 @@ +# Copyright (c) 2012, 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. + +from print_out import print_out_str + +tmc_registers = { + 'RSZ': (0x004, 'RAM Size'), + 'STS': (0x00C, 'Status Register'), + 'RRD': (0x010, 'RAM Read Data Register'), + 'RRP': (0x014, 'RAM Read Pointer Register'), + 'RWP': (0x018, 'RAM Write Pointer Register'), + 'TRG': (0x01C, 'Trigger Counter Register'), + 'CTL': (0x020, 'Control Register'), + 'RWD': (0x024, 'RAM Write Data Register'), + 'MODE': (0x028, 'Mode Register'), + 'LBUFLEVEL': (0x02C, 'Latched Buffer Fill Level'), + 'CBUFLEVEL': (0x030, 'Current Buffer Fill Level'), + 'BUFWM': (0x034, 'Buffer Level Water Mark'), + 'RRPHI': (0x038, 'RAM Read Pointer High Register'), + 'RWPHI': (0x03C, 'RAM Write Pointer High Register'), + 'AXICTL': (0x110, 'AXI Control Register'), + 'DBALO': (0x118, 'Data Buffer Address Low Register'), + 'DBAHI': (0x11C, 'Data Buffer Address High Register'), + 'FFSR': (0x300, 'Formatter and Flush Status Register'), + 'FFCR': (0x304, 'Formatter and Flush Control Register'), + 'PSCR': (0x308, 'Periodic Synchronization Counter Register'), + 'ITATBMDATA0': (0xED0, 'Integration Test ATB Master Data Register 0'), + 'ITATBMCTR2': (0xED4, 'Integration Test ATB Master Interface Control 2 Register'), + 'ITATBMCTR1': (0xED8, 'Integration Test ATB Master Control Register 1'), + 'ITATBMCTR0': (0xEDC, 'Integration Test ATB Master Interface Control 0 Register'), + 'ITMISCOP0': (0xEE0, 'Integration Test Miscellaneous Output Register 0'), + 'ITTRFLIN': (0xEE8, 'Integration Test Trigger In and Flush In Register'), + 'ITATBDATA0': (0xEEC, 'Integration Test ATB Data Register 0'), + 'ITATBCTR2': (0xEF0, 'Integration Test ATB Control 2 Register'), + 'ITATBCTR1': (0xEF4, 'Integration Test ATB Control 1 Register'), + 'ITATBCTR0': (0xEF8, 'Integration Test ATB Control 0 Register'), + 'ITCTRL': (0xF00, 'Integration Mode Control Register'), + 'CLAIMSET': (0xFA0, 'Claim Tag Set Register'), + 'CLAIMCLR': (0xFA4, 'Claim Tag Clear Register'), + 'LAR': (0xFB0, 'Lock Access Register'), + 'LSR': (0xFB4, 'Lock Status Register'), + 'AUTHSTATUS': (0xFB8, 'Authentication Status Register'), + 'DEVID': (0xFC8, 'Device Configuration Register'), + 'DEVTYPE': (0xFCC, 'Device Type Identifier Register'), + 'PERIPHID4': (0xFD0, 'Peripheral ID4 Register'), + 'PERIPHID5': (0xFD4, 'Peripheral ID5 Register'), + 'PERIPHID6': (0xFD8, 'Peripheral ID6 Register'), + 'PERIPHID7': (0xFDC, 'Peripheral ID7 Register'), + 'PERIPHID0': (0xFE0, 'Peripheral ID0 Register'), + 'PERIPHID1': (0xFE4, 'Peripheral ID1 Register'), + 'PERIPHID2': (0xFE8, 'Peripheral ID2 Register'), + 'PERIPHID3': (0xFEC, 'Peripheral ID3 Register'), + 'COMPID0': (0xFF0, 'Component ID0 Register'), + 'COMPID1': (0xFF4, 'Component ID1 Register'), + 'COMPID2': (0xFF8, 'Component ID2 Register'), + 'COMPID3': (0xFFC, 'Component ID3 Register'), +} + +etm_registers = { + 'ETMCR': (0x000, 'Main Control Register'), + 'ETMCCR': (0x001, 'Configuration Code Register'), + 'ETMTRIGGER': (0x002, 'Trigger Event Register'), + 'ETMSR': (0x004, 'Status Register'), + 'ETMCSR': (0x005, 'System Configuration Register'), + 'ETMTSSCR': (0x006, 'TraceEnable Start/Stop Control Register'), + 'ETMTEEVR': (0x008, 'TraceEnable Event Register'), + 'ETMTECR1': (0x009, 'TraceEnable Control Register'), + 'ETMFFLR': (0x00B, 'FIFOFULL Level Register'), + 'ETMACVR0': (0x10, 'Address Comparator Value Register 0'), + 'ETMACVR1': (0x11, 'Address Comparator Value Register 1'), + 'ETMACVR2': (0x12, 'Address Comparator Value Register 2'), + 'ETMACVR3': (0x13, 'Address Comparator Value Register 3'), + 'ETMACVR4': (0x14, 'Address Comparator Value Register 4'), + 'ETMACVR5': (0x15, 'Address Comparator Value Register 5'), + 'ETMACVR6': (0x16, 'Address Comparator Value Register 6'), + 'ETMACVR7': (0x17, 'Address Comparator Value Register 7'), + 'ETMACVR8': (0x18, 'Address Comparator Value Register 8'), + 'ETMACVR9': (0x19, 'Address Comparator Value Register 9'), + 'ETMACVRA': (0x1A, 'Address Comparator Value Register A'), + 'ETMACVRB': (0x1B, 'Address Comparator Value Register B'), + 'ETMACVRC': (0x1C, 'Address Comparator Value Register C'), + 'ETMACVRD': (0x1D, 'Address Comparator Value Register D'), + 'ETMACVRE': (0x1E, 'Address Comparator Value Register E'), + 'ETMACVRF': (0x1F, 'Address Comparator Value Register F'), + + 'ETMACVT0': (0x20, 'Address Comparator Type Register 0'), + 'ETMACVT1': (0x21, 'Address Comparator Type Register 1'), + 'ETMACVT2': (0x22, 'Address Comparator Type Register 2'), + 'ETMACVT3': (0x23, 'Address Comparator Type Register 3'), + 'ETMACVT4': (0x24, 'Address Comparator Type Register 4'), + 'ETMACVT5': (0x25, 'Address Comparator Type Register 5'), + 'ETMACVT6': (0x26, 'Address Comparator Type Register 6'), + 'ETMACVT7': (0x27, 'Address Comparator Type Register 7'), + 'ETMACVT8': (0x28, 'Address Comparator Type Register 8'), + 'ETMACVT9': (0x29, 'Address Comparator Type Register 9'), + 'ETMACVTA': (0x2A, 'Address Comparator Type Register A'), + 'ETMACVTB': (0x2B, 'Address Comparator Type Register B'), + 'ETMACVTC': (0x2C, 'Address Comparator Type Register C'), + 'ETMACVTD': (0x2D, 'Address Comparator Type Register D'), + 'ETMACVTE': (0x2E, 'Address Comparator Type Register E'), + 'ETMACVTF': (0x2F, 'Address Comparator Type Register F'), + + 'ETMCNTRLDVR0': (0x050, 'Counter Reload Value Register 0'), + 'ETMCNTRLDVR1': (0x051, 'Counter Reload Value Register 1'), + 'ETMCNTRLDVR2': (0x052, 'Counter Reload Value Register 2'), + 'ETMCNTRLDVR3': (0x053, 'Counter Reload Value Register 3'), + + 'ETMCNTRENR0': (0x054, 'Counter Enable Event Register 0'), + 'ETMCNTRENR1': (0x055, 'Counter Enable Event Register 1'), + 'ETMCNTRENR2': (0x056, 'Counter Enable Event Register 2'), + 'ETMCNTRENR3': (0x057, 'Counter Enable Event Register 3'), + + 'ETMCNTRLDEVR0': (0x058, 'Counter Reload Event Registers 0'), + 'ETMCNTRLDEVR1': (0x059, 'Counter Reload Event Registers 1'), + 'ETMCNTRLDEVR2': (0x05A, 'Counter Reload Event Registers 2'), + 'ETMCNTRLDEVR3': (0x05B, 'Counter Reload Event Registers 3'), + + 'ETMCNTVR0': (0x05C, 'Counter Value Register 0'), + 'ETMCNTVR1': (0x05D, 'Counter Value Register 1'), + 'ETMCNTVR2': (0x05E, 'Counter Value Register 2'), + 'ETMCNTVR3': (0x05F, 'Counter Value Register 3'), + + 'ETMSQabEVR0': (0x060, 'Sequencer State Transition Event Register 0'), + 'ETMSQabEVR1': (0x061, 'Sequencer State Transition Event Register 1'), + 'ETMSQabEVR2': (0x062, 'Sequencer State Transition Event Register 2'), + 'ETMSQabEVR3': (0x063, 'Sequencer State Transition Event Register 3'), + 'ETMSQabEVR4': (0x064, 'Sequencer State Transition Event Register 4'), + 'ETMSQabEVR5': (0x065, 'Sequencer State Transition Event Register 5'), + + 'ETMSQR': (0x067, 'Current Sequencer State Register'), + + 'ETMEXTOUTEVR0': (0x068, 'External Output Event Registers 0'), + 'ETMEXTOUTEVR1': (0x069, 'External Output Event Registers 1'), + 'ETMEXTOUTEVR2': (0x06A, 'External Output Event Registers 2'), + 'ETMEXTOUTEVR3': (0x06B, 'External Output Event Registers 3'), + + 'ETMCIDCVR0': (0x06C, 'Context ID Comparator Value Register 0'), + 'ETMCIDCVR1': (0x06D, 'Context ID Comparator Value Register 1'), + 'ETMCIDCVR2': (0x06E, 'Context ID Comparator Value Register 2'), + + 'ETMCIDCMR0': (0x06F, 'Context ID Mask Register'), + + 'ETMIMPSPEC0': (0x070, 'Implementation Specific Register 0'), + 'ETMIMPSPEC1': (0x071, 'Implementation Specific Register 1'), + 'ETMIMPSPEC2': (0x072, 'Implementation Specific Register 2'), + 'ETMIMPSPEC3': (0x073, 'Implementation Specific Register 3'), + 'ETMIMPSPEC4': (0x074, 'Implementation Specific Register 4'), + 'ETMIMPSPEC5': (0x075, 'Implementation Specific Register 5'), + 'ETMIMPSPEC6': (0x076, 'Implementation Specific Register 6'), + 'ETMIMPSPEC7': (0x077, 'Implementation Specific Register 7'), + + 'ETMSYNCFR': (0x078, 'Synchronization Frequency Register'), + 'ETMIDR': (0x079, 'ID register'), + 'ETMCCER': (0x07A, 'Configuration Code Extension Register'), + 'ETMEXTINSELR': (0x07B, 'Extended External Input Selection Register'), + 'ETMTESSEICR': (0x07C, 'TraceEnable Start/Stop EmbeddedICE Control Register'), + 'ETMEIBCR': (0x07D, 'EmbeddedICE Behavior COntrol Register'), + 'ETMTSEVR': (0x07E, 'Timestamp Event Register'), + 'ETMAUXCR': (0x07F, 'Auxilary Control Register'), + 'ETMTRACEIDR': (0x080, 'CoreSight Trace ID Register'), + 'ETMVMIDCVR': (0x090, 'VMID Comparator Value Register'), + + 'ETMOSLAR': (0x0C0, 'OS Lock Access Register'), + 'ETMOSLSR': (0x0C1, 'OS Lock Status Register'), + 'ETMPDCR': (0x0C4, 'Device Power-DOwn Control Register'), + 'ETMPDSR': (0x0C5, 'Device Power Down Status Register'), + + 'ETMITCTRL': (0x3C0, 'Integration Mode Control Register'), + 'ETMCLAIMSET': (0x3E8, 'Claim Tag Set Register'), + 'ETMCLAIMCLR': (0x3E9, 'Claim Tag Clear Register'), + 'ETMLAR': (0x3Ec, 'Lock Access Register'), + 'ETMLSR': (0x3ED, 'Lock Status Register'), + 'ETMAUTHSTATUS': (0x3EE, 'Authentication Status Register'), + 'ETMDEVID': (0x3F2, 'Device Configuration Register'), + 'ETMDEVTYPE': (0x3F3, 'Device Type Register'), + 'ETMPIDR4': (0x3F4, 'Peripheral ID4 Register'), + 'ETMPIDR0': (0x3F8, 'Peripheral ID0 Register'), + 'ETMPIDR1': (0x3F9, 'Peripheral ID1 Register'), + 'ETMPIDR2': (0x3FA, 'Peripheral ID2 Register'), + 'ETMPIDR3': (0x3FB, 'Peripheral ID3 Register'), + 'ETMCIDR0': (0x3FC, 'Component ID0 Register'), + 'ETMCIDR1': (0x3FD, 'Component ID1 Register'), + 'ETMCIDR2': (0x3FE, 'Component ID2 Register'), +} + + +class QDSSDump(): + + def __init__(self): + self.tmc_etr_start = None + self.etf_start = None + self.tmc_etf_start = None + self.etm_regs0 = None + self.etm_regs1 = None + self.etm_regs2 = None + self.etm_regs3 = None + + # Assumptions: Any address given here has been checked for correct magic + def print_tmc_etf(self, ram_dump): + if self.tmc_etf_start is None: + print_out_str( + "!!! TMC-ETF address has not been set! I can't continue!") + return + + print_out_str('Now printing TMC-ETF registers to file') + tmc_etf_out = ram_dump.open_file('tmc_etf.txt') + for a, b in tmc_registers.iteritems(): + offset, name = b + tmc_etf_out.write('{0} ({1}): {2:x}\n'.format( + a, name, ram_dump.read_word(self.tmc_etf_start + offset, False))) + tmc_etf_out.close() + + def print_tmc_etr(self, ram_dump): + if self.tmc_etr_start is None: + print_out_str( + "!!! TMC-ETR address has not been set! I can't continue!") + return + + print_out_str('Now printing TMC-ETR registers to file') + tmc_etf_out = ram_dump.open_file('tmc_etr.txt') + for a, b in tmc_registers.iteritems(): + offset, name = b + tmc_etf_out.write('{0} ({1}): {2:x}\n'.format( + a, name, ram_dump.read_word(self.tmc_etr_start + offset, False))) + tmc_etf_out.close() + + def print_etm_registers(self, ram_dump, base, fname): + etm_out = ram_dump.open_file(fname) + for a, b in etm_registers.iteritems(): + offset, name = b + etm_out.write('{0} ({1}): {2:x})\n'.format( + a, name, ram_dump.read_word(base + offset * 4, False))) + etm_out.close() + + def print_all_etm_register(self, ram_dump): + if self.etm_regs0 is None: + print_out_str( + '!!! ETM REGS 0 address was not set! Nothing will be parsed') + else: + self.print_etm_registers(ram_dump, self.etm_regs0, 'etm_regs0') + + if self.etm_regs1 is None: + print_out_str( + '!!! ETM REGS 1 address was not set! Nothing will be parsed') + else: + self.print_etm_registers(ram_dump, self.etm_regs1, 'etm_regs1') + + if self.etm_regs2 is None: + print_out_str( + '!!! ETM REGS 2 address was not set! Nothing will be parsed') + else: + self.print_etm_registers(ram_dump, self.etm_regs2, 'etm_regs2') + + if self.etm_regs3 is None: + print_out_str( + '!!! ETM REGS 3 address was not set! Nothing will be parsed') + else: + self.print_etm_registers(ram_dump, self.etm_regs3, 'etm_regs3') + + def save_etf_bin(self, ram_dump): + tmc_etf = ram_dump.open_file('tmc-etf.bin') + if self.tmc_etf_start is None or self.etf_start is None: + print_out_str('!!! ETF was not the current sink!') + tmc_etf.close() + return + + ctl_offset, ctl_desc = tmc_registers['CTL'] + mode_offset, mode_desc = tmc_registers['MODE'] + + ctl = ram_dump.read_word(self.tmc_etf_start + ctl_offset, False) + mode = ram_dump.read_word(self.tmc_etf_start + mode_offset, False) + + if (ctl & 0x1) == 1 and (mode == 0): + # Save the 64kb of data + for i in range(0, 64 * 1024): + val = ram_dump.read_byte(self.etf_start + i, False) + tmc_etf.write(struct.pack('<B', val)) + else: + print_out_str('!!! ETF was not the current sink!') + + tmc_etf.close() + + def save_etr_bin(self, ram_dump): + tmc_etr = ram_dump.open_file('tmc-etr.bin') + if self.tmc_etr_start is None: + print_out_str('!!! ETR was not enabled!') + tmc_etr.close() + return + + ctl_offset, ctl_desc = tmc_registers['CTL'] + mode_offset, mode_desc = tmc_registers['MODE'] + + ctl = ram_dump.read_word(self.tmc_etr_start + ctl_offset, False) + mode = ram_dump.read_word(self.tmc_etr_start + mode_offset, False) + + if (ctl & 0x1) == 1 and (mode == 0): + sts_offset, sts_desc = tmc_registers['STS'] + sts = ram_dump.read_word(self.tmc_etr_start + sts_offset, False) + + dbalo_offset, dbalo_desc = tmc_registers['DBALO'] + dbalo = ram_dump.read_word( + self.tmc_etr_start + dbalo_offset, False) + + rsz_offset, rsz_desc = tmc_registers['RSZ'] + rsz = ram_dump.read_word(self.tmc_etr_start + rsz_offset, False) + # rsz is given in words so convert to bytes + rsz = 4 * rsz + + rwp_offset, rwp_desc = tmc_registers['RWP'] + rwp = ram_dump.read_word(self.tmc_etr_start + rwp_offset, False) + + if (sts & 0x1) == 1: + for i in range(rwp, dbalo + rsz): + val = ram_dump.read_byte(i, False) + tmc_etr.write(struct.pack('<B', val)) + + for i in range(dbalo, rwp): + val = ram_dump.read_byte(i, False) + tmc_etr.write(struct.pack('<B', val)) + + else: + for i in range(dbalo, dbalo + rsz): + val = ram_dump.read_byte(i, False) + tmc_etr.write(struct.pack('<B', val)) + else: + print_out_str('!!! ETR was not the current sink!') + + tmc_etr.close() + + def dump_all(self, ram_dump): + self.print_tmc_etf(ram_dump) + self.print_tmc_etr(ram_dump) + self.print_all_etm_register(ram_dump) + self.save_etf_bin(ram_dump) + self.save_etr_bin(ram_dump) diff --git a/linux-ramdump-parser-v2/ramdump.py b/linux-ramdump-parser-v2/ramdump.py new file mode 100644 index 0000000000000000000000000000000000000000..bedd21f1bd50034b52f9a460b87846786cd4f8b8 --- /dev/null +++ b/linux-ramdump-parser-v2/ramdump.py @@ -0,0 +1,1304 @@ +# Copyright (c) 2012-2013, 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 os +import struct +import gzip +import functools +from tempfile import NamedTemporaryFile + +import gdbmi +from print_out import print_out_str +from mmu import Armv7MMU, Armv7LPAEMMU + +FP = 11 +SP = 13 +LR = 14 +PC = 15 +THREAD_SIZE = 8192 + +HARDWARE_ID_IDX = 0 +MEMORY_START_IDX = 1 +PHYS_OFFSET_IDX = 2 +WATCHDOG_BARK_OFFSET_IDX = 3 +IMEM_START_IDX = 4 +CPU_TYPE = 5 +IMEM_FILENAME = 6 +VERSION_COMPARE = 7 + +smem_offsets = [ + 0, # 8960/9x15 family and earlier + 0x0FA00000, # 8974 + 0x00100000, + 0x0D900000, # 8610 + 0x01100000, # 9635 +] + +hw_ids = [ + (8660, 0x40000000, 0x40200000, 0x2a05f658, + 0x2a05f000, 'SCORPION', 'IMEM_C.BIN', None), + (8960, 0x80000000, 0x80200000, 0x2a03f658, + 0x2a03f000, 'KRAIT', 'IMEM_C.BIN', None), + (8064, 0x80000000, 0x80200000, 0x2a03f658, + 0x2a03f000, 'KRAIT', 'IMEM_C.BIN', None), + (9615, 0x40000000, 0x40800000, 0x0, + 0x0, 'CORTEXA5', None, None), + (8974, 0x0, 0x0, 0xfe805658, + 0xfe800000, 'KRAIT', 'OCIMEM.BIN', None), + (9625, 0x0, 0x00200000, 0xfc42b658, + 0xfc428000, 'CORTEXA5', 'MSGRAM.BIN', 1), + (9625, 0x0, 0x00200000, 0xfe805658, + 0xfe800000, 'CORTEXA5', 'OCIMEM.BIN', 2), + (8625, 0x0, 0x00200000, 0x0, + 0x0, 'SCORPION', None, None), + (8226, 0x0, 0x00000000, 0xfe805658, + 0xfe800000, 'CORTEXA7', 'OCIMEM.BIN', None), + (8610, 0x0, 0x00000000, 0xfe805658, + 0xfe800000, 'CORTEXA7', 'OCIMEM.BIN', None), + (8084, 0x0, 0x0, 0xfe805658, + 0xfe800000, 'KRAIT', 'OCIMEM.BIN', None), + (9635, 0x0, 0x00000000, 0xfe805658, + 0xfe800000, 'CORTEXA7', 'OCIMEM.BIN', None), + (8092, 0x0, 0x0, 0xfe805658, + 0xfe800000, 'KRAIT', 'OCIMEM.BIN', None), +] + +MSM_CPU_UNKNOWN = 0 +MSM_CPU_7X01 = -1 +MSM_CPU_7X25 = -1 +MSM_CPU_7X27 = -1 +MSM_CPU_8X50 = -1 +MSM_CPU_8X50A = -1 +MSM_CPU_7X30 = -1 +MSM_CPU_8X55 = -1 +MSM_CPU_8X60 = 8660 +MSM_CPU_8960 = 8960 +MSM_CPU_8960AB = 8960 +MSM_CPU_7X27A = 8625 +FSM_CPU_9XXX = -1 +MSM_CPU_7X25A = 8625 +MSM_CPU_7X25AA = 8625 +MSM_CPU_7X25AB = 8625 +MSM_CPU_8064 = 8064 +MSM_CPU_8064AB = 8064 +MSM_CPU_8930 = 8960 +MSM_CPU_8930AA = 8960 +MSM_CPU_8930AB = 8960 +MSM_CPU_7X27AA = -1 +MSM_CPU_9615 = 9615 +MSM_CPU_8974 = 8974 +MSM_CPU_8974PRO_AA = 8974 +MSM_CPU_8974PRO_AB = 8974 +MSM_CPU_8974PRO_AC = 8974 +MSM_CPU_8627 = 8960 +MSM_CPU_8625 = 9615 +MSM_CPU_9625 = 9625 +MSM_CPU_8226 = 8226 +MSM_CPU_8610 = 8610 +MSM_CPU_8084 = 8084 +MSM_CPU_KRYPTON = 9635 +MSM_CPU_8092 = 8092 + + # id, cpu, cpuname +cpu_of_id = [ + # 7x01 IDs + (1, MSM_CPU_7X01, 'MSM_CPU_7X01'), + (16, MSM_CPU_7X01, 'MSM_CPU_7X01'), + (17, MSM_CPU_7X01, 'MSM_CPU_7X01'), + (18, MSM_CPU_7X01, 'MSM_CPU_7X01'), + (19, MSM_CPU_7X01, 'MSM_CPU_7X01'), + (23, MSM_CPU_7X01, 'MSM_CPU_7X01'), + (25, MSM_CPU_7X01, 'MSM_CPU_7X01'), + (26, MSM_CPU_7X01, 'MSM_CPU_7X01'), + (32, MSM_CPU_7X01, 'MSM_CPU_7X01'), + (33, MSM_CPU_7X01, 'MSM_CPU_7X01'), + (34, MSM_CPU_7X01, 'MSM_CPU_7X01'), + (35, MSM_CPU_7X01, 'MSM_CPU_7X01'), + + # 7x25 IDs + (20, MSM_CPU_7X25, 'MSM_CPU_7X25'), + (21, MSM_CPU_7X25, 'MSM_CPU_7X25'), # 7225 + (24, MSM_CPU_7X25, 'MSM_CPU_7X25'), # 7525 + (27, MSM_CPU_7X25, 'MSM_CPU_7X25'), # 7625 + (39, MSM_CPU_7X25, 'MSM_CPU_7X25'), + (40, MSM_CPU_7X25, 'MSM_CPU_7X25'), + (41, MSM_CPU_7X25, 'MSM_CPU_7X25'), + (42, MSM_CPU_7X25, 'MSM_CPU_7X25'), + (62, MSM_CPU_7X25, 'MSM_CPU_7X25'), # 7625-1 + (63, MSM_CPU_7X25, 'MSM_CPU_7X25'), # 7225-1 + (66, MSM_CPU_7X25, 'MSM_CPU_7X25'), # 7225-2 + + + # 7x27 IDs + (43, MSM_CPU_7X27, 'MSM_CPU_7X27'), + (44, MSM_CPU_7X27, 'MSM_CPU_7X27'), + (61, MSM_CPU_7X27, 'MSM_CPU_7X27'), + (67, MSM_CPU_7X27, 'MSM_CPU_7X27'), # 7227-1 + (68, MSM_CPU_7X27, 'MSM_CPU_7X27'), # 7627-1 + (69, MSM_CPU_7X27, 'MSM_CPU_7X27'), # 7627-2 + + + # 8x50 IDs + (30, MSM_CPU_8X50, 'MSM_CPU_8X50'), + (36, MSM_CPU_8X50, 'MSM_CPU_8X50'), + (37, MSM_CPU_8X50, 'MSM_CPU_8X50'), + (38, MSM_CPU_8X50, 'MSM_CPU_8X50'), + + # 7x30 IDs + (59, MSM_CPU_7X30, 'MSM_CPU_7X30'), + (60, MSM_CPU_7X30, 'MSM_CPU_7X30'), + + # 8x55 IDs + (74, MSM_CPU_8X55, 'MSM_CPU_8X55'), + (75, MSM_CPU_8X55, 'MSM_CPU_8X55'), + (85, MSM_CPU_8X55, 'MSM_CPU_8X55'), + + # 8x60 IDs + (70, MSM_CPU_8X60, 'MSM_CPU_8X60'), + (71, MSM_CPU_8X60, 'MSM_CPU_8X60'), + (86, MSM_CPU_8X60, 'MSM_CPU_8X60'), + + # 8960 IDs + (87, MSM_CPU_8960, 'MSM_CPU_8960'), + + # 7x25A IDs + (88, MSM_CPU_7X25A, 'MSM_CPU_7X25A'), + (89, MSM_CPU_7X25A, 'MSM_CPU_7X25A'), + (96, MSM_CPU_7X25A, 'MSM_CPU_7X25A'), + + # 7x27A IDs + (90, MSM_CPU_7X27A, 'MSM_CPU_7X27A'), + (91, MSM_CPU_7X27A, 'MSM_CPU_7X27A'), + (92, MSM_CPU_7X27A, 'MSM_CPU_7X27A'), + (97, MSM_CPU_7X27A, 'MSM_CPU_7X27A'), + + # FSM9xxx ID + (94, FSM_CPU_9XXX, 'FSM_CPU_9XXX'), + (95, FSM_CPU_9XXX, 'FSM_CPU_9XXX'), + + # 7x25AA ID + (98, MSM_CPU_7X25AA, 'MSM_CPU_7X25AA'), + (99, MSM_CPU_7X25AA, 'MSM_CPU_7X25AA'), + (100, MSM_CPU_7X25AA, 'MSM_CPU_7X25AA'), + + # 7x27AA ID + (101, MSM_CPU_7X27AA, 'MSM_CPU_7X27AA'), + (102, MSM_CPU_7X27AA, 'MSM_CPU_7X27AA'), + (103, MSM_CPU_7X27AA, 'MSM_CPU_7X27AA'), + + # 9x15 ID + (104, MSM_CPU_9615, 'MSM_CPU_9615'), + (105, MSM_CPU_9615, 'MSM_CPU_9615'), + (106, MSM_CPU_9615, 'MSM_CPU_9615'), + (107, MSM_CPU_9615, 'MSM_CPU_9615'), + + # 8064 IDs + (109, MSM_CPU_8064, 'MSM_CPU_8064'), + (130, MSM_CPU_8064, 'MSM_CPU_8064'), + + # 8930 IDs + (116, MSM_CPU_8930, 'MSM_CPU_8930'), + (117, MSM_CPU_8930, 'MSM_CPU_8930'), + (118, MSM_CPU_8930, 'MSM_CPU_8930'), + (119, MSM_CPU_8930, 'MSM_CPU_8930'), + + # 8627 IDs + (120, MSM_CPU_8627, 'MSM_CPU_8627'), + (121, MSM_CPU_8627, 'MSM_CPU_8627'), + + # 8660A ID + (122, MSM_CPU_8960, 'MSM_CPU_8960'), + + # 8260A ID + (123, MSM_CPU_8960, '8260A'), + + # 8060A ID + (124, MSM_CPU_8960, '8060A'), + + # Copper IDs + (126, MSM_CPU_8974, 'MSM_CPU_8974'), + (184, MSM_CPU_8974, 'MSM_CPU_8974'), + (185, MSM_CPU_8974, 'MSM_CPU_8974'), + (186, MSM_CPU_8974, 'MSM_CPU_8974'), + + # 8974 PRO AA IDs + (208, MSM_CPU_8974PRO_AA, 'MSM_CPU_8974PRO_AA'), + (211, MSM_CPU_8974PRO_AA, 'MSM_CPU_8974PRO_AA'), + (214, MSM_CPU_8974PRO_AA, 'MSM_CPU_8974PRO_AA'), + (217, MSM_CPU_8974PRO_AA, 'MSM_CPU_8974PRO_AA'), + + # 8974 PRO AB IDs + (209, MSM_CPU_8974PRO_AB, 'MSM_CPU_8974PRO_AB'), + (212, MSM_CPU_8974PRO_AB, 'MSM_CPU_8974PRO_AB'), + (215, MSM_CPU_8974PRO_AB, 'MSM_CPU_8974PRO_AB'), + (218, MSM_CPU_8974PRO_AB, 'MSM_CPU_8974PRO_AB'), + + # 8974 PRO AC IDs + (194, MSM_CPU_8974PRO_AC, 'MSM_CPU_8974PRO_AC'), + (210, MSM_CPU_8974PRO_AC, 'MSM_CPU_8974PRO_AC'), + (213, MSM_CPU_8974PRO_AC, 'MSM_CPU_8974PRO_AC'), + (216, MSM_CPU_8974PRO_AC, 'MSM_CPU_8974PRO_AC'), + + # 8625 IDs + (127, MSM_CPU_8625, 'MSM_CPU_8625'), + (128, MSM_CPU_8625, 'MSM_CPU_8625'), + (129, MSM_CPU_8625, 'MSM_CPU_8625'), + + # 8064 MPQ ID */ + (130, MSM_CPU_8064, 'MSM_CPU_8064'), + + # 7x25AB IDs + (131, MSM_CPU_7X25AB, 'MSM_CPU_7X25AB'), + (132, MSM_CPU_7X25AB, 'MSM_CPU_7X25AB'), + (133, MSM_CPU_7X25AB, 'MSM_CPU_7X25AB'), + (135, MSM_CPU_7X25AB, 'MSM_CPU_7X25AB'), + + # 9625 IDs + (134, MSM_CPU_9625, 'MSM_CPU_9625'), + (148, MSM_CPU_9625, 'MSM_CPU_9625'), + (149, MSM_CPU_9625, 'MSM_CPU_9625'), + (150, MSM_CPU_9625, 'MSM_CPU_9625'), + (151, MSM_CPU_9625, 'MSM_CPU_9625'), + (152, MSM_CPU_9625, 'MSM_CPU_9625'), + (173, MSM_CPU_9625, 'MSM_CPU_9625'), + (174, MSM_CPU_9625, 'MSM_CPU_9625'), + (175, MSM_CPU_9625, 'MSM_CPU_9625'), + + # 8960AB IDs + (138, MSM_CPU_8960AB, 'MSM_CPU_8960AB'), + (139, MSM_CPU_8960AB, 'MSM_CPU_8960AB'), + (140, MSM_CPU_8960AB, 'MSM_CPU_8960AB'), + (141, MSM_CPU_8960AB, 'MSM_CPU_8960AB'), + + # 8930AA IDs + (142, MSM_CPU_8930AA, 'MSM_CPU_8930AA'), + (143, MSM_CPU_8930AA, 'MSM_CPU_8930AA'), + (144, MSM_CPU_8930AA, 'MSM_CPU_8930AA'), + + # 8226 IDx + (145, MSM_CPU_8226, 'MSM_CPU_8226'), + (158, MSM_CPU_8226, 'MSM_CPU_8226'), + (159, MSM_CPU_8226, 'MSM_CPU_8226'), + (198, MSM_CPU_8226, 'MSM_CPU_8226'), + (199, MSM_CPU_8226, 'MSM_CPU_8226'), + (200, MSM_CPU_8226, 'MSM_CPU_8226'), + (205, MSM_CPU_8226, 'MSM_CPU_8226'), + (219, MSM_CPU_8226, 'MSM_CPU_8226'), + (220, MSM_CPU_8226, 'MSM_CPU_8226'), + (221, MSM_CPU_8226, 'MSM_CPU_8226'), + (222, MSM_CPU_8226, 'MSM_CPU_8226'), + (223, MSM_CPU_8226, 'MSM_CPU_8226'), + (224, MSM_CPU_8226, 'MSM_CPU_8226'), + + # 8610 IDx + (147, MSM_CPU_8610, 'MSM_CPU_8610'), + (161, MSM_CPU_8610, 'MSM_CPU_8610'), + (162, MSM_CPU_8610, 'MSM_CPU_8610'), + (163, MSM_CPU_8610, 'MSM_CPU_8610'), + (164, MSM_CPU_8610, 'MSM_CPU_8610'), + (165, MSM_CPU_8610, 'MSM_CPU_8610'), + (166, MSM_CPU_8610, 'MSM_CPU_8610'), + + # 8064AB IDs + (153, MSM_CPU_8064AB, 'MSM_CPU_8064AB'), + + # 8930AB IDs + (154, MSM_CPU_8930AB, 'MSM_CPU_8930AB'), + (155, MSM_CPU_8930AB, 'MSM_CPU_8930AB'), + (156, MSM_CPU_8930AB, 'MSM_CPU_8930AB'), + (157, MSM_CPU_8930AB, 'MSM_CPU_8930AB'), + + (160, MSM_CPU_8930AA, 'MSM_CPU_8930AA'), + + # 8084 IDs + (178, MSM_CPU_8084, 'MSM_CPU_8084'), + + # 9635 IDs + (187, MSM_CPU_KRYPTON, 'MSM_CPU_KRYPTON'), + (227, MSM_CPU_KRYPTON, 'MSM_CPU_KRYPTON'), + (228, MSM_CPU_KRYPTON, 'MSM_CPU_KRYPTON'), + (229, MSM_CPU_KRYPTON, 'MSM_CPU_KRYPTON'), + (230, MSM_CPU_KRYPTON, 'MSM_CPU_KRYPTON'), + (231, MSM_CPU_KRYPTON, 'MSM_CPU_KRYPTON'), + + (146, MSM_CPU_8092, 'MSM_CPU_8092'), + + # Uninitialized IDs are not known to run Linux. + # MSM_CPU_UNKNOWN is set to 0 to ensure these IDs are + # considered as unknown CPU. +] + +socinfo_v1 = functools.reduce(lambda x, y: x + y, [ + 'I', # format + 'I', # id + 'I', # version +]) + +launch_config_str = 'OS=\nID=T32_1000002\nTMP=C:\\TEMP\nSYS=C:\\T32\nHELP=C:\\T32\\pdf\n\nPBI=SIM\nSCREEN=\nFONT=SMALL\nHEADER=Trace32-ScorpionSimulator\nPRINTER=WINDOWS' + +# The smem code is very stable and unlikely to go away or be changed. +# Rather than go through the hassel of parsing the id through gdb, +# just hard code it + +SMEM_HW_SW_BUILD_ID = 0x89 +BUILD_ID_LENGTH = 32 + +first_mem_file_names = ['EBICS0.BIN', + 'EBI1.BIN', 'DDRCS0.BIN', 'ebi1_cs0.bin', 'DDRCS0_0.BIN'] +extra_mem_file_names = ['EBI1CS1.BIN', 'DDRCS1.BIN', 'ebi1_cs1.bin', 'DDRCS0_1.BIN'] + + +class RamDump(): + + class Unwinder (): + + class Stackframe (): + + def __init__(self, fp, sp, lr, pc): + self.fp = fp + self.sp = sp + self.lr = lr + self.pc = pc + + class UnwindCtrlBlock (): + + def __init__(self): + self.vrs = 16 * [0] + self.insn = 0 + self.entries = -1 + self.byte = -1 + self.index = 0 + + def __init__(self, ramdump): + start = ramdump.addr_lookup('__start_unwind_idx') + end = ramdump.addr_lookup('__stop_unwind_idx') + if (start is None) or (end is None): + print_out_str('!!! Could not lookup unwinding information') + return None + # addresses + self.start_idx = start + self.stop_idx = end + self.unwind_table = [] + self.ramdump = ramdump + i = 0 + for addr in range(start, end, 8): + (a, b) = ramdump.read_string(addr, '<II') + self.unwind_table.append((a, b, start + 8 * i)) + i += 1 + + ver = ramdump.version + if re.search('3.0.\d', ver) is not None: + self.search_idx = self.search_idx_3_0 + else: + self.search_idx = self.search_idx_3_4 + # index into the table + self.origin = self.unwind_find_origin() + + def unwind_find_origin(self): + start = 0 + stop = len(self.unwind_table) + while (start < stop): + mid = start + ((stop - start) >> 1) + if (self.unwind_table[mid][0] >= 0x40000000): + start = mid + 1 + else: + stop = mid + return stop + + def unwind_frame_generic(self, frame): + high = 0 + fp = frame.fp + + low = frame.sp + mask = (THREAD_SIZE) - 1 + + high = (low + mask) & (~mask) # ALIGN(low, THREAD_SIZE) + + # /* check current frame pointer is within bounds */ + if (fp < (low + 12) or fp + 4 >= high): + return -1 + + fp_is_at = self.ramdump.read_word(frame.fp - 12) + sp_is_at = self.ramdump.read_word(frame.fp - 8) + pc_is_at = self.ramdump.read_word(frame.fp - 4) + + frame.fp = fp_is_at + frame.sp = sp_is_at + frame.pc = pc_is_at + + return 0 + + def walk_stackframe_generic(self, frame): + while True: + symname = self.ramdump.addr_to_symbol(frame.pc) + print_out_str(symname) + + ret = self.unwind_frame_generic(frame) + if ret < 0: + break + + def unwind_backtrace_generic(self, sp, fp, pc): + frame = self.Stackframe() + frame.fp = fp + frame.pc = pc + frame.sp = sp + walk_stackframe_generic(frame) + + def search_idx_3_4(self, addr): + start = 0 + stop = len(self.unwind_table) + orig = addr + + if (addr < self.start_idx): + stop = self.origin + else: + start = self.origin + + if (start >= stop): + return None + + addr = (addr - self.unwind_table[start][2]) & 0x7fffffff + + while (start < (stop - 1)): + mid = start + ((stop - start) >> 1) + + dif = (self.unwind_table[mid][2] + - self.unwind_table[start][2]) + if ((addr - dif) < self.unwind_table[mid][0]): + stop = mid + else: + addr = addr - dif + start = mid + + if self.unwind_table[start][0] <= addr: + return self.unwind_table[start] + else: + return None + + def search_idx_3_0(self, addr): + first = 0 + last = len(self.unwind_table) + while (first < last - 1): + mid = first + ((last - first + 1) >> 1) + if (addr < self.unwind_table[mid][0]): + last = mid + else: + first = mid + + return self.unwind_table[first] + + def unwind_get_byte(self, ctrl): + + if (ctrl.entries <= 0): + print_out_str('unwind: Corrupt unwind table') + return 0 + + val = self.ramdump.read_word(ctrl.insn) + + ret = (val >> (ctrl.byte * 8)) & 0xff + + if (ctrl.byte == 0): + ctrl.insn += 4 + ctrl.entries -= 1 + ctrl.byte = 3 + else: + ctrl.byte -= 1 + + return ret + + def unwind_exec_insn(self, ctrl, trace=False): + insn = self.unwind_get_byte(ctrl) + + if ((insn & 0xc0) == 0x00): + ctrl.vrs[SP] += ((insn & 0x3f) << 2) + 4 + if trace: + print_out_str( + ' add {0} to stack'.format(((insn & 0x3f) << 2) + 4)) + elif ((insn & 0xc0) == 0x40): + ctrl.vrs[SP] -= ((insn & 0x3f) << 2) + 4 + if trace: + print_out_str( + ' subtract {0} from stack'.format(((insn & 0x3f) << 2) + 4)) + elif ((insn & 0xf0) == 0x80): + vsp = ctrl.vrs[SP] + reg = 4 + + insn = (insn << 8) | self.unwind_get_byte(ctrl) + mask = insn & 0x0fff + if (mask == 0): + print_out_str("unwind: 'Refuse to unwind' instruction") + return -1 + + # pop R4-R15 according to mask */ + load_sp = mask & (1 << (13 - 4)) + while (mask): + if (mask & 1): + ctrl.vrs[reg] = self.ramdump.read_word(vsp) + if trace: + print_out_str( + ' pop r{0} from stack'.format(reg)) + if ctrl.vrs[reg] is None: + return -1 + vsp += 4 + mask >>= 1 + reg += 1 + if not load_sp: + ctrl.vrs[SP] = vsp + + elif ((insn & 0xf0) == 0x90 and (insn & 0x0d) != 0x0d): + if trace: + print_out_str( + ' set SP with the value from {0}'.format(insn & 0x0f)) + ctrl.vrs[SP] = ctrl.vrs[insn & 0x0f] + elif ((insn & 0xf0) == 0xa0): + vsp = ctrl.vrs[SP] + a = list(range(4, 4 + (insn & 7))) + a.append(4 + (insn & 7)) + # pop R4-R[4+bbb] */ + for reg in (a): + ctrl.vrs[reg] = self.ramdump.read_word(vsp) + if trace: + print_out_str(' pop r{0} from stack'.format(reg)) + + if ctrl.vrs[reg] is None: + return -1 + vsp += 4 + if (insn & 0x80): + if trace: + print_out_str(' set LR from the stack') + ctrl.vrs[14] = self.ramdump.read_word(vsp) + if ctrl.vrs[14] is None: + return -1 + vsp += 4 + ctrl.vrs[SP] = vsp + elif (insn == 0xb0): + if trace: + print_out_str(' set pc = lr') + if (ctrl.vrs[PC] == 0): + ctrl.vrs[PC] = ctrl.vrs[LR] + ctrl.entries = 0 + elif (insn == 0xb1): + mask = self.unwind_get_byte(ctrl) + vsp = ctrl.vrs[SP] + reg = 0 + + if (mask == 0 or mask & 0xf0): + print_out_str('unwind: Spare encoding') + return -1 + + # pop R0-R3 according to mask + while mask: + if (mask & 1): + ctrl.vrs[reg] = self.ramdump.read_word(vsp) + if trace: + print_out_str( + ' pop r{0} from stack'.format(reg)) + if ctrl.vrs[reg] is None: + return -1 + vsp += 4 + mask >>= 1 + reg += 1 + ctrl.vrs[SP] = vsp + elif (insn == 0xb2): + uleb128 = self.unwind_get_byte(ctrl) + if trace: + print_out_str( + ' Adjust sp by {0}'.format(0x204 + (uleb128 << 2))) + + ctrl.vrs[SP] += 0x204 + (uleb128 << 2) + else: + print_out_str('unwind: Unhandled instruction') + return -1 + + return 0 + + def prel31_to_addr(self, addr): + value = self.ramdump.read_word(addr) + # offset = (value << 1) >> 1 + # C wants this sign extended. Python doesn't do that. + # Sign extend manually. + if (value & 0x40000000): + offset = value | 0x80000000 + else: + offset = value + + # This addition relies on integer overflow + # Emulate this behavior + temp = addr + offset + return (temp & 0xffffffff) + ((temp >> 32) & 0xffffffff) + + def unwind_frame(self, frame, trace=False): + low = frame.sp + high = ((low + (THREAD_SIZE - 1)) & ~(THREAD_SIZE - 1)) + \ + THREAD_SIZE + idx = self.search_idx(frame.pc) + + if (idx is None): + if trace: + print_out_str("can't find %x" % frame.pc) + return -1 + + ctrl = self.UnwindCtrlBlock() + ctrl.vrs[FP] = frame.fp + ctrl.vrs[SP] = frame.sp + ctrl.vrs[LR] = frame.lr + ctrl.vrs[PC] = 0 + + if (idx[1] == 1): + return -1 + + elif ((idx[1] & 0x80000000) == 0): + ctrl.insn = self.prel31_to_addr(idx[2] + 4) + + elif (idx[1] & 0xff000000) == 0x80000000: + ctrl.insn = idx[2] + 4 + else: + print_out_str('not supported') + return -1 + + val = self.ramdump.read_word(ctrl.insn) + + if ((val & 0xff000000) == 0x80000000): + ctrl.byte = 2 + ctrl.entries = 1 + elif ((val & 0xff000000) == 0x81000000): + ctrl.byte = 1 + ctrl.entries = 1 + ((val & 0x00ff0000) >> 16) + else: + return -1 + + while (ctrl.entries > 0): + urc = self.unwind_exec_insn(ctrl, trace) + if (urc < 0): + return urc + if (ctrl.vrs[SP] < low or ctrl.vrs[SP] >= high): + return -1 + + if (ctrl.vrs[PC] == 0): + ctrl.vrs[PC] = ctrl.vrs[LR] + + # check for infinite loop */ + if (frame.pc == ctrl.vrs[PC]): + return -1 + + frame.fp = ctrl.vrs[FP] + frame.sp = ctrl.vrs[SP] + frame.lr = ctrl.vrs[LR] + frame.pc = ctrl.vrs[PC] + + return 0 + + def unwind_backtrace(self, sp, fp, pc, lr, extra_str='', out_file=None, trace=False): + offset = 0 + frame = self.Stackframe(fp, sp, lr, pc) + frame.fp = fp + frame.sp = sp + frame.lr = lr + frame.pc = pc + + while True: + where = frame.pc + offset = 0 + + r = self.ramdump.unwind_lookup(frame.pc) + if r is None: + symname = 'UNKNOWN' + offset = 0x0 + else: + symname, offset = r + pstring = ( + extra_str + '[<{0:x}>] {1}+0x{2:x}'.format(frame.pc, symname, offset)) + if out_file: + out_file.write(pstring + '\n') + else: + print_out_str(pstring) + + urc = self.unwind_frame(frame, trace) + if urc < 0: + break + + def __init__(self, vmlinux_path, nm_path, gdb_path, ebi, file_path, phys_offset, outdir, hw_id=None, hw_version=None): + self.ebi_files = [] + self.phys_offset = None + self.tz_start = 0 + self.ebi_start = 0 + self.cpu_type = None + self.hw_id = hw_id + self.hw_version = hw_version + self.offset_table = [] + self.vmlinux = vmlinux_path + self.nm_path = nm_path + self.gdb_path = gdb_path + self.outdir = outdir + self.imem_fname = None + self.gdbmi = gdbmi.GdbMI(self.gdb_path, self.vmlinux) + self.gdbmi.open() + if ebi is not None: + # TODO sanity check to make sure the memory regions don't overlap + for file_path, start, end in ebi: + fd = open(file_path, 'rb') + if not fd: + print_out_str( + 'Could not open {0}. Will not be part of dump'.format(file_path)) + continue + self.ebi_files.append((fd, start, end, file_path)) + else: + if not self.auto_parse(file_path): + return None + if self.ebi_start == 0: + self.ebi_start = self.ebi_files[0][1] + if self.phys_offset is None: + self.get_hw_id() + if phys_offset is not None: + print_out_str( + '[!!!] Phys offset was set to {0:x}'.format(phys_offset)) + self.phys_offset = phys_offset + self.lookup_table = [] + self.page_offset = 0xc0000000 + self.config = [] + self.setup_symbol_tables() + + # The address of swapper_pg_dir can be used to determine + # whether or not we're running with LPAE enabled since an + # extra 4k is needed for LPAE. If it's 0x5000 below + # PAGE_OFFSET + TEXT_OFFSET then we know we're using LPAE. For + # non-LPAE it should be 0x4000 below PAGE_OFFSET + TEXT_OFFSET + swapper_pg_dir_addr = self.addr_lookup('swapper_pg_dir') + kernel_text_offset = 0x8000 + pg_dir_size = kernel_text_offset - \ + (swapper_pg_dir_addr - self.page_offset) + if pg_dir_size == 0x4000: + print_out_str('Using non-LPAE MMU') + self.mmu = Armv7MMU(self) + elif pg_dir_size == 0x5000: + print_out_str('Using LPAE MMU') + self.mmu = Armv7LPAEMMU(self) + else: + print_out_str( + "!!! Couldn't determine whether or not we're using LPAE!") + print_out_str( + '!!! This is a BUG in the parser and should be reported.') + sys.exit(1) + + if not self.get_version(): + print_out_str('!!! Could not get the Linux version!') + print_out_str( + '!!! Your vmlinux is probably wrong for these dumps') + print_out_str('!!! Exiting now') + sys.exit(1) + if not self.get_config(): + print_out_str('!!! Could not get saved configuration') + print_out_str( + '!!! This is really bad and probably indicates RAM corruption') + print_out_str('!!! Some features may be disabled!') + self.unwind = self.Unwinder(self) + + def __del__(self): + self.gdbmi.close() + + def open_file(self, file_name, mode='wb'): + file_path = os.path.join(self.outdir, file_name) + f = None + try: + f = open(file_path, mode) + except: + print_out_str('Could not open path {0}'.format(file_path)) + print_out_str('Do you have write/read permissions on the path?') + sys.exit(1) + return f + + def get_config(self): + kconfig_addr = self.addr_lookup('kernel_config_data') + if kconfig_addr is None: + return + kconfig_size = self.sizeof('kernel_config_data') + # size includes magic, offset from it + kconfig_size = kconfig_size - 16 - 1 + zconfig = NamedTemporaryFile(mode='wb', delete=False) + # kconfig data starts with magic 8 byte string, go past that + s = self.read_cstring(kconfig_addr, 8) + if s != 'IKCFG_ST': + return + kconfig_addr = kconfig_addr + 8 + for i in range(0, kconfig_size): + val = self.read_byte(kconfig_addr + i) + zconfig.write(struct.pack('<B', val)) + + zconfig.close() + zconfig_in = gzip.open(zconfig.name, 'rb') + try: + t = zconfig_in.readlines() + except: + return False + zconfig_in.close() + os.remove(zconfig.name) + for l in t: + self.config.append(l.rstrip().decode('ascii', 'ignore')) + return True + + def is_config_defined(self, config): + s = config + '=y' + return s in self.config + + def get_version(self): + banner_addr = self.addr_lookup('linux_banner') + if banner_addr is not None: + # Don't try virt to phys yet, compute manually + banner_addr = banner_addr - 0xc0000000 + self.phys_offset + b = self.read_cstring(banner_addr, 256, False) + if b is None: + print_out_str('!!! Could not read banner address!') + return False + v = re.search('Linux version (\d{0,2}\.\d{0,2}\.\d{0,2})', b) + if v is None: + print_out_str('!!! Could not match version! {0}'.format(b)) + return False + self.version = v.group(1) + print_out_str('Linux Banner: ' + b.rstrip()) + print_out_str('version = {0}'.format(self.version)) + return True + else: + print_out_str('!!! Could not lookup banner address') + return False + + def print_command_line(self): + command_addr = self.addr_lookup('saved_command_line') + if command_addr is not None: + command_addr = self.read_word(command_addr) + b = self.read_cstring(command_addr, 2048) + if b is None: + print_out_str('!!! could not read saved command line address') + return False + print_out_str('Command Line: ' + b) + return True + else: + print_out_str('!!! Could not lookup saved command line address') + return False + + def auto_parse(self, file_path): + first_mem_path = None + + for f in first_mem_file_names: + test_path = file_path + '/' + f + if os.path.exists(test_path): + first_mem_path = test_path + break + + if first_mem_path is None: + print_out_str('!!! Could not open a memory file. I give up') + sys.exit(1) + + first_mem = open(first_mem_path, 'rb') + # put some dummy data in for now + self.ebi_files = [(first_mem, 0, 0xffff0000, first_mem_path)] + if not self.get_hw_id(): + return False + first_mem_end = self.ebi_start + os.path.getsize(first_mem_path) - 1 + self.ebi_files = [ + (first_mem, self.ebi_start, first_mem_end, first_mem_path)] + print_out_str( + 'Adding {0} {1:x}--{2:x}'.format(first_mem_path, self.ebi_start, first_mem_end)) + + for f in extra_mem_file_names: + extra_path = file_path + '/' + f + + if os.path.exists(extra_path): + extra = open(extra_path, 'rb') + extra_start = self.ebi_start + os.path.getsize(first_mem_path) + extra_end = extra_start + os.path.getsize(extra_path) - 1 + print_out_str( + 'Adding {0} {1:x}--{2:x}'.format(extra_path, extra_start, extra_end)) + self.ebi_files.append( + (extra, extra_start, extra_end, extra_path)) + + if self.imem_fname is not None: + imemc_path = file_path + '/' + self.imem_fname + if os.path.exists(imemc_path): + imemc = open(imemc_path, 'rb') + imemc_start = self.tz_start + imemc_end = imemc_start + os.path.getsize(imemc_path) - 1 + print_out_str( + 'Adding {0} {1:x}--{2:x}'.format(imemc_path, imemc_start, imemc_end)) + self.ebi_files.append( + (imemc, imemc_start, imemc_end, imemc_path)) + return True + + # TODO support linux launcher, for when linux T32 actually happens + def create_t32_launcher(self): + out_path = self.outdir + launch_config = open(out_path + '/t32_config.t32', 'wb') + launch_config.write(launch_config_str.encode('ascii', 'ignore')) + launch_config.close() + + startup_script = open(out_path + '/t32_startup_script.cmm', 'wb') + + startup_script.write( + 'sys.cpu {0}\n'.format(self.cpu_type).encode('ascii', 'ignore')) + startup_script.write('sys.up\n'.encode('ascii', 'ignore')) + + for ram in self.ebi_files: + ebi_path = os.path.abspath(ram[3]) + startup_script.write('data.load.binary {0} 0x{1:x}\n'.format( + ebi_path, ram[1]).encode('ascii', 'ignore')) + startup_script.write( + 'PER.S.F C15:0x2 %L 0x{0:x}\n'.format(self.mmu.ttbr).encode('ascii', 'ignore')) + if isinstance(self.mmu, Armv7LPAEMMU): + # TTBR1. This gets setup once and never change again even if TTBR0 + # changes + startup_script.write('PER.S.F C15:0x102 %L 0x{0:x}\n'.format( + self.mmu.ttbr + 0x4000).encode('ascii', 'ignore')) + # TTBCR with EAE and T1SZ set approprately + startup_script.write( + 'PER.S.F C15:0x202 %L 0x80030000\n'.encode('ascii', 'ignore')) + startup_script.write('mmu.on\n'.encode('ascii', 'ignore')) + startup_script.write('mmu.scan\n'.encode('ascii', 'ignore')) + startup_script.write( + ('data.load.elf ' + os.path.abspath(self.vmlinux) + ' /nocode\n').encode('ascii', 'ignore')) + startup_script.write( + 'task.config c:\\t32\\demo\\arm\\kernel\\linux\\linux.t32\n'.encode('ascii', 'ignore')) + startup_script.write( + 'menu.reprogram c:\\t32\\demo\\arm\\kernel\\linux\\linux.men\n'.encode('ascii', 'ignore')) + startup_script.write('task.dtask\n'.encode('ascii', 'ignore')) + startup_script.write( + 'v.v %ASCII %STRING linux_banner\n'.encode('ascii', 'ignore')) + if os.path.exists(out_path + '/regs_panic.cmm'): + startup_script.write( + 'do {0}\n'.format(out_path + '/regs_panic.cmm').encode('ascii', 'ignore')) + elif os.path.exists(out_path + '/core0_regs.cmm'): + startup_script.write( + 'do {0}\n'.format(out_path + '/core0_regs.cmm').encode('ascii', 'ignore')) + startup_script.close() + + t32_bat = open(out_path + '/launch_t32.bat', 'wb') + t32_bat.write(('start c:\\t32\\t32MARM.exe -c ' + out_path + '/t32_config.t32, ' + + out_path + '/t32_startup_script.cmm').encode('ascii', 'ignore')) + t32_bat.close() + print_out_str( + '--- Created a T32 Simulator launcher (run {0}/launch_t32.bat)'.format(out_path)) + + def read_tz_offset(self): + if self.tz_addr == 0: + print_out_str( + 'No TZ address was given, cannot read the magic value!') + return None + else: + return self.read_word(self.tz_addr, False) + + def find_hw_id(self, socinfo_id, version): + if self.hw_version is not None: + version = self.hw_version + for cpuid in cpu_of_id: + if socinfo_id == cpuid[0]: + for hwid in hw_ids: + if cpuid[1] == hwid[HARDWARE_ID_IDX]: + if hwid[VERSION_COMPARE] is not None and hwid[VERSION_COMPARE] != version: + continue + + return hwid + return None + + def get_hw_id(self): + heap_toc_offset = self.field_offset('struct smem_shared', 'heap_toc') + if heap_toc_offset is None: + print_out_str( + '!!!! Could not get a necessary offset for auto detection!') + print_out_str( + '!!!! Please check the gdb path which is used for offsets!') + print_out_str('!!!! Also check that the vmlinux is not stripped') + print_out_str('!!!! Exiting...') + sys.exit(1) + + smem_heap_entry_size = self.sizeof('struct smem_heap_entry') + offset_offset = self.field_offset('struct smem_heap_entry', 'offset') + socinfo_format = -1 + socinfo_id = -1 + socinfo_version = 0 + socinfo_build_id = 'DUMMY' + hwid = None + + if (self.hw_id is None): + for smem_offset in smem_offsets: + socinfo_start_addr = self.ebi_files[0][ + 1] + smem_offset + heap_toc_offset + smem_heap_entry_size * SMEM_HW_SW_BUILD_ID + offset_offset + soc_start = self.read_word(socinfo_start_addr, False) + if soc_start is None: + continue + + socinfo_start = self.ebi_files[0][1] + smem_offset + soc_start + + socinfo_format = self.read_word(socinfo_start, False) + socinfo_id = self.read_word(socinfo_start + 4, False) + socinfo_version = self.read_word(socinfo_start + 8, False) + socinfo_build_id = self.read_cstring( + socinfo_start + 12, BUILD_ID_LENGTH, False) + + if socinfo_id is not None and socinfo_version is not None: + hwid = self.find_hw_id(socinfo_id, socinfo_version >> 16) + if (hwid is not None): + break + if (hwid is None): + print_out_str('!!!! Could not find hardware') + print_out_str("!!!! The SMEM didn't match anything") + print_out_str( + '!!!! You can use --force-hardware to use a specific set of values') + sys.exit(1) + + else: + hwid = None + for a in hw_ids: + if self.hw_id == a[HARDWARE_ID_IDX] and self.hw_version == a[VERSION_COMPARE]: + print_out_str( + '!!! Hardware id found! The socinfo values given are bogus') + print_out_str('!!! Proceed with caution!') + hwid = a + break + if hwid is None: + print_out_str( + '!!! A bogus hardware id was specified: {0}'.format(self.hw_id)) + print_out_str( + '!!! Try passing one of these to --force-hardware.') + print_out_str( + '!!! If a version is specified, pass the version with --force-version') + for a in hw_ids: + if a[VERSION_COMPARE] is not None: + v = 'v{0}'.format(a[VERSION_COMPARE]) + else: + v = '' + print_out_str( + '!!! {0}{1}'.format(a[HARDWARE_ID_IDX], v)) + sys.exit(1) + + print_out_str('\nHardware match: {0}'.format(hwid[HARDWARE_ID_IDX])) + print_out_str('Socinfo id = {0}, version {1:x}.{2:x}'.format( + socinfo_id, socinfo_version >> 16, socinfo_version & 0xFFFF)) + print_out_str('Socinfo build = {0}'.format(socinfo_build_id)) + print_out_str( + 'Now setting phys_offset to {0:x}'.format(hwid[PHYS_OFFSET_IDX])) + print_out_str( + 'TZ address: {0:x}'.format(hwid[WATCHDOG_BARK_OFFSET_IDX])) + self.phys_offset = hwid[PHYS_OFFSET_IDX] + self.tz_addr = hwid[WATCHDOG_BARK_OFFSET_IDX] + self.ebi_start = hwid[MEMORY_START_IDX] + self.tz_start = hwid[IMEM_START_IDX] + self.hw_id = hwid[HARDWARE_ID_IDX] + self.cpu_type = hwid[CPU_TYPE] + self.imem_fname = hwid[IMEM_FILENAME] + return True + + def virt_to_phys(self, virt): + return self.mmu.virt_to_phys(virt) + + def setup_symbol_tables(self): + stream = os.popen(self.nm_path + ' -n ' + self.vmlinux) + symbols = stream.readlines() + for line in symbols: + s = line.split(' ') + if len(s) == 3: + self.lookup_table.append((int(s[0], 16), s[2].rstrip())) + stream.close() + + def addr_lookup(self, symbol): + try: + return self.gdbmi.address_of(symbol) + except gdbmi.GdbMIException: + pass + + def symbol_lookup(self, addr): + try: + return self.gdbmi.symbol_at(addr).symbol + except gdbmi.GdbMIException: + pass + + def sizeof(self, the_type): + try: + return self.gdbmi.sizeof(the_type) + except gdbmi.GdbMIException: + pass + + def array_index(self, addr, the_type, index): + """Index into the array of type `the_type' located at `addr'. + + I.e.: + + Given: + + int my_arr[3]; + my_arr[2] = 42; + + + The following: + + my_arr_addr = dump.addr_lookup("my_arr") + dump.read_word(dump.array_index(my_arr_addr, "int", 2)) + + will return 42. + + """ + offset = self.gdbmi.sizeof(the_type) * index + return addr + offset + + def field_offset(self, the_type, field): + try: + return self.gdbmi.field_offset(the_type, field) + except gdbmi.GdbMIException: + pass + + def unwind_lookup(self, addr, symbol_size=0): + if (addr is None): + return ('(Invalid address)', 0x0) + + # modules are not supported so just print out an address + # instead of a confusing symbol + if (addr < self.page_offset): + return ('(No symbol for address {0:x})'.format(addr), 0x0) + + low = 0 + high = len(self.lookup_table) + # Python now complains about division producing floats + mid = (low + high) >> 1 + premid = 0 + + while(not(addr >= self.lookup_table[mid][0] and addr < self.lookup_table[mid + 1][0])): + + if(addr < self.lookup_table[mid][0]): + high = mid - 1 + + if(addr > self.lookup_table[mid][0]): + low = mid + 1 + + mid = (high + low) >> 1 + + if(mid == premid): + return None + if (mid + 1) >= len(self.lookup_table) or mid < 0: + return None + + premid = mid + + if symbol_size == 0: + return (self.lookup_table[mid][1], addr - self.lookup_table[mid][0]) + else: + return (self.lookup_table[mid][1], self.lookup_table[mid + 1][0] - self.lookup_table[mid][0]) + + def read_physical(self, addr, length, trace=False): + ebi = (-1, -1, -1) + for a in self.ebi_files: + fd, start, end, path = a + if addr >= start and addr <= end: + ebi = a + break + if ebi[0] is -1: + return None + if trace: + print_out_str('reading from {0}'.format(ebi[0])) + print_out_str('start = {0:x}'.format(ebi[1])) + print_out_str('end = {0:x}'.format(ebi[2])) + print_out_str('length = {0:x}'.format(length)) + offset = addr - ebi[1] + if trace: + print_out_str('offset = {0:x}'.format(offset)) + ebi[0].seek(offset) + a = ebi[0].read(length) + if trace: + print_out_str('result = {0}'.format(a)) + print_out_str('lenght = {0}'.format(len(a))) + return a + + def read_dword(self, address, virtual=True, trace=False, cpu=None): + if trace: + print_out_str('reading {0:x}'.format(address)) + s = self.read_string(address, '<Q', virtual, trace, cpu) + if s is None: + return None + else: + return s[0] + + # returns the 4 bytes read from the specified virtual address + # return None on error + def read_word(self, address, virtual=True, trace=False, cpu=None): + if trace: + print_out_str('reading {0:x}'.format(address)) + s = self.read_string(address, '<I', virtual, trace, cpu) + if s is None: + return None + else: + return s[0] + + def read_halfword(self, address, virtual=True, trace=False, cpu=None): + if trace: + print_out_str('reading {0:x}'.format(address)) + s = self.read_string(address, '<H', virtual, trace, cpu) + if s is None: + return None + else: + return s[0] + + def read_byte(self, address, virtual=True, trace=False, cpu=None): + if trace: + print_out_str('reading {0:x}'.format(address)) + s = self.read_string(address, '<B', virtual, trace, cpu) + if s is None: + return None + else: + return s[0] + + def read_cstring(self, address, max_length, virtual=True, cpu=None): + addr = address + if virtual: + if cpu is not None: + address += pcpu_offset + self.per_cpu_offset(cpu) + addr = self.virt_to_phys(address) + s = self.read_physical(addr, max_length) + if s is not None: + a = s.decode('ascii', 'ignore') + return a.split('\0')[0] + else: + return s + + # returns a tuple of the result from reading from the specified fromat string + # return None on failure + def read_string(self, address, format_string, virtual=True, trace=False, cpu=None): + addr = address + per_cpu_string = '' + if virtual: + if cpu is not None: + pcpu_offset = self.per_cpu_offset(cpu) + address += pcpu_offset + per_cpu_string = ' with per-cpu offset of ' + hex(pcpu_offset) + addr = self.virt_to_phys(address) + if trace: + print_out_str('reading from phys {0:x}{1}'.format(addr, + per_cpu_string)) + s = self.read_physical(addr, struct.calcsize(format_string), trace) + if (s is None) or (s == ''): + if trace: + print_out_str( + 'address {0:x} failed hard core (v {1} t{2})'.format(addr, virtual, trace)) + return None + return struct.unpack(format_string, s) + + def per_cpu_offset(self, cpu): + per_cpu_offset_addr = self.addr_lookup('__per_cpu_offset') + if per_cpu_offset_addr is None: + return 0 + per_cpu_offset_addr_indexed = self.array_index( + per_cpu_offset_addr, 'unsigned long', cpu) + return self.read_word(per_cpu_offset_addr_indexed) + + def get_num_cpus(self): + cpu_present_bits_addr = self.addr_lookup('cpu_present_bits') + cpu_present_bits = self.read_word(cpu_present_bits_addr) + return bin(cpu_present_bits).count('1') + + def iter_cpus(self): + return xrange(self.get_num_cpus()) diff --git a/linux-ramdump-parser-v2/ramparse.py b/linux-ramdump-parser-v2/ramparse.py new file mode 100755 index 0000000000000000000000000000000000000000..4128c2116f411543ea7aa4691c7d965842e880f5 --- /dev/null +++ b/linux-ramdump-parser-v2/ramparse.py @@ -0,0 +1,218 @@ +#!/usr/bin/python + +# Copyright (c) 2012-2013, 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 os +from optparse import OptionParser + +import parser_util +from ramdump import RamDump +from print_out import print_out_str, set_outfile, print_out_section + +# Please update version when something is changed!' +VERSION = '2.0' + + +def parse_ram_file(option, opt_str, value, parser): + a = getattr(parser.values, option.dest) + if a is None: + a = [] + temp = [] + for arg in parser.rargs: + if arg[:2] == '--': + break + if arg[:1] == '-' and len(arg) > 1: + break + temp.append(arg) + + if len(temp) is not 3: + raise OptionValueError( + "Ram files must be specified in 'name, start, end' format") + + a.append((temp[0], int(temp[1], 16), int(temp[2], 16))) + setattr(parser.values, option.dest, a) + +if __name__ == '__main__': + usage = 'usage: %prog [options to print]. Run with --help for more details' + parser = OptionParser(usage) + parser.add_option('', '--print-watchdog-time', action='store_true', + dest='watchdog_time', help='Print watchdog timing information', default=False) + parser.add_option('-e', '--ram-file', dest='ram_addr', + help='List of ram files (name, start, end)', action='callback', callback=parse_ram_file) + parser.add_option('-v', '--vmlinux', dest='vmlinux', help='vmlinux path') + parser.add_option('-n', '--nm-path', dest='nm', help='nm path') + parser.add_option('-g', '--gdb-path', dest='gdb', help='gdb path') + parser.add_option('-a', '--auto-dump', dest='autodump', + help='Auto find ram dumps from the path') + parser.add_option('-o', '--outdir', dest='outdir', help='Output directory') + parser.add_option('-s', '--t32launcher', action='store_true', + dest='t32launcher', help='Create T32 simulator launcher', default=False) + parser.add_option('-x', '--everything', action='store_true', + dest='everything', help='Output everything (may be slow') + parser.add_option('-f', '--output-file', dest='outfile', + help='Name of file to save output') + parser.add_option('', '--stdout', action='store_true', + dest='stdout', help='Dump to stdout instead of the file') + parser.add_option('', '--phys-offset', type='int', + dest='phys_offset', help='use custom phys offset') + parser.add_option('', '--force-hardware', type='int', + dest='force_hardware', help='Force the hardware detection') + parser.add_option( + '', '--force-version', type='int', dest='force_hardware_version', + help='Force the hardware detection to a specific hardware version') + parser.add_option('', '--parse-qdss', action='store_true', + dest='qdss', help='Parse QDSS (deprecated)') + + for p in parser_util.get_parsers(): + parser.add_option(p.shortopt or '', + p.longopt, + dest=p.cls.__name__, + help=p.desc, + action='store_true') + + (options, args) = parser.parse_args() + + if options.outdir: + if not os.path.exists(options.outdir): + print ('!!! Out directory does not exist. Create it first.') + sys.exit(1) + else: + options.outdir = '.' + + if options.outfile is None: + # dmesg_TZ is a very non-descriptive name and should be changed + # sometime in the future + options.outfile = 'dmesg_TZ.txt' + + if not options.stdout: + set_outfile(options.outdir + '/' + options.outfile) + + print_out_str('Linux Ram Dump Parser Version %s' % VERSION) + if options.vmlinux is None: + print_out_str("No vmlinux given. I can't proceed!") + parser.print_usage() + sys.exit(1) + + args = '' + for arg in sys.argv: + args = args + arg + ' ' + + print_out_str('Arguments: {0}'.format(args)) + + system_type = parser_util.get_system_type() + + if not os.path.exists(options.vmlinux): + print_out_str( + '{0} does not exist. Cannot proceed without vmlinux. Exiting...'.format(options.vmlinux)) + sys.exit(1) + elif not os.path.isfile(options.vmlinux): + print_out_str( + '{0} is not a file. Did you pass the ram file directory instead of the vmlinux?'.format(options.vmlinux)) + sys.exit(1) + else: + print_out_str('using vmlinx file {0}'.format(options.vmlinux)) + + if options.ram_addr is None and options.autodump is None: + print_out_str('Need one of --auto-dump or at least one --ram-file') + sys.exit(1) + + if options.ram_addr is not None: + for a in options.ram_addr: + if os.path.exists(a[0]): + print_out_str( + 'Loading Ram file {0} from {1:x}--{2:x}'.format(a[0], a[1], a[2])) + else: + print_out_str( + 'Ram file {0} does not exist. Exiting...'.format(a[0])) + sys.exit(1) + + if options.autodump is not None: + if os.path.exists(options.autodump): + print_out_str( + 'Looking for Ram dumps in {0}'.format(options.autodump)) + else: + print_out_str( + 'Path {0} does not exist for Ram dumps. Exiting...'.format(options.autodump)) + sys.exit(1) + + gdb_path = None + nm_path = None + + try: + import local_settings + gdb_path = local_settings.gdb_path + nm_path = local_settings.nm_path + except ImportError: + cross_compile = os.environ.get('CROSS_COMPILE') + if cross_compile is not None: + gdb_path = cross_compile+"gdb" + nm_path = cross_compile+"nm" + + if gdb_path is None or nm_path is None: + print_out_str("!!! Incorrect path for toolchain specified.") + print_out_str("!!! Please see the README for instructions on setting up local_settings.pyi or CROSS_COMPILE") + sys.exit(1) + + print_out_str("Using gdb path {0}".format(gdb_path)) + print_out_str("Using nm path {0}".format(nm_path)) + + if not os.path.exists(gdb_path): + print_out_str("!!! gdb_path {0} does not exist! Check your settings!".format(gdb_path)) + sys.exit(1) + + if not os.access(gdb_path, os.X_OK): + print_out_str("!!! No execute permissions on gdb path {0}".format(gdb_path)) + print_out_str("!!! Please check the path settings") + print_out_str("!!! If this tool is being run from a shared location, contact the maintainer") + sys.exit(1) + + if not os.path.exists(nm_path): + print_out_str("!!! nm_path {0} does not exist! Check your settings!".format(gdb_path)) + sys.exit(1) + + if not os.access(nm_path, os.X_OK): + print_out_str("!!! No execute permissions on nm path {0}".format(gdb_path)) + print_out_str("!!! Please check the path settings") + print_out_str("!!! If this tool is being run from a shared location, contact the maintainer") + sys.exit(1) + + dump = RamDump(options.vmlinux, nm_path, gdb_path, options.ram_addr, + options.autodump, options.phys_offset, options.outdir, + options.force_hardware, options.force_hardware_version) + + if not dump.print_command_line(): + print_out_str('!!! Error printing saved command line.') + print_out_str('!!! The vmlinux is probably wrong for the ramdumps') + print_out_str('!!! Exiting now...') + sys.exit(1) + + if options.qdss: + print_out_str('!!! --parse-qdss is now deprecated') + print_out_str( + '!!! Please just use --parse-debug-image to get QDSS information') + + if options.watchdog_time: + print_out_str('\n--------- watchdog time -------') + get_wdog_timing(dump) + print_out_str('---------- end watchdog time-----') + + for p in parser_util.get_parsers(): + # we called parser.add_option with dest=p.cls.__name__ above, + # so if the user passed that option then `options' will have a + # p.cls.__name__ attribute. + if getattr(options, p.cls.__name__) or (options.everything and not p.optional): + with print_out_section(p.cls.__name__): + p.cls(dump).parse() + + if options.t32launcher or options.everything: + dump.create_t32_launcher() diff --git a/linux-ramdump-parser-v2/rb_tree.py b/linux-ramdump-parser-v2/rb_tree.py new file mode 100644 index 0000000000000000000000000000000000000000..8a6732616b8faf37dbfb46c751457631c6ccd144 --- /dev/null +++ b/linux-ramdump-parser-v2/rb_tree.py @@ -0,0 +1,43 @@ +# Copyright (c) 2012-2013, 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. + +""" +struct rb_node +{ + unsigned long rb_parent_color; +#define RB_RED 0 +#define RB_BLACK 1 + struct rb_node *rb_right; + struct rb_node *rb_left; +} __attribute__((aligned(sizeof(long)))); +""" + + +class RbTreeWalker(object): + + def __init__(self, ram_dump): + self.ram_dump = ram_dump + self.right_offset = self.ram_dump.field_offset( + 'struct rb_node', 'rb_right') + self.left_offset = self.ram_dump.field_offset( + 'struct rb_node', 'rb_left') + + def walk(self, node, func): + if node != 0: + left_node_addr = node + self.left_offset + left_node = self.ram_dump.read_word(left_node_addr) + self.walk(left_node, func) + + func(node) + + right_node_addr = node + self.right_offset + right_node = self.ram_dump.read_word(right_node_addr) + self.walk(right_node, func) diff --git a/linux-ramdump-parser-v2/register.py b/linux-ramdump-parser-v2/register.py new file mode 100644 index 0000000000000000000000000000000000000000..5acd9f9ad95a91c53ae8b8a1f6952632295c5c92 --- /dev/null +++ b/linux-ramdump-parser-v2/register.py @@ -0,0 +1,102 @@ +# Copyright (c) 2013, 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 bitops + + +class Register(object): + + """Represents a register (or any general field partitioning of + bits). Provides easy read and write access to the fields in the + register by taking care of all bit-shifting automatically. Fields + can be defined at __init__ time using kwargs or can be added + dynamically with `add_field'. Fields are accessible as instance + attributes. + + For example: + + >>> abc = Register(0x42, stuff=(2, 0)) + >>> abc + value: 0x42 {stuff[2:0]=>0x2} + >>> hex(abc.value) + '0x42' + >>> hex(abc.stuff) + '0x2' + >>> abc.stuff = 1 + >>> hex(abc.value) + '0x41' + >>> abc.add_field("other", (8, 4)) + >>> hex(abc.other) + '0x4' + >>> abc.other = 0x3 + >>> hex(abc.value) + '0x31' + + You can also overlay fields on top of each other without problems: + + >>> abc.add_field("another_other", (8, 0)) + abc.another_other = 0x5 + >>> hex(abc.value) + '0x5' + + """ + + def __init__(self, value=0, **kwargs): + """Register constructor. + + kwargs should represent the fields in this object. Their + values should be 2-tuples of the form (msb, lsb). + + """ + + # All the object.__setattr__ stuff is to prevent us from going + # into our __setattr__ method here (which would try to access + # these again and would then recurse inifitely) + object.__setattr__(self, 'value', value) + object.__setattr__(self, '_regs', {}) + for (k, v) in kwargs.iteritems(): + self.add_field(k, v) + + def add_field(self, field, bitrange): + """Add field to Register. + + bitrange should be the same format as the kwargs in __init__ + (i.e. (msb, lsb)). + + """ + self._regs[field] = bitrange + + def __dir__(self): + return self.__dict__.keys() + self._regs.keys() + + def __getattr__(self, name): + if name not in self._regs: + raise AttributeError + msb, lsb = self._regs[name] + return bitops.bvalsel(msb, lsb, self.value) + + def __setattr__(self, name, newvalue): + if name not in self._regs: + raise AttributeError + msb, lsb = self._regs[name] + val = self.value & (~bitops.bm(msb, lsb)) + val |= newvalue << lsb + # can't access self.value directly since that would cause + # infinite recursion to __setattr__ + object.__setattr__(self, 'value', val) + + def __repr__(self): + ret = [] + for r in sorted(self._regs, key=self._regs.get, reverse=True): + msb, lsb = self._regs[r] + val = bitops.bvalsel(msb, lsb, self.value) + ret.append('%s[%d:%d]=>0x%0x' % (r, msb, lsb, val)) + return 'value: 0x%x {%s}' % (self.value, ', '.join(ret))