The Anatomy of an ESP32 Crash (and Why You Need File Logging)
When an ESP32 encounters a fatal error, it halts execution and prints a Guru Meditation Error to the serial monitor. In a bench environment, this is fine. In a remote deployment or enclosed IoT product, you cannot rely on a live serial connection. To effectively debug field failures, you must capture the ESP32 Arduino core dump backtrace to a file stored on local media (like an SD card or LittleFS) or a dedicated flash partition before the hardware watchdog resets the chip.
The direct answer: To save a backtrace to a file in the Arduino IDE, you must use the <esp_debug_helpers.h> library to iterate through the esp_backtrace_frame_t array and write the Program Counter (PC) addresses to an SD card during a software fault, or configure a dedicated coredump partition in your sdkconfig for bare-metal hardware panics.
ESP32 Exception Causes & Hex Codes
Understanding the exception code printed in the panic handler tells you exactly what went wrong. Here are the most common hardware exceptions that trigger a dump:
| Exception Name | Hex Code | Typical Root Cause | Recovery Possible? |
|---|---|---|---|
| LoadProhibited | 0x01 | Reading from an invalid/unmapped memory address (e.g., dangling pointer). | No (Hard Fault) |
| StoreProhibited | 0x02 | Writing to an invalid memory address (e.g., null pointer dereference). | No (Hard Fault) |
| Privileged | 0x04 | Attempting to execute a privileged CPU instruction in user mode. | No (Hard Fault) |
| IntegerDivideByZero | 0x06 | Dividing an integer by zero without a software check. | No (Hard Fault) |
| Alloca / Stack Overflow | N/A (SW) | Exceeding the FreeRTOS task stack limit, corrupting the stack canary. | Yes (via Hooks) |
Hardware Setup & Pin Mapping
For this guide, we are targeting the ESP32-S3 DevKitC-1 (N8R8) variant, which features 8MB of Flash and 8MB of Octal PSRAM. We will use a standard MicroSD card breakout module connected via hardware SPI to log the backtrace data.
Parts List
- MCU: ESP32-S3 DevKitC-1 (N8R8) - Ensure you select the "ESP32S3 Dev Module" in the Arduino IDE Boards Manager.
- Storage: MicroSD Card Breakout Module (3.3V logic compatible, e.g., Adafruit 254).
- Wiring: 24 AWG silicone jumper wires for SPI bus.
- Software: Arduino IDE 2.x with ESP32 Core v2.0.14 or v3.x installed.
SPI Pin Mapping Table
The ESP32-S3 allows flexible GPIO routing for SPI, but these are the default hardware SPI pins for the S3 DevKitC-1 that avoid conflicts with internal PSRAM routing:
| SD Card Breakout Pin | ESP32-S3 DevKitC-1 GPIO | Function | Notes |
|---|---|---|---|
| VCC | 3V3 | Power | Do NOT use 5V; S3 logic is strictly 3.3V. |
| GND | GND | Ground | Connect to any common ground rail. |
| MOSI | GPIO 11 | Master Out Slave In | Default SPI MOSI for ESP32-S3. |
| MISO | GPIO 13 | Master In Slave Out | Default SPI MISO for ESP32-S3. |
| SCK | GPIO 12 | Serial Clock | Default SPI CLK for ESP32-S3. |
| CS | GPIO 10 | Chip Select | Active LOW. Must be defined in code. |
The Code: Capturing and Writing the Backtrace
The following complete, compilable Arduino sketch initializes the SD card, sets up a deliberate software fault (a failed assertion), intercepts the backtrace using esp_backtrace_get(), and writes the raw Program Counter (PC) addresses to a text file on the SD card.
#include <Arduino.h>
#include <SD.h>
#include <SPI.h>
#include <esp_debug_helpers.h>
#include <esp_system.h>
// --- PIN DEFINITIONS (ESP32-S3 DevKitC-1) ---
#define SD_CS_PIN 10
#define SD_MOSI_PIN 11
#define SD_MISO_PIN 13
#define SD_SCK_PIN 12
#define TRIGGER_PIN 0 // BOOT button on DevKitC-1
// --- FUNCTION PROTOTYPES ---
void initSDCard();
void dumpBacktraceToSD();
void triggerSoftwareFault();
void setup() {
Serial.begin(115200);
delay(1000); // Allow serial monitor to connect
Serial.println("[BOOT] ESP32-S3 Backtrace Logger Initialized.");
pinMode(TRIGGER_PIN, INPUT_PULLUP);
initSDCard();
}
void loop() {
// Press the BOOT button (GPIO 0) to trigger a controlled software fault
if (digitalRead(TRIGGER_PIN) == LOW) {
delay(50); // Debounce
if (digitalRead(TRIGGER_PIN) == LOW) {
Serial.println("[FAULT] Triggering software assertion failure...");
triggerSoftwareFault();
}
}
delay(100);
}
void initSDCard() {
SPI.begin(SD_SCK_PIN, SD_MISO_PIN, SD_MOSI_PIN, SD_CS_PIN);
if (!SD.begin(SD_CS_PIN)) {
Serial.println("[ERROR] SD Card Mount Failed. Check wiring and FAT32 format.");
return;
}
uint8_t cardType = SD.cardType();
if (cardType == CARD_NONE) {
Serial.println("[ERROR] No SD card attached.");
return;
}
Serial.printf("[OK] SD Card Initialized. Type: %d, Size: %lluMB\n", cardType, SD.cardSize() / (1024 * 1024));
}
void dumpBacktraceToSD() {
esp_backtrace_frame_t frame;
// Initialize the backtrace iterator
esp_err_t ret = esp_backtrace_get_start(&(frame.pc), &(frame.sp), &(frame.next_pc));
if (ret != ESP_OK) {
Serial.println("[ERROR] Failed to start backtrace capture.");
return;
}
File logFile = SD.open("/crash_dump.txt", FILE_APPEND);
if (!logFile) {
Serial.println("[ERROR] Failed to open crash_dump.txt for writing.");
return;
}
logFile.println("--- ESP32 BACKTRACE DUMP ---");
logFile.printf("Timestamp: %lu\n", millis());
logFile.println("Backtrace (PC Addresses):");
// Write the first frame
logFile.printf("0x%08x\n", frame.pc);
Serial.printf("Backtrace: 0x%08x ", frame.pc);
// Iterate through the stack frames
while (esp_backtrace_get_next_frame(&frame) == ESP_OK) {
logFile.printf("0x%08x\n", frame.pc);
Serial.printf("0x%08x ", frame.pc);
}
logFile.println("\n--- END DUMP ---\n");
logFile.close();
Serial.println("\n[OK] Backtrace saved to /crash_dump.txt");
}
void triggerSoftwareFault() {
// This simulates a software-level failure (like a failed config check or API error)
// We capture the backtrace BEFORE calling abort() or resetting.
dumpBacktraceToSD();
// Force a restart after logging
Serial.println("[SYSTEM] Restarting in 2 seconds...");
delay(2000);
ESP.restart();
}
xTaskCreate) is large enough (minimum 4096 bytes recommended for logging operations) so the SD write operation doesn't trigger a secondary stack overflow.
Decoding the Backtrace File
The crash_dump.txt file on your SD card will contain a list of raw hex addresses (e.g., 0x42005A1C). To translate these into human-readable function names and line numbers, you need the xtensa-esp32s3-elf-addr2line tool included in the ESP32 Arduino toolchain.
- Locate the Tool: Navigate to your Arduino ESP32 tools directory. On Windows, this is typically
C:\Users\[User]\AppData\Local\Arduino15\packages\esp32\tools\xtensa-esp-elf-gcc\[version]\bin\. - Locate the ELF File: In the Arduino IDE, go to Sketch > Export Compiled Binary. This generates a
.elffile in your sketch'sbuildfolder. - Run the Decoder: Open your terminal and run the following command for each address in your dump file:
xtensa-esp32s3-elf-addr2line -e YourSketch.ino.elf -f -p -a -C 0x42005A1C
This will output the exact function name, source file, and line number where the fault originated. For automated decoding, refer to the official Espressif Core Dump Documentation, which details using the espcoredump.py script for flash-based dumps.
Troubleshooting: When the Dump Fails
If your ESP32 crashes but the SD card file remains empty, or you encounter the following exact error string in your serial monitor:
Guru Meditation Error: Core 1 panic'ed (StoreProhibited). Exception was unhandled.
Core 1 register dump:
PC : 0x42005a1c PS : 0x00060030 A0 : 0x82005a1c A1 : 0x3fcb1d50
Here are the first three things to check when your backtrace logging fails to execute:
- Interrupt Masking (The Hard Fault Trap): As noted earlier, a
StoreProhibited(0x02) error is a Level 3 hardware panic. The CPU instantly halts and masks interrupts. The SPI bus cannot clock data to the SD card without interrupts. Fix: You must use the ESP-IDF Core Dump to Flash partition method for hard faults, or use a secondary "watchdog" microcontroller (like an ATtiny85) on a secondary UART to log the serial output before the ESP32 resets. - Task Stack Overflow During Logging: If the crash was caused by a stack overflow, attempting to allocate memory for the SD file buffer (
File logFile = SD.open...) will trigger a secondary panic, aborting the dump. Fix: Pre-allocate a static buffer for the file name, or ensure the task running the logger has at least 8192 bytes of stack space. - SD Card SPI Bus Contention: If the crash occurred while another task was actively writing to the SD card or using the SPI bus, the bus mutex will be locked forever. The panic handler will deadlock waiting for the SPI lock. Fix: Use a dedicated SPI bus (SPI2_HOST) for the crash logger, separate from your main application peripherals.
How to Extend or Simplify the Build
To Simplify: If you don't want to wire an SD card, simplify the build by using a host-side Python script (like pyserial) to monitor the UART port and regex-match the Backtrace: 0x... string, saving it to a local .txt file on your PC. This is ideal for bench debugging.
To Extend: For production IoT deployments, extend the code by adding AES-256 encryption to the backtrace file before writing it to LittleFS, and implement an MQTT or HTTPS POST routine on the next boot cycle to upload the crash_dump.txt file to an AWS S3 bucket or Grafana Loki instance for centralized fleet monitoring. Check the Arduino ESP32 Core GitHub repository for the latest esp_http_client examples.






