From f20621a3b322b702864a1a1bbcfb39898b0d1f0d Mon Sep 17 00:00:00 2001 From: Paul Date: Wed, 19 Jun 2019 18:35:46 +0200 Subject: [PATCH] added stackdump decoder for linux --- scripts/analyze_stackdmp.py | 4 + scripts/decoder_linux.py | 307 ++++++++++++++++++++++++++++++++++++ 2 files changed, 311 insertions(+) create mode 100644 scripts/decoder_linux.py diff --git a/scripts/analyze_stackdmp.py b/scripts/analyze_stackdmp.py index f8ff39353..17a63e218 100644 --- a/scripts/analyze_stackdmp.py +++ b/scripts/analyze_stackdmp.py @@ -18,3 +18,7 @@ import os # <<[0-9]*)\\):$") +COUNTER_REGEX = re.compile('^epc1=(?P0x[0-9a-f]+) epc2=(?P0x[0-9a-f]+) epc3=(?P0x[0-9a-f]+) ' + 'excvaddr=(?P0x[0-9a-f]+) depc=(?P0x[0-9a-f]+)$') +CTX_REGEX = re.compile("^ctx: (?P.+)$") +POINTER_REGEX = re.compile('^sp: (?P[0-9a-f]+) end: (?P[0-9a-f]+) offset: (?P[0-9a-f]+)$') +STACK_BEGIN = '>>>stack>>>' +STACK_END = '<<[0-9a-f]+):\W+(?P[0-9a-f]+) (?P[0-9a-f]+) (?P[0-9a-f]+) (?P[0-9a-f]+)(\W.*)?$') + +StackLine = namedtuple("StackLine", ["offset", "content"]) + + +class ExceptionDataParser(object): + def __init__(self): + self.exception = None + + self.epc1 = None + self.epc2 = None + self.epc3 = None + self.excvaddr = None + self.depc = None + + self.ctx = None + + self.sp = None + self.end = None + self.offset = None + + self.stack = [] + + def _parse_exception(self, line): + match = EXCEPTION_REGEX.match(line) + if match is not None: + self.exception = int(match.group('exc')) + return self._parse_counters + return self._parse_exception + + def _parse_counters(self, line): + match = COUNTER_REGEX.match(line) + if match is not None: + self.epc1 = match.group("epc1") + self.epc2 = match.group("epc2") + self.epc3 = match.group("epc3") + self.excvaddr = match.group("excvaddr") + self.depc = match.group("depc") + return self._parse_ctx + return self._parse_counters + + def _parse_ctx(self, line): + match = CTX_REGEX.match(line) + if match is not None: + self.ctx = match.group("ctx") + return self._parse_pointers + return self._parse_ctx + + def _parse_pointers(self, line): + match = POINTER_REGEX.match(line) + if match is not None: + self.sp = match.group("sp") + self.end = match.group("end") + self.offset = match.group("offset") + return self._parse_stack_begin + return self._parse_pointers + + def _parse_stack_begin(self, line): + if line == STACK_BEGIN: + return self._parse_stack_line + return self._parse_stack_begin + + def _parse_stack_line(self, line): + if line != STACK_END: + match = STACK_REGEX.match(line) + if match is not None: + self.stack.append(StackLine(offset=match.group("off"), + content=(match.group("c1"), match.group("c2"), match.group("c3"), + match.group("c4")))) + return self._parse_stack_line + return None + + def parse_file(self, file, stack_only=False): + func = self._parse_exception + if stack_only: + func = self._parse_stack_begin + + for line in file: + func = func(line.strip()) + if func is None: + break + + if func is not None: + print("ERROR: Parser not complete!") + sys.exit(1) + + +class AddressResolver(object): + def __init__(self, tool_path, elf_path): + self._tool = tool_path + self._elf = elf_path + self._address_map = {} + + def _lookup(self, addresses): + cmd = [self._tool, "-aipfC", "-e", self._elf] + [addr for addr in addresses if addr is not None] + + if sys.version_info[0] < 3: + output = subprocess.check_output(cmd) + else: + output = subprocess.check_output(cmd, encoding="utf-8") + + line_regex = re.compile("^(?P[0-9a-fx]+): (?P.+)$") + + last = None + for line in output.splitlines(): + line = line.strip() + match = line_regex.match(line) + + if match is None: + if last is not None and line.startswith('(inlined by)'): + line = line [12:].strip() + self._address_map[last] += ("\n \-> inlined by: " + line) + continue + + if match.group("result") == '?? ??:0': + continue + + self._address_map[match.group("addr")] = match.group("result") + last = match.group("addr") + + def fill(self, parser): + addresses = [parser.epc1, parser.epc2, parser.epc3, parser.excvaddr, parser.sp, parser.end, parser.offset] + for line in parser.stack: + addresses.extend(line.content) + + self._lookup(addresses) + + def _sanitize_addr(self, addr): + if addr.startswith("0x"): + addr = addr[2:] + + fill = "0" * (8 - len(addr)) + return "0x" + fill + addr + + def resolve_addr(self, addr): + out = self._sanitize_addr(addr) + + if out in self._address_map: + out += ": " + self._address_map[out] + + return out + + def resolve_stack_addr(self, addr, full=True): + addr = self._sanitize_addr(addr) + if addr in self._address_map: + return addr + ": " + self._address_map[addr] + + if full: + return "[DATA (0x" + addr + ")]" + + return None + + +def print_addr(name, value, resolver): + print("{}:{} {}".format(name, " " * (8 - len(name)), resolver.resolve_addr(value))) + + +def print_stack_full(lines, resolver): + print("stack:") + for line in lines: + print(line.offset + ":") + for content in line.content: + print(" " + resolver.resolve_stack_addr(content)) + + +def print_stack(lines, resolver): + print("stack:") + for line in lines: + for content in line.content: + out = resolver.resolve_stack_addr(content, full=False) + if out is None: + continue + print(out) + + +def print_result(parser, resolver, full=True, stack_only=False): + if not stack_only: + print('Exception: {} ({})'.format(parser.exception, EXCEPTIONS[parser.exception])) + + print("") + print_addr("epc1", parser.epc1, resolver) + print_addr("epc2", parser.epc2, resolver) + print_addr("epc3", parser.epc3, resolver) + print_addr("excvaddr", parser.excvaddr, resolver) + print_addr("depc", parser.depc, resolver) + + print("") + print("ctx: " + parser.ctx) + + print("") + print_addr("sp", parser.sp, resolver) + print_addr("end", parser.end, resolver) + print_addr("offset", parser.offset, resolver) + + print("") + if full: + print_stack_full(parser.stack, resolver) + else: + print_stack(parser.stack, resolver) + + +def parse_args(): + parser = argparse.ArgumentParser(description="decode ESP Stacktraces.") + + parser.add_argument("-p", "--platform", help="The platform to decode from", choices=PLATFORMS.keys(), + default="ESP8266") + parser.add_argument("-t", "--tool", help="Path to the xtensa toolchain", + default="~/.platformio/packages/toolchain-xtensa/") + parser.add_argument("-e", "--elf", help="path to elf file", required=True) + parser.add_argument("-f", "--full", help="Print full stack dump", action="store_true") + parser.add_argument("-s", "--stack_only", help="Decode only a stractrace", action="store_true") + parser.add_argument("file", help="The file to read the exception data from ('-' for STDIN)", default="-") + + return parser.parse_args() + + +if __name__ == "__main__": + args = parse_args() + + if args.file == "-": + file = sys.stdin + else: + if not os.path.exists(args.file): + print("ERROR: file " + args.file + " not found") + sys.exit(1) + file = open(args.file, "r") + + addr2line = os.path.join(os.path.abspath(os.path.expanduser(args.tool)), + "bin/xtensa-" + PLATFORMS[args.platform] + "-elf-addr2line") + if not os.path.exists(addr2line): + print("ERROR: addr2line not found (" + addr2line + ")") + + elf_file = os.path.abspath(os.path.expanduser(args.elf)) + if not os.path.exists(elf_file): + print("ERROR: elf file not found (" + elf_file + ")") + + parser = ExceptionDataParser() + resolver = AddressResolver(addr2line, elf_file) + + parser.parse_file(file, args.stack_only) + resolver.fill(parser) + + print_result(parser, resolver, args.full, args.stack_only)