From 6b53c58f92cb693432fa6aad47dc4df2ad936d3d Mon Sep 17 00:00:00 2001 From: deadprogram Date: Sun, 30 Aug 2026 10:36:44 +0200 Subject: [PATCH] esp32c6: fix .rodata flash mapping being off by a few bytes The flash MMU maps 64kB pages, so a flash-mapped section is only read correctly when its virtual address and its offset within the firmware image agree modulo 64kB. targets/esp32c6.ld reproduces the image offset in .rodata_dummy by accumulating the preceding segment sizes and headers, which works only if the linker inserts no alignment padding between the dummy and .rodata -- padding moves the virtual address without moving the image offset. lld gives .rodata 8-byte alignment, but the accumulated offset is only 4-byte aligned, so whenever the preceding segments happened to leave the running offset at a 4-mod-8 boundary the whole of .rodata was mapped 4 bytes off. Every read of constant data then returned neighbouring bytes. This is silent and looks like arbitrary memory corruption rather than a mapping bug. It was found while bringing up WiFi: the blob rejected its init config because the first field of a const struct read back as a code pointer, and whether a given build was affected depended on unrelated code size changes. Pad .data, .iram and .text to a multiple of 8 so the running image offset stays 8-aligned and matches, and add ASSERTs for both flash-mapped sections so this cannot regress silently. Signed-off-by: Ron Evans --- targets/esp32c6.ld | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/targets/esp32c6.ld b/targets/esp32c6.ld index 2bb030bd4..48e9de9aa 100644 --- a/targets/esp32c6.ld +++ b/targets/esp32c6.ld @@ -65,7 +65,9 @@ SECTIONS . = ALIGN (4); _sbss = ABSOLUTE(.); *(.sbss) + *(.sbss.*) *(.bss .bss.*) + *(COMMON) . = ALIGN (4); _ebss = ABSOLUTE(.); } >SRAM @@ -78,10 +80,13 @@ SECTIONS . = ALIGN (4); _sdata = ABSOLUTE(.); *(.sdata) + *(.sdata.*) *(.data .data.*) *(.dram*) . = ALIGN (4); _edata = ABSOLUTE(.); + /* Keep the image segment a multiple of 8; see the MMU note below. */ + . = ALIGN (8); } >SRAM /* Code that must run from IRAM (not flash-mapped), for example interrupt @@ -113,6 +118,8 @@ SECTIONS . = ALIGN(256); *(.text.exception_vectors) . = ALIGN(4); + /* Keep the image segment a multiple of 8; see the MMU note below. */ + . = ALIGN(8); } > SRAM /* Heap starts after IRAM. @@ -149,6 +156,8 @@ SECTIONS .text : ALIGN(4) { *(.text .text.*) + /* Keep the image segment a multiple of 8; see the MMU note below. */ + . = ALIGN(8); } > IROM /* === DROM sections (flash-mapped read-only data) === */ @@ -168,13 +177,39 @@ SECTIONS . += 0x8; /* rodata segment header */ } > DROM - /* Constant global variables, stored in DROM. */ + /* Constant global variables, stored in DROM. + * + * The flash MMU maps 64kB pages, so a flash-mapped section is only read + * correctly when its virtual address and its offset within the firmware + * image agree modulo 64kB. The dummy section above reproduces the image + * offset by accumulating the preceding segment sizes and headers, but that + * only works if the linker does not then insert alignment padding between + * the dummy and this section: the padding moves the virtual address without + * moving the image offset. lld gives .rodata 8-byte alignment, so every + * preceding segment is padded to a multiple of 8 above to keep the two in + * step. Getting this wrong shifts the whole of .rodata by a few bytes, which + * is silent and looks like arbitrary data corruption rather than a mapping + * bug -- it was found via a WiFi blob rejecting a struct whose first field + * read back as a code pointer. The ASSERT after this section catches it. + */ .rodata : ALIGN(4) { *(.rodata*) + *(.srodata*) . = ALIGN (4); } >DROM + /* The flash MMU constraint described above, enforced at link time for both + * flash-mapped sections. */ + ASSERT(((ADDR(.text) - ORIGIN(IROM)) & 0xffff) == + ((0x18 + ((SIZEOF(.data) > 0) ? (SIZEOF(.data) + 0x8) : 0) + + SIZEOF(.iram) + 0x8 + 0x8) & 0xffff), + ".text virtual address and flash offset disagree modulo 64kB") + ASSERT(((ADDR(.rodata) - ORIGIN(DROM)) & 0xffff) == + ((0x18 + ((SIZEOF(.data) > 0) ? (SIZEOF(.data) + 0x8) : 0) + + SIZEOF(.iram) + 0x8 + SIZEOF(.text) + 0x8 + 0x8) & 0xffff), + ".rodata virtual address and flash offset disagree modulo 64kB") + /DISCARD/ : { *(.eh_frame) /* we don't do exception handling in C */