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 <ron@hybridgroup.com>
This commit is contained in:
deadprogram
2026-08-30 10:36:44 +02:00
parent a7626e87e9
commit 6b53c58f92
+36 -1
View File
@@ -65,7 +65,9 @@ SECTIONS
. = ALIGN (4); . = ALIGN (4);
_sbss = ABSOLUTE(.); _sbss = ABSOLUTE(.);
*(.sbss) *(.sbss)
*(.sbss.*)
*(.bss .bss.*) *(.bss .bss.*)
*(COMMON)
. = ALIGN (4); . = ALIGN (4);
_ebss = ABSOLUTE(.); _ebss = ABSOLUTE(.);
} >SRAM } >SRAM
@@ -78,10 +80,13 @@ SECTIONS
. = ALIGN (4); . = ALIGN (4);
_sdata = ABSOLUTE(.); _sdata = ABSOLUTE(.);
*(.sdata) *(.sdata)
*(.sdata.*)
*(.data .data.*) *(.data .data.*)
*(.dram*) *(.dram*)
. = ALIGN (4); . = ALIGN (4);
_edata = ABSOLUTE(.); _edata = ABSOLUTE(.);
/* Keep the image segment a multiple of 8; see the MMU note below. */
. = ALIGN (8);
} >SRAM } >SRAM
/* Code that must run from IRAM (not flash-mapped), for example interrupt /* Code that must run from IRAM (not flash-mapped), for example interrupt
@@ -113,6 +118,8 @@ SECTIONS
. = ALIGN(256); . = ALIGN(256);
*(.text.exception_vectors) *(.text.exception_vectors)
. = ALIGN(4); . = ALIGN(4);
/* Keep the image segment a multiple of 8; see the MMU note below. */
. = ALIGN(8);
} > SRAM } > SRAM
/* Heap starts after IRAM. /* Heap starts after IRAM.
@@ -149,6 +156,8 @@ SECTIONS
.text : ALIGN(4) .text : ALIGN(4)
{ {
*(.text .text.*) *(.text .text.*)
/* Keep the image segment a multiple of 8; see the MMU note below. */
. = ALIGN(8);
} > IROM } > IROM
/* === DROM sections (flash-mapped read-only data) === */ /* === DROM sections (flash-mapped read-only data) === */
@@ -168,13 +177,39 @@ SECTIONS
. += 0x8; /* rodata segment header */ . += 0x8; /* rodata segment header */
} > DROM } > 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 : ALIGN(4)
{ {
*(.rodata*) *(.rodata*)
*(.srodata*)
. = ALIGN (4); . = ALIGN (4);
} >DROM } >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/ : /DISCARD/ :
{ {
*(.eh_frame) /* we don't do exception handling in C */ *(.eh_frame) /* we don't do exception handling in C */