ESP32-C3: Executing code from RTC_FAST_MEM causes “Illegal Instruction” (works fine under GDB)
Posted: Tue Oct 14, 2025 11:39 am
Hello everyone,
I'm experimenting with the idea of having self-modifying or dynamically loaded code in my ESP-IDF project and have encountered some behavior I can’t explain.
I have a regular ESP-IDF project compiled and running on an ESP32-C3.
After flashing the normal firmware, I modify the firmware image in flash by adding an additional code segment that should be loaded at 0x50001000.
After a reboot, the bootloader indeed detects and loads my new segment:
When I read memory at address 0x50001000 via the D-Bus, my code appears correctly in RAM as expected.
However, when I try to execute it by jumping to 0x50001000, I get an Illegal Instruction exception:
Here’s the strange part:
If I connect GDB via OpenOCD (USB/JTAG) and then perform the exact same jump to 0x50001000, the code executes perfectly—no exception, no issue. This happens without any breakpoints or stepping; just having GDB attached makes it work.
This behavior is fully reproducible:
Without GDB → Illegal Instruction
With GDB attached → works flawlessly
Does anyone have an idea why executing from 0x50001000 only works when GDB is connected?
Any insights or pointers would be greatly appreciated.
I'm experimenting with the idea of having self-modifying or dynamically loaded code in my ESP-IDF project and have encountered some behavior I can’t explain.
I have a regular ESP-IDF project compiled and running on an ESP32-C3.
After flashing the normal firmware, I modify the firmware image in flash by adding an additional code segment that should be loaded at 0x50001000.
After a reboot, the bootloader indeed detects and loads my new segment:
Code: Select all
I (222) esp_image: segment 6: paddr=002d89a0 vaddr=50001000 size=001e4h ( 484) loadHowever, when I try to execute it by jumping to 0x50001000, I get an Illegal Instruction exception:
Code: Select all
Guru Meditation Error: Core 0 panic'ed (Illegal instruction). Exception was unhandled.
Core 0 register dump:
MEPC : 0x50001000 RA : 0x42088722 SP : 0x3fca2d40 GP : 0x3fc8ca00
TP : 0x3fca3360 T0 : 0x42085e36 T1 : 0x40387f60 T2 : 0xffffffff
S0/FP : 0x00000004 S1 : 0x50001000 A0 : 0x3fca5274 A1 : 0x3fca52b0
A2 : 0x00000000 A3 : 0x00000000 A4 : 0x00000002 A5 : 0x3fcb6b5c
A6 : 0x3fcc5574 A7 : 0x3fca2c24 S2 : 0x3fcabeec S3 : 0x00000000
S4 : 0x00000001 S5 : 0x00000016 S6 : 0x00000d79 S7 : 0x00000001
S8 : 0x00000001 S9 : 0x00000d79 S10 : 0x00000d79 S11 : 0x3c1df000
T3 : 0xffffffff T4 : 0x00000000 T5 : 0x0ccccccc T6 : 0x00000019
MSTATUS : 0x00001881 MTVEC : 0x40380001 MCAUSE : 0x00000002 MTVAL : 0x00000000
MHARTID : 0x00000000
If I connect GDB via OpenOCD (USB/JTAG) and then perform the exact same jump to 0x50001000, the code executes perfectly—no exception, no issue. This happens without any breakpoints or stepping; just having GDB attached makes it work.
This behavior is fully reproducible:
Without GDB → Illegal Instruction
With GDB attached → works flawlessly
Does anyone have an idea why executing from 0x50001000 only works when GDB is connected?
Any insights or pointers would be greatly appreciated.