diff --git a/linux-ramdump-parser-v2/README b/linux-ramdump-parser-v2/README index ab3704b5899d35f2469cbba11c2382d1408154cf..2b9f5da8ca3be5cc0383ad2a97d717252f42469e 100644 --- a/linux-ramdump-parser-v2/README +++ b/linux-ramdump-parser-v2/README @@ -40,6 +40,10 @@ linux-parser-output.txt is used --stdout : Write to stdout instead of the out-file. This overrides any --out-file given. +--qtf : Use QTF tool to parse and save QDSS trace data + +--qtf-path <path> : QTF tool executable + The list of features parsed is constantly growing. Please use --help option to see the full list of features that can be parsed. @@ -86,6 +90,7 @@ gdb_path - absolute path to the gdb tool for the ramdumps nm_path - absolute path to the gdb tool for the ramdumps gdb64_path - absolute path to the 64-bit gdb tool for the ramdumps nm64_path - absolute path to the 64-bit nm tool for the ramdumps +qtf_path - absolute path to qtf tool executable 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/debug_image_v2.py b/linux-ramdump-parser-v2/debug_image_v2.py index 04d72c2c7c10063450613b5a69fce4b12ad43cc6..78fcae803b6f5645f05d07d2e59097fff71d1970 100644 --- a/linux-ramdump-parser-v2/debug_image_v2.py +++ b/linux-ramdump-parser-v2/debug_image_v2.py @@ -10,6 +10,12 @@ # GNU General Public License for more details. import struct +import linux_list as llist +import re +import shutil +import os +import platform +import subprocess from print_out import print_out_str from qdss import QDSSDump @@ -85,6 +91,130 @@ class DebugImage_v2(): else: setattr(self.qdss, qdss_tag_to_field_name[client_name], start) + def ftrace_field_func(self, common_list, ram_dump): + name_offset = ram_dump.field_offset('struct ftrace_event_field', 'name') + type_offset = ram_dump.field_offset('struct ftrace_event_field', 'type') + filter_type_offset = ram_dump.field_offset('struct ftrace_event_field', 'filter_type') + field_offset = ram_dump.field_offset('struct ftrace_event_field', 'offset') + size_offset = ram_dump.field_offset('struct ftrace_event_field', 'size') + signed_offset = ram_dump.field_offset('struct ftrace_event_field', 'is_signed') + + name = ram_dump.read_word(common_list + name_offset) + field_name = ram_dump.read_cstring(name, 256) + type_name = ram_dump.read_word(common_list + type_offset) + type_str = ram_dump.read_cstring(type_name, 256) + offset = ram_dump.read_u32(common_list + field_offset) + size = ram_dump.read_u32(common_list + size_offset) + signed = ram_dump.read_u32(common_list + signed_offset) + + if re.match('(.*)\[(.*)', type_str) and not(re.match('__data_loc', type_str)): + s = re.split('\[', type_str) + s[1] = '[' + s[1] + self.formats_out.write("\tfield:{0} {1}{2};\toffset:{3};\tsize:{4};\tsigned:{5};\n".format(s[0], field_name, s[1], offset, size, signed)) + else: + self.formats_out.write("\tfield:{0} {1};\toffset:{2};\tsize:{3};\tsigned:{4};\n".format(type_str, field_name, offset, size, signed)) + + def ftrace_events_func(self, ftrace_list, ram_dump): + name_offset = ram_dump.field_offset('struct ftrace_event_call', 'name') + event_offset = ram_dump.field_offset('struct ftrace_event_call', 'event') + fmt_offset = ram_dump.field_offset('struct ftrace_event_call', 'print_fmt') + class_offset = ram_dump.field_offset('struct ftrace_event_call', 'class') + type_offset = ram_dump.field_offset('struct trace_event', 'type') + fields_offset = ram_dump.field_offset('struct ftrace_event_class', 'fields') + common_field_list = ram_dump.addr_lookup('ftrace_common_fields') + field_next_offset = ram_dump.field_offset('struct ftrace_event_field', 'link') + + name = ram_dump.read_word(ftrace_list + name_offset) + name_str = ram_dump.read_cstring(name, 512) + event_id = ram_dump.read_word(ftrace_list + event_offset + type_offset) + fmt = ram_dump.read_word(ftrace_list + fmt_offset) + fmt_str = ram_dump.read_cstring(fmt, 2048) + + self.formats_out.write("name: {0}\n".format(name_str)) + self.formats_out.write("ID: {0}\n".format(event_id)) + self.formats_out.write("format:\n") + + list_walker = llist.ListWalker(ram_dump, common_field_list, field_next_offset) + list_walker.walk_prev(common_field_list, self.ftrace_field_func, ram_dump) + self.formats_out.write("\n") + + event_class = ram_dump.read_word(ftrace_list + class_offset) + field_list = event_class + fields_offset + list_walker = llist.ListWalker(ram_dump, field_list, field_next_offset) + list_walker.walk_prev(field_list, self.ftrace_field_func, ram_dump) + self.formats_out.write("\n") + self.formats_out.write("print fmt: {0}\n".format(fmt_str)) + + def collect_ftrace_format(self, ram_dump): + formats = os.path.join(self.qtf_dir, 'map_files\\formats.txt') + formats_out = ram_dump.open_file(formats) + self.formats_out = formats_out + + ftrace_events_list = ram_dump.addr_lookup('ftrace_events') + next_offset = ram_dump.field_offset('struct ftrace_event_call', 'list') + list_walker = llist.ListWalker(ram_dump, ftrace_events_list, next_offset) + list_walker.walk_prev(ftrace_events_list, self.ftrace_events_func, ram_dump) + + self.formats_out.close + + def parse_qtf(self, ram_dump): + if platform.system() != 'Windows': + return + + qtf_path = ram_dump.qtf_path + if qtf_path is None: + try: + import local_settings + try: + qtf_path = local_settings.qtf_path + except AttributeError as e: + print_out_str("attribute qtf_path in local_settings.py looks bogus. Please see README.txt") + print_out_str("Full message: %s" % e.message) + return + except ImportError: + print_out_str("missing qtf_path local_settings.") + print_out_str("Please see the README for instructions on setting up local_settings.py") + return + + if qtf_path is None: + print_out_str("!!! Incorrect path for qtf specified.") + print_out_str("!!! Please see the README for instructions on setting up local_settings.py") + return + + if not os.path.exists(qtf_path): + print_out_str("!!! qtf_path {0} does not exist! Check your settings!".format(qtf_path)) + return + + if not os.access(qtf_path, os.X_OK): + print_out_str("!!! No execute permissions on qtf path {0}".format(qtf_path)) + return + + if os.path.getsize('tmc-etf.bin') > 0: + trace_file = 'tmc-etf.bin' + elif os.path.getsize('tmc-etr.bin') > 0: + trace_file = 'tmc-etr.bin' + else: + return + + port = 12345 + qtf_dir = 'qtf' + workspace = os.path.join(qtf_dir, 'qtf.workspace') + qtf_out = 'qtf.txt' + chipset = 'msm' + str(ram_dump.hw_id) + hlos = 'LA' + + p = subprocess.Popen([qtf_path, '-s', '{0}'.format(port)]) + subprocess.call('{0} -c {1} new workspace {2} {3} {4}'.format(qtf_path, port, qtf_dir, chipset, hlos)) + + self.qtf_dir = qtf_dir + self.collect_ftrace_format(ram_dump) + + subprocess.call('{0} -c {1} open workspace {2}'.format(qtf_path, port, workspace)) + subprocess.call('{0} -c {1} open bin {2}'.format(qtf_path, port, trace_file)) + subprocess.call('{0} -c {1} stream trace table {2}'.format(qtf_path, port, qtf_out)) + subprocess.call('{0} -c {1} exit'.format(qtf_path, port)) + p.communicate('quit') + def parse_dump_v2(self, ram_dump): self.dump_type_lookup_table = ram_dump.gdbmi.get_enum_lookup_table( 'msm_dump_type', 2) @@ -241,3 +371,5 @@ class DebugImage_v2(): client_id, ram_dump) self.qdss.dump_all(ram_dump) + if ram_dump.qtf: + self.parse_qtf(ram_dump) diff --git a/linux-ramdump-parser-v2/linux_list.py b/linux-ramdump-parser-v2/linux_list.py index e1637bed6268c136dbc2b1e0a38669ef605c31c0..119b351d8cde83aee286dcd5b87a371e9b3cc556 100644 --- a/linux-ramdump-parser-v2/linux_list.py +++ b/linux-ramdump-parser-v2/linux_list.py @@ -58,3 +58,30 @@ class ListWalker(object): break node_addr = next_node self.seen_nodes.append(node_addr) + + def walk_prev(self, node_addr, func, *args): + """Walk the linked list starting at `node_addr' previous node and traverse the list in + reverse order, calling `func' on each node. `func' will be passed the current node and *args, + if given. + + """ + node_addr = self.ram_dump.read_word(node_addr + self.ram_dump.field_offset('struct list_head', 'prev')) + while True: + if node_addr == 0: + break + + funcargs = [node_addr - self.list_elem_offset] + list(args) + func(*funcargs) + + prev_node_addr = node_addr + self.ram_dump.field_offset('struct list_head', 'prev') + prev_node = self.ram_dump.read_word(prev_node_addr) + + if prev_node == self.last_node: + break + + if prev_node in self.seen_nodes: + print_out_str( + '[!] WARNING: Cycle found in attach list. List is corrupted!') + break + node_addr = prev_node + self.seen_nodes.append(node_addr) diff --git a/linux-ramdump-parser-v2/ramdump.py b/linux-ramdump-parser-v2/ramdump.py index 9eec2f8f5515f7108f1f77628b7f7612df746a66..0ba4e820b610b9ee7dcd72c02cbc8ec89ea1caca 100755 --- a/linux-ramdump-parser-v2/ramdump.py +++ b/linux-ramdump-parser-v2/ramdump.py @@ -436,7 +436,7 @@ class RamDump(): if urc < 0: break - def __init__(self, vmlinux_path, nm_path, gdb_path, objdump_path, ebi, file_path, phys_offset, outdir, hw_id=None, hw_version=None, arm64=False, page_offset=None): + def __init__(self, vmlinux_path, nm_path, gdb_path, objdump_path, ebi, file_path, phys_offset, outdir, qtf_path, hw_id=None, hw_version=None, arm64=False, page_offset=None, qtf=False): self.ebi_files = [] self.phys_offset = None self.tz_start = 0 @@ -456,6 +456,8 @@ class RamDump(): self.arm64 = arm64 self.page_offset = 0xc0000000 self.thread_size = 8192 + self.qtf_path = qtf_path + self.qtf = qtf if ebi is not None: # TODO sanity check to make sure the memory regions don't overlap for file_path, start, end in ebi: diff --git a/linux-ramdump-parser-v2/ramparse.py b/linux-ramdump-parser-v2/ramparse.py index 5c221d521c18e17c5b47eda276a0e798db597f03..9920ee95eae1e6bac378cda8594c94f770966084 100755 --- a/linux-ramdump-parser-v2/ramparse.py +++ b/linux-ramdump-parser-v2/ramparse.py @@ -122,6 +122,10 @@ if __name__ == '__main__': help='Run an interactive python interpreter with the ramdump loaded') parser.add_option('', '--classic-shell', action='store_true', help='Like --shell, but forces the use of the classic python shell, even if ipython is installed') + parser.add_option('', '--qtf', action='store_true', dest='qtf', + help='Use QTF tool to parse and save QDSS trace data') + parser.add_option('', '--qtf-path', dest='qtf_path', + help='QTF tool executable') for p in parser_util.get_parsers(): parser.add_option(p.shortopt or '', @@ -263,11 +267,14 @@ if __name__ == '__main__': print_out_str("!!! If this tool is being run from a shared location, contact the maintainer") sys.exit(1) + if options.everything: + options.qtf = True + dump = RamDump(options.vmlinux, nm_path, gdb_path, objdump_path, options.ram_addr, - options.autodump, options.phys_offset, options.outdir, + options.autodump, options.phys_offset, options.outdir, options.qtf_path, options.force_hardware, options.force_hardware_version, arm64=options.arm64, - page_offset=options.page_offset) + page_offset=options.page_offset, qtf=options.qtf) if options.shell or options.classic_shell: print("Entering interactive shell mode.")