When an ESP32 hits a fatal exception in the field or on the bench, it reboots instantly, taking the contents of its RAM with it. To capture the exact state of the CPU registers, stack, and heap at the moment of failure, you need a post-mortem coredump. To enable Arduino ESP32 coredump, navigate to Tools > Core Dump to > Flash in the Arduino IDE, ensure your Partition Scheme includes a dedicated coredump partition, and set Tools > Erase All Flash Before Upload to 'Enabled' for the initial flash. Once a crash occurs, the ESP32 writes the RAM snapshot to flash before rebooting, allowing you to extract and decode it using Espressif's espcoredump.py utility.
ESP32 Coredump Partition Schemes & Memory Overhead
The Arduino IDE abstracts the ESP-IDF partition table, but coredumps require a dedicated physical region in the SPI flash. If your selected partition scheme lacks a coredump partition, the firmware will compile, but the crash dump will silently fail to save. Below is the data-dense breakdown of standard Arduino ESP32 partition schemes and their coredump viability.
| Partition Scheme (Arduino IDE) | App Partition Size | Coredump Partition Size | SPIFFS / FAT Size | Best Use Case & Coredump Viability |
|---|---|---|---|---|
| Default 4MB with spiffs | 1.2 MB | 64 KB (0x10000) | 1.4 MB | Standard IoT projects. 64KB is sufficient for basic stack/core registers, but may truncate large heap dumps. |
| Minimal SPIFFS | 1.9 MB | 64 KB (0x10000) | 190 KB | Code-heavy projects with OTA. Excellent for coredumps; leaves ample room for application logic. |
| Huge APP (3MB No OTA/SPFFS) | 3.0 MB | 64 KB (0x10000) | None | Massive firmware (e.g., heavy audio/DSP). Coredump works, but no filesystem for logging. |
| Custom CSV (User Defined) | Variable | 256 KB (0x40000) | Variable | Production debugging. 256KB guarantees full heap + stack capture without truncation. |
For deep debugging, I highly recommend generating a Custom CSV partition table and allocating at least 256KB to the coredump partition. A standard stack trace takes roughly 8-12KB, but if you enable heap dumping in the ESP-IDF menuconfig, the payload can easily exceed 64KB.
Hardware Build: The Deliberate Crash Rig
To test your coredump pipeline, we need a reproducible hardware trigger. This rig uses a simple tactile switch to force a memory violation on demand.
Parts List
- MCU: ESP32-WROOM-32 DevKit V1 (30-pin variant, CP2102 or CH340 USB-UART bridge)
- Switch: 6x6mm Tactile Pushbutton (Normally Open)
- Resistor: 10kΩ (Optional, as we will use internal pull-ups, but good for breadboard stability)
- Decoupling: 100µF Electrolytic Capacitor (16V rated)
- Wiring: 22 AWG solid core jumper wires, half-size breadboard
Pin Mapping Table
| ESP32 GPIO | Component | Function / Notes |
|---|---|---|
| GPIO 0 | Tactile Switch (Leg 1) | Crash Trigger. Switch Leg 2 to GND. Uses internal INPUT_PULLUP. |
| GPIO 2 | Onboard LED | Status indicator. Blinks in loop, goes solid on crash. |
| 5V / VIN | Capacitor (+) | Power decoupling to prevent brownout during flash write. |
| GND | Capacitor (-) / Switch | Common ground reference. |
Compilable Crash-Test Firmware
This code targets the ESP32 Dev Module board variant (specifically the ESP32-WROOM-32). It runs a standard loop, blinking the onboard LED. When you press the button on GPIO 0, it deliberately dereferences a null pointer, triggering a fatal CPU exception. The ESP32 Arduino Core intercepts this, writes the coredump to the flash partition, and reboots.
/*
* ESP32 Coredump Crash Test Rig
* Target Board: ESP32 Dev Module (ESP32-WROOM-32)
* Arduino IDE Settings:
* - Core Debug Level: Verbose
* - Core Dump to: Flash
* - Partition Scheme: Default 4MB with spiffs
*/
#include
#include
// --- PIN DEFINITIONS ---
#define PIN_CRASH_BUTTON 0 // Tactile switch to GND
#define PIN_STATUS_LED 2 // Onboard LED on most DevKit V1 boards
// Function prototypes
void triggerNullPointerCrash();
void triggerStackOverflowCrash(int depth);
void setup() {
// Initialize Serial with error handling
Serial.begin(115200);
unsigned long timeout = millis();
while (!Serial && (millis() - timeout < 3000)) {
delay(10); // Wait for serial monitor, but don't block forever
}
Serial.println("\n[BOOT] ESP32 Coredump Test Rig Initialized.");
Serial.println("[BOOT] Press the button on GPIO 0 to trigger a StoreProhibited panic.");
pinMode(PIN_CRASH_BUTTON, INPUT_PULLUP);
pinMode(PIN_STATUS_LED, OUTPUT);
}
void loop() {
// Normal operation: Blink LED to show system is alive
digitalWrite(PIN_STATUS_LED, HIGH);
delay(250);
digitalWrite(PIN_STATUS_LED, LOW);
delay(250);
// Check for crash trigger
if (digitalRead(PIN_CRASH_BUTTON) == LOW) {
// Debounce delay
delay(50);
if (digitalRead(PIN_CRASH_BUTTON) == LOW) {
Serial.println("\n[ERROR] Button pressed. Triggering deliberate crash in 1 second...");
Serial.flush(); // Ensure serial buffer empties before CPU dies
delay(1000);
// Turn LED solid to indicate crash state
digitalWrite(PIN_STATUS_LED, HIGH);
// Execute the crash
triggerNullPointerCrash();
// Code below this line will never execute
Serial.println("[WARN] If you see this, the crash failed to trigger.");
}
}
}
// --- CRASH FUNCTIONS ---
void triggerNullPointerCrash() {
// Deliberately write to memory address 0x00000000
// This causes a 'StoreProhibited' Guru Meditation Error
int *nullPtr = nullptr;
*nullPtr = 42;
}
void triggerStackOverflowCrash(int depth) {
// Alternative crash: Exhaust the FreeRTOS task stack
// Useful for testing stack boundary coredumps
volatile char buffer[1024];
buffer[0] = depth;
triggerStackOverflowCrash(depth + 1);
}
Decoding the Panic: Exact Error Strings & Ranked Causes
When the ESP32 reboots after the crash, open the Serial Monitor at 115200 baud. You will see a wall of text. The exact error string for our null pointer test looks like this:
Guru Meditation Error: Core 1 panic'ed (StoreProhibited). Exception was unhandled.
Core 1 register dump:
PC : 0x400d1234 PS : 0x00060030 A0 : 0x800d1256 A1 : 0x3ffb1e00
...
Backtrace: 0x400d1234:0x3ffb1e00 0x800d1256:0x3ffb1e20
According to the Espressif Fatal Errors Guide, here are the ranked causes for the most common panic strings you will encounter:
- StoreProhibited / LoadProhibited: The CPU tried to write to or read from an invalid memory address (usually
0x00000000for null pointers, or an uninitialized pointer). Fix: Check your pointer math and ensure objects are instantiated before use. - Interrupt wdt timeout / Task wdt timeout: The Watchdog Timer tripped because a task monopolized the CPU for too long (usually >5 seconds) without yielding, or an interrupt service routine (ISR) took too long. Fix: Add
yield()orvTaskDelay()in tight loops; move heavy processing out of ISRs. - Stack overflow: A task exceeded its allocated FreeRTOS stack space, often due to deep recursion or massive local arrays (like the
triggerStackOverflowCrashfunction above). Fix: Increase the stack size inxTaskCreateor move large buffers to the heap usingmallocorstd::vector. - Corrupted heap: Triggered by double-freeing memory or writing past the bounds of an allocated array. Fix: Use
heap_caps_check_integrity_all(true)periodically during development to catch heap corruption early.
To decode the raw backtrace addresses into actual line numbers in your C++ code, use the espcoredump.py tool bundled with the ESP-IDF. You pass it the ELF file generated during compilation and the raw base64 coredump extracted from the flash.
The First Three Things to Check When Coredump Fails
If your ESP32 crashes but the Serial Monitor just shows a reboot without the coredump summary, run through this diagnostic decision path:
- Partition Table Mismatch: Did you change the partition scheme after the initial flash? If you switch from 'Huge APP' to 'Default' without selecting Tools > Erase All Flash Before Upload: Enabled, the old partition table remains in flash. The new firmware will look for a coredump partition that the old table doesn't define, causing the dump to fail silently. Always erase flash when changing partition schemes.
- Power Brownout During Flash Write: Writing the coredump to SPI flash takes roughly 200-500ms and draws peak current. If your USB cable is thin (high resistance) or your PC's USB port is underpowered, the voltage will drop below the ESP32's brownout threshold (~2.4V on the 3V3 rail) mid-write, aborting the dump. Use a high-quality, short USB cable and add the 100µF decoupling capacitor mentioned in the hardware list.
- Flash Encryption / Secure Boot: If you have enabled Flash Encryption or Secure Boot in the ESP-IDF menuconfig (rare in Arduino IDE, but possible via custom
sdkconfig), the coredump data is encrypted. The standardespcoredump.pyscript cannot parse it without the encryption keys. For standard Arduino debugging, ensure these security features are disabled.
Extending and Simplifying Your Debug Build
Depending on your deployment environment, writing coredumps to flash isn't always the best choice. Here is how to adapt your debugging strategy.
Simplify: Switch to UART Coredump
If you are working with a custom PCB that lacks a dedicated flash partition, or you simply don't want to manage partition tables, switch to Tools > Core Dump to > UART. When a crash occurs, the ESP32 will print the coredump as a Base64 encoded string directly to the Serial Monitor. You can copy-paste this text block into a file and feed it to the decoding script. The trade-off? If the crash corrupts the UART peripheral or baud rate generator, the dump will be garbled.
Extend: Live JTAG Debugging
Coredumps are strictly post-mortem. If you need to inspect variables before a crash, or step through code line-by-line, extend your bench setup with an ESP-Prog JTAG debugger. By connecting the ESP-Prog to the ESP32's JTAG pins (GPIO 12, 13, 14, 15), you can use OpenOCD and GDB to set hardware breakpoints, inspect live RAM, and catch exceptions the exact microsecond they occur. This is the gold standard for debugging complex FreeRTOS race conditions that coredumps can only show the aftermath of.






