esp-idf
esp-idf copied to clipboard
heap_caps_add_region_with_caps (_EXEC, _32BIT) cause memory protection fault on boot, but works on restart (IDFGH-14916)
Answers checklist.
- [x] I have read the documentation ESP-IDF Programming Guide and the issue is not addressed there.
- [x] I have updated my IDF branch (master or release) to the latest version and checked that the issue is present there.
- [x] I have searched the issue tracker for a similar issue and not found a similar issue.
IDF version.
v5.4
Espressif SoC revision.
esp32c3 (api1-20210207) rev v0.3
Operating System used.
Windows
How did you build your project?
VS Code IDE
If you are using Windows, please specify command line type.
CMD
Development Kit.
NodeMCU ESP32C3 DevKit 2MB
Power Supply used.
USB
What is the expected behavior?
Consistent work (or fail), and not only work after the Guru Meditation restarts the app
What is the actual behavior?
One first boot:
I (274) main_task: Calling app_main()
I (274) play: app_main
I (274) play: this->data 456 direct
I (274) C++ bind: Binding 420093c6 with this 3fc90688, heap size: 0
I (274) C++ bind: Initialize IRAM heap
Guru Meditation Error: Core 0 panic'ed (Memory protection fault).
memory type: IRAM0_SRAM
faulting address: 0x403818e0
world: PMS_WORLD_0
operation type: WRITE
Core 0 register dump:
MEPC : 0x42016aa8 RA : 0x42016a80 SP : 0x3fc8fb90 GP : 0x3fc8c800
TP : 0x3fc8fc80 T0 : 0x4005890e T1 : 0x00000000 T2 : 0x00000000
S0/FP : 0x403818d0 S1 : 0x000007ec A0 : 0x00000b00 A1 : 0x000007ec
A2 : 0x00000000 A3 : 0xffffc0ff A4 : 0x0000000b A5 : 0x00000000
A6 : 0x00000000 A7 : 0x00000002 S2 : 0x000007ec S3 : 0x3fc906ac
S4 : 0x00000000 S5 : 0x00000000 S6 : 0x00000000 S7 : 0x00000000
S8 : 0x00000000 S9 : 0x00000000 S10 : 0x00000000 S11 : 0x00000000
T3 : 0x00000000 T4 : 0x00000000 T5 : 0x00000001 T6 : 0x00000000
MSTATUS : 0x00001881 MTVEC : 0x40380001 MCAUSE : 0x0000001a MTVAL : 0xff9006b7
MHARTID : 0x00000000
On auto-restart after the above:
I (475) play: app_main
I (475) play: this->data 456 direct
I (475) C++ bind: Binding 420093c6 with this 3fc90688, heap size: 0
I (475) C++ bind: Initialize IRAM heap
Heap summary for capabilities 0x00000003:
At 0x403818bc len 2048 free 1740 allocated 0 min_free 1740
largest_free_block 1664 alloc_blocks 0 free_blocks 1 total_blocks 1
Totals:
free 1740 allocated 0 min_free 1740 largest_free_block 1664
I (495) C++ bind: Bound function 403819f4: 3fc90537, 68850513, 420092b7, 3c628293, 00028067
Steps to reproduce.
Code that generates the above output:
BoundMemberFunction _bind(uint32_t self, uint32_t f) {
size_t heap_size = heap_caps_get_total_size(MALLOC_CAP_32BIT | MALLOC_CAP_EXEC);
ESP_LOGI(TAG,"Binding %lx with this %lx, heap size: %u", f, self, heap_size);
if (heap_size == 0) {
ESP_LOGI(TAG,"Initialize IRAM heap");
uint32_t caps[3] = { MALLOC_CAP_EXEC, MALLOC_CAP_32BIT, 0 };
if (heap_caps_add_region_with_caps(caps,(int)custom_heap, (int)(custom_heap + CUSTOM_HEAP_SIZE)) != ESP_OK) {
ESP_LOGE(TAG,"Failed to initialise bound function heap");
return NULL;
}
}
heap_caps_print_heap_info(MALLOC_CAP_32BIT | MALLOC_CAP_EXEC);
// Allocate executable space for the 5 preamble instructions that load "this" into A0 and call the target function
uint32_t *binding = heap_caps_malloc(5 * sizeof (uint32_t), MALLOC_CAP_EXEC | MALLOC_CAP_32BIT);
// # Load 32-bit constant 0x12345678 into a0
binding[0] = (self & 0xFFFFF000) | 0x0000537; // 0x12345537 # lui a0, 0x12345 # Load upper 20 bits
binding[1] = ((self & 0x00000FFF) << 20) | 0x00050513; // 0x67850513 # addi a0, a0, 0x678 # Add lower 12 bits
// # Jump to 32-bit address 0xABCDEF00
binding[2] = (f & 0xFFFFF000) | 0x000002B7; // 0xABCDE2B7 # lui t0, 0xABCDE # Load upper 20 bits of jump address into t0
binding[3] = ((f & 0x00000FFF) << 20) | 0x00028293; // 0xF0028293 # addi t0, t0, 0xF00 # Add lower 12 bits of jump address
// # Jump to address in t0
binding[4] = 0x00028067; // 0x00028067 # jr t0 # Jump to address in t0
ESP_LOGI(TAG,"Bound function %lx: %08lx, %08lx, %08lx, %08lx, %08lx", (uint32_t)binding, binding[0], binding[1], binding[2], binding[3], binding[4]);
return (BoundMemberFunction)binding;
}
Debug Logs.
Diagnostic report archive.
No response
More Information.
No response
From the logging, the error occurs in heap_caps_add_region_with_caps
I have not managed to make this error occurs with the debugger connected. The initial boot sequence indicates there is no IRAM heap by default:
ESP-ROM:esp32c3-api1-20210207
Build:Feb 7 2021
rst:0x3 (RTC_SW_SYS_RST),boot:0xc (SPI_FAST_FLASH_BOOT)
Saved PC:0x2000084c
SPIWP:0xee
mode:DIO, clock div:1
load:0x3fcd5820,len:0x1574
load:0x403cc710,len:0xc30
load:0x403ce710,len:0x2f58
entry 0x403cc71a
I (35) boot: ESP-IDF v5.4 2nd stage bootloader
I (35) boot: compile time Mar 22 2025 08:40:07
I (35) boot: chip revision: v0.3
I (35) boot: efuse block revision: v1.1
I (39) boot.esp32c3: SPI Speed : 80MHz
I (42) boot.esp32c3: SPI Mode : DIO
I (46) boot.esp32c3: SPI Flash Size : 2MB
I (50) boot: Enabling RNG early entropy source...
I (54) boot: Partition Table:
I (57) boot: ## Label Usage Type ST Offset Length
I (63) boot: 0 nvs WiFi data 01 02 00009000 00006000
I (70) boot: 1 phy_init RF data 01 01 0000f000 00001000
I (76) boot: 2 factory factory app 00 00 00010000 00177000
I (83) boot: End of partition table
I (86) esp_image: segment 0: paddr=00010020 vaddr=3c020020 size=08f38h ( 36664) map
I (99) esp_image: segment 1: paddr=00018f60 vaddr=3fc8c000 size=012dch ( 4828) load
I (102) esp_image: segment 2: paddr=0001a244 vaddr=40380000 size=05dd4h ( 24020) load
I (113) esp_image: segment 3: paddr=00020020 vaddr=42000020 size=1704ch ( 94284) map
I (131) esp_image: segment 4: paddr=00037074 vaddr=40385dd4 size=06104h ( 24836) load
I (136) esp_image: segment 5: paddr=0003d180 vaddr=50000200 size=0001ch ( 28) load
I (140) boot: Loaded app from partition at offset 0x10000
I (140) boot: Disabling RNG early entropy source...
I (356) cpu_start: Unicore app
I (364) cpu_start: Pro cpu start user code
I (365) cpu_start: cpu freq: 160000000 Hz
I (365) app_init: Application information:
I (365) app_init: Project name: play
I (368) app_init: App version: 1
I (372) app_init: Compile time: Mar 22 2025 09:38:00
I (377) app_init: ELF file SHA256: a7d0caf2c...
I (381) app_init: ESP-IDF: v5.4
I (385) efuse_init: Min chip rev: v0.3
I (389) efuse_init: Max chip rev: v1.99
I (392) efuse_init: Chip rev: v0.3
I (396) heap_init: Initializing. RAM available for dynamic allocation:
I (403) heap_init: At 3FC8E190 len 00031E70 (199 KiB): RAM
I (408) heap_init: At 3FCC0000 len 0001C710 (113 KiB): Retention RAM
I (414) heap_init: At 3FCDC710 len 00002950 (10 KiB): Retention RAM
I (420) heap_init: At 5000021C len 00001DCC (7 KiB): RTCRAM
I (426) spi_flash: detected chip: generic
I (429) spi_flash: flash io: dio
I (432) sleep_gpio: Configure to isolate all GPIO pins in sleep state
I (438) sleep_gpio: Enable automatic switching of GPIO sleep configuration
I (445) main_task: Started on CPU0
I (475) main_task: Calling app_main()
For completeness, the difference between the startup that works, and the one that doesn't is:
-rst:0x1 (POWERON),boot:0xc (SPI_FAST_FLASH_BOOT)
+rst:0xc (RTC_SW_CPU_RST),boot:0xc (SPI_FAST_FLASH_BOOT)
+Saved PC:0x40380830
...at the startup, and at the end:
-Guru Meditation Error: Core 0 panic'ed (Memory protection fault).
- memory type: IRAM0_SRAM
- faulting address: 0x403818e0
- world: PMS_WORLD_0
- operation type: WRITE
+Heap summary for capabilities 0x00000003:
The -red is the first boot, and the +green is after the "Rebooting..." log line.
Since it reliably works after the SW reboot, this feels like an uninitialized value in the heap manager, or a race to initialize the heap manager which works second time around as whatever it is that requires initialization remains in RAM in the correct state
Update: I believe this is an issue with the CONFIG_ESP_SYSTEM_MEMPROT_FEATURE implementation.
Disabling this feature removes the error entirely. However this begs two questions:
- Why does it work after the soft restart? This suggests the CONFIG_ESP_SYSTEM_MEMPROT_FEATURE could be overcome by a bad actor by doing a software restart which appears to disable the feature
- What is the API for enabling limited write ability to IRAM for execution? The docs say:
Please note that the API for this feature is private and used exclusively by ESP-IDF code only.
...which implies there's no way to define a memory region through the compiler attributes or linker which IS read/write executable, which precludes a number of classes of application, such as JITs and thunks
Any insight into this would be greatly appreciated
Why does it work after the soft restart? This suggests the CONFIG_ESP_SYSTEM_MEMPROT_FEATURE could be overcome by a bad actor by doing a software restart which appears to disable the feature
Thanks for reporting, that does indeed look like a bug, we'll take a look!
which implies there's no way to define a memory region through the compiler attributes or linker which IS read/write executable, which precludes a number of classes of application, such as JITs and thunks
That's correct, currently there is no supported way of enabling write access to IRAM when memprot (on ESP32-C3 or S3) or PMP (on newer chips) are enabled. You would have to disable CONFIG_ESP_SYSTEM_MEMPROT_FEATURE in order to be able to write to IRAM. (Or do a software reset, as you found, but that should hopefully be fixed soon!)
Well, I appreciate the quick feedback! Hopefully I'm wrong and this isn't a bug.
Some way of defining a r/w/x, either via a linker section or attribute would be great!
Hi @MatAtBread, I've been checking your issue and I can't reproduce it anyhow. The Memprot component works as expected, ie you are not allowed to write into the IRAM memory section (set as a default during the system startup. The division line between the instruction and the data memory regions is set slightly after the .iram_text section end, and the application code area is configured as RX for any IRAM access. All the remaining IRAM space is blocked against ANY access [as in fact it's the space occupied by DBUS with RW]. See https://gitlab.espressif.cn:6688/espressif/esp-idf/-/blob/master/components/esp_hw_support/port/esp32c3/esp_memprot.c?ref_type=heads#L704).
- it seems the Memprot in your case is disabled after the first system reset, which would be bad - could you please share your code (the relevant parts)? I somewhat mimicked your test but couldn't do it exactly, of course. It would be great to see your sdkconfig.h too (the generated one), and you can also use esp_mprot_dump_configuration() to see what has changed after the reset.
- The PMS setup allows having multiple memory zones for both IRAM and DRAM, so it is certainly possible to create a sort of "IRAM sandbox" with RWX while still keeping the rest of the system safe. You can achieve the same with PMP setup on other RiscV boards, PMS/Memprot is used only in few models. Anyway, this could be a solution for your JIT-like projects
Thank you
Actually, I don't have the code anymore, tho it might be in some old commit. When I couldn't get it to reliably work, I took a different approach.
I'm actually away for a couple of weeks. When I'm back, I'll see if I can recreate a simple test case.
Understand. Thank you very much
Hi @MatAtBread, have you had a chance to find your testing code, please? It would be great to resolve this issue ASAP, in case it's a real bug in IDF
Thank you
I'm afraid not. I'm still away. Back next week when I'll try to recreate it
No problem. Thanks for the swift reply
I've not managed to resurrect the code where this failed reliably.
If it helps, the code was a "thunk" that bound a C++ member function by shuffling all the parameter registers up by one and forcing the object address into a0, and then jumped to the function address (de-virtualising as necessary). The space for the bound function was allocated using an executable heap.
In the absence of anything repeatable, I daresay you should close this, but I'll leave it up to you as it definitely happened, and if I can stumble across it there's going to be someone smarter than me who can exploit it