Skip to content

Commit

Permalink
feat: make esp32_exception_decoder more generic
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
Tasssadar committed Apr 16, 2024
1 parent 48de235 commit 39da575
Showing 1 changed file with 40 additions and 11 deletions.
51 changes: 40 additions & 11 deletions monitor/filter_exception_decoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ""
Expand All @@ -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(
Expand Down Expand Up @@ -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:
Expand Down

0 comments on commit 39da575

Please sign in to comment.