Skip to content

Instantly share code, notes, and snippets.

@pschatzmann
Created January 25, 2023 14:31
Show Gist options
  • Save pschatzmann/84361b313baa4479c9cbf2e3cea4c49a to your computer and use it in GitHub Desktop.
Save pschatzmann/84361b313baa4479c9cbf2e3cea4c49a to your computer and use it in GitHub Desktop.
ESP32 Stacktrace
#!/usr/bin/env python
import argparse
import re
import os
import subprocess
import sys
from termcolor import colored
# update the following default values to what is relevant for your system
toolchain_default = "/home/pschatzmann/.arduino15/packages/esp32/tools/xtensa-esp32-elf-gcc/gcc8_4_0-esp-2021r2-patch5"
elf_default = "/home/pschatzmann/.var/app/cc.arduino.IDE2/cache"
class ESP32CrashParser(object):
def __init__(self, toolchain_path, elf_path):
self.toolchain_path = toolchain_path
self.gdb_path = os.path.join(toolchain_path, "bin", "xtensa-esp32-elf-gdb")
self.addr2line_path = os.path.join(toolchain_path, "bin", "xtensa-esp32-elf-addr2line")
if not os.path.exists(self.gdb_path):
raise Exception("GDB for ESP not found in {} - {} does not exist.\nUse --toolchain to point to "
"your toolchain folder.".format(self.toolchain_path, self.gdb_path))
if not os.path.exists(self.addr2line_path):
raise Exception("addr2line for ESP not found in {} - {} does not exist.\nUse --toolchain to point to "
"your toolchain folder.".format(self.toolchain_path, self.addr2line_path))
self.elf_path = elf_path
# if it is not a elf file we search the path for the newest elf file
if not elf_path.endswith(".elf"):
self.elf_path = find_newest_elf(elf_path)
if not os.path.exists(self.elf_path):
raise Exception("ELF file not found: '{}'".format(self.elf_path))
def print_with_colors(self, text):
method_start = text.find(":")+1
method_end = text.find(" at ")
line_start = text.rindex(":")+1
address = text[2:method_start]
method = text[method_start:method_end]
middle = text[method_end:line_start]
line_nr = text[line_start:]
highlight_color = 'light_yellow'
print(" ", address, colored(method, highlight_color), middle, 'line', colored(line_nr, highlight_color))
def parse_text(self, text):
m = re.search('Backtrace: (.*)', text)
if m:
print();
print("Stack trace:")
print();
for l in self.parse_stack(m.group(1)):
self.print_with_colors(str(l))
print();
else:
print ("No stack trace found.")
'''
Decode one stack or backtrace.
See: https://github.com/me-no-dev/EspExceptionDecoder/blob/master/src/EspExceptionDecoder.java#L402
'''
def parse_stack(self, text):
r = re.compile('40[0-2][0-9a-fA-F]{5}')
m = r.findall(text)
return self.decode_function_addresses(m)
def decode_function_address(self, address):
args = [self.addr2line_path, "-e", self.elf_path, "-aipfC", address]
return subprocess.check_output(args).strip()
def decode_function_addresses(self, addresses):
out = []
for a in addresses:
out.append(self.decode_function_address(a))
return out
'''
GDB Should produce line number: https://github.com/me-no-dev/EspExceptionDecoder/commit/a78672da204151cc93979a96ed9f89139a73893f
However it does not produce anything for me. So not using it for now.
'''
def decode_function_addresses_with_gdb(self, addresses):
args = [self.gdb_path, "--batch"]
# Disable user config file which might interfere here
args.extend(["-iex", "set auto-load local-gdbinit off"])
args.append(self.elf_path)
args.extend(["-ex", "set listsize 1"])
for address in addresses:
args.append("-ex")
args.append("l *0x{}".format(address))
args.extend(["-ex", "q"])
print ("Running: {}".format(args))
out = subprocess.check_output(args)
print (out)
@staticmethod
def find_newest_elf(start_path):
if start_path.endswith(".elf"):
return start_path
latest = 0
result = ""
for (dir_path, dir_names, file_names) in os.walk(start_path):
for file_name in file_names:
if file_name.endswith(".elf"):
file_path = os.path.join(dir_path, file_name)
time = os.path.getmtime(file_path)
if time > latest:
latest = time
result = file_path
if (result==""):
print("No elf file found in ", start_path)
else:
print("elf =",result)
return result
def main():
print()
parser = argparse.ArgumentParser()
parser.add_argument("--toolchain", help="Path to the Xtensa toolchain",
default=os.path.join(os.environ.get("HOME"), toolchain_default))
elf_file = ESP32CrashParser.find_newest_elf(elf_default)
parser.add_argument("--elf", help="Path to the ELF file of the firmware", default=elf_file)
parser.add_argument("input")
args = parser.parse_args()
crash_parser = ESP32CrashParser(args.toolchain, args.elf)
crash_parser.parse_text(args.input)
if __name__ == '__main__':
main()
@pschatzmann
Copy link
Author

pschatzmann commented Jan 25, 2023

When your Arudino sketch is crashing it produces something as follows

13:45:41.992 -> 
13:45:41.992 -> Backtrace: 0x40083585:0x3ffb2690 0x400892ad:0x3ffb26b0 0x4008e2f1:0x3ffb26d0 0x400d1442:0x3ffb2800 0x400d2be1:0x3ffb2820
13:45:41.992 -> 
13:45:41.992 -> 
13:45:41.992 -> 
13:45:41.992 -> 
13:45:41.992 -> ELF file SHA256: 9868280367130d7a

It is vital to be able to analyse this backtrace and translate it to methods and file names. In Arduino we could use the EspExceptionDecoder to do this.

Unfortunately this is not supported by Arduino 2.0! No problem - I thought, so I just continue to use the old Arduino version. But when I moved my work on Linux, this functionality stopped to work as well.

Now I was looking for some easy to use work-around. It turns out we can do this with a python script. I was not very happy with it because of all the necessary parameters so I decided create my own version and to add the following improvements:

  • Provide a global variable to set the toolchain_default path
  • Provide a global variable to set the elf_default path
  • Determine the elf file automatically by using the most recent elf file in the path defined above

The values can be determined from the last entry in the Log when you compile your Sketch in Arduino

I also did not like that the backtrace needed to be specified in a separate file, so I replaced it that we can specify this directly on the command line.

In order to install this file

1.Download it to the file esp32-stacktrace
2.Update the global default variables
2.Make it executable (e.g. with chmod +x esp32-esp32-stacktrace
3. Move it to a location on the search path (e.g. usr/bin)

esp32-stacktrace "0x40083585:0x3ffb2690 0x400892ad:0x3ffb26b0 0x4008e2f1:0x3ffb26d0 0x400d1442:0x3ffb2800 0x400d2be1:0x3ffb2820"

This gives the following result

elf = /home/pschatzmann/.var/app/cc.arduino.IDE2/cache/arduino-sketch-93C5D26949BAF1CE46343309937C5D93/sketch_jan25a.ino.elf

Stack trace:

     0x40083585:  panic_abort  at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/esp_system/panic.c: line 402'
     0x400892ad:  esp_system_abort  at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/esp_system/esp_system.c: line 128'
     0x4008e2f1:  __assert_func  at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/newlib/assert.c: line 85'
     0x400d1442:  loop()  at /home/pschatzmann/.var/app/cc.arduino.IDE2/cache/.arduinoIDE-unsaved2023025-30-ox4b5b.v436/sketch_jan25a/sketch_jan25a.ino: line 10'
     0x400d2be1:  loopTask(void*)  at /home/pschatzmann/.arduino15/packages/esp32/hardware/esp32/2.0.6/cores/esp32/main.cpp: line 50'

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment