From 39da57546b83206e95c5ffaead8fd862b595ebbc Mon Sep 17 00:00:00 2001 From: Vojtech Bocek Date: Tue, 16 Apr 2024 20:15:16 +0200 Subject: [PATCH] feat: make esp32_exception_decoder more generic * The official idf.py monitor just tries to decode any 4 byte hex number. This variant safely decodes backtraces as well as heap tracing dumps. --- monitor/filter_exception_decoder.py | 51 ++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/monitor/filter_exception_decoder.py b/monitor/filter_exception_decoder.py index 8d89022b5..d0f4a39b3 100644 --- a/monitor/filter_exception_decoder.py +++ b/monitor/filter_exception_decoder.py @@ -32,8 +32,9 @@ class Esp32ExceptionDecoder(DeviceMonitorFilterBase): NAME = "esp32_exception_decoder" - BACKTRACE_PATTERN = re.compile(r"^Backtrace:(((\s?0x[0-9a-fA-F]{8}:0x[0-9a-fA-F]{8}))+)") - BACKTRACE_ADDRESS_PATTERN = re.compile(r'0x[0-9a-fA-F]{8}:0x[0-9a-fA-F]{8}') + ADDR_PATTERN = re.compile(r"((?:0x[0-9a-fA-F]{8}[: ]?)+)\s?$") + ADDR_SPLIT = re.compile(r"[ :]") + PREFIX_RE = re.compile(r"^ *") def __call__(self): self.buffer = "" @@ -57,6 +58,7 @@ def setup_paths(self): self.project_dir = os.path.abspath(self.project_dir) try: data = load_build_metadata(self.project_dir, self.environment, cache=True) + self.firmware_path = data["prog_path"] if not os.path.isfile(self.firmware_path): sys.stderr.write( @@ -100,38 +102,65 @@ def rx(self, text): self.buffer = "" last = idx + 1 - m = self.BACKTRACE_PATTERN.match(line) + m = self.ADDR_PATTERN.search(line) if m is None: continue - trace = self.get_backtrace(m) - if len(trace) != "": + trace = self.build_backtrace(line, m.group(1)) + if trace: text = text[: idx + 1] + trace + text[idx + 1 :] last += len(trace) return text - def get_backtrace(self, match): - trace = "\n" + def is_address_ignored(self, address): + return address in ("", "0x00000000") + + def filter_addresses(self, adresses_str): + addresses = self.ADDR_SPLIT.split(adresses_str) + size = len(addresses) + while size > 1 and self.is_address_ignored(addresses[size-1]): + size -= 1 + return addresses[:size] + + def build_backtrace(self, line, address_match): + addresses = self.filter_addresses(address_match) + if not addresses: + return "" + + prefix_match = self.PREFIX_RE.match(line) + prefix = prefix_match.group(0) if prefix_match is not None else "" + + trace = "" enc = "mbcs" if IS_WINDOWS else "utf-8" args = [self.addr2line_path, u"-fipC", u"-e", self.firmware_path] try: - for i, addr in enumerate(self.BACKTRACE_ADDRESS_PATTERN.findall(match.group(1))): + i = 0 + for addr in addresses: output = ( subprocess.check_output(args + [addr]) .decode(enc) .strip() ) + + # newlines happen with inlined methods output = output.replace( "\n", "\n " - ) # newlines happen with inlined methods + ) + + # throw out addresses not from ELF + if output == "?? ??:0": + continue + output = self.strip_project_dir(output) - trace += " #%-2d %s in %s\n" % (i, addr, output) + trace += "%s #%-2d %s in %s\n" % (prefix, i, addr, output) + i += 1 except subprocess.CalledProcessError as e: sys.stderr.write( "%s: failed to call %s: %s\n" % (self.__class__.__name__, self.addr2line_path, e) ) - return trace + + return trace + "\n" if trace else "" def strip_project_dir(self, trace): while True: