The puzzle
Add one lookup table to a working project and the build fails with region `FLASH' overflowed by 2048 bytes. Add one buffer instead and it builds, but the board crashes at start-up because the stack now overlaps the buffer. Neither the compiler nor the C language knows how much flash or RAM the chip has, or where they are. The linker knows, because someone wrote it down in a small text file with a .ld extension. What does that file say, and how does it turn a pile of sections into an image with every address decided?
STEP 1
Describing the memory
A linker script’s MEMORY command names the chip’s memory regions, with an origin, a length and attributes that say what may go there:
MEMORY
{
FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 128K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K
}
These numbers come from the datasheet’s memory map, and getting them wrong is invisible until the image fails on the chip. The RP2040 SDK’s default script describes its own map the same way: FLASH(rx) of 2048k at 0x1000_0000, RAM(rwx) of 256k at 0x2000_0000, and two 4k scratch banks (unit 3). The GNU ld manual is explicit that the linker places sections into these regions but “will not shuffle sections around to fit”: the layout is exactly what the script says, and it fails rather than improvise.
STEP 2
Placing the sections
The SECTIONS command builds output sections out of input sections from the object files and says which region each goes to:
SECTIONS
{
.text : {
KEEP(*(.vectors)) /* vector table first, never collected */
*(.text*) /* all code, including .text.<function> */
*(.rodata*)
. = ALIGN(4);
_etext = .;
} > FLASH
_sidata = LOADADDR(.data); /* where .data's initial values sit in flash */
.data : {
_sdata = .;
*(.data*)
. = ALIGN(4);
_edata = .;
} > RAM AT> FLASH
.bss (NOLOAD) : {
_sbss = .;
*(.bss*) *(COMMON)
. = ALIGN(4);
_ebss = .;
} > RAM
.stack (NOLOAD) : {
. = . + 0x800; /* reserve 2 KiB so the overflow check includes the stack */
_estack = .;
} > RAM
}
Within an output section, the location counter . is the next free address. Each input section is placed at the next multiple of its own alignment requirement, and an explicit . = ALIGN(n) rounds the counter up:
Assignments such as _etext = .; define symbols whose value is the address at that point. Startup code and the C library use them to find the boundaries of .data and .bss.
Inside SECTIONS the location counter “.” is the next free address. Each input section is placed at the next multiple of its own alignment, and “. = ALIGN(n)” rounds the counter up explicitly, ⌈x / n⌉ × n. The gaps are padding the linker fills; they count against the region even though they hold nothing.
STEP 3
Two addresses for .data
Initialised data is the one section that must live in two places. The program reads and writes .data in RAM, so that is where the linker must resolve every reference to it: its virtual address (VMA). But RAM forgets everything at power-off, so the initial values must be stored in flash: its load address (LMA). In the script, > RAM AT> FLASH says exactly that, and LOADADDR(.data) gives the load address to the startup code. The GNU ld manual illustrates the same idea (with an explicit AT(...) address) together with the copy loop the startup code runs; adapted to this script’s symbols:
extern char _sidata, _sdata, _edata, _sbss, _ebss;
char *src = &_sidata; /* initial values, in flash */
char *dst = &_sdata; /* .data, in RAM */
while (dst < &_edata) *dst++ = *src++;
for (dst = &_sbss; dst < &_ebss; dst++) *dst = 0;
Notice the &. A linker-script symbol is not a variable: it is a name for an address, with no storage behind it. The manual’s “Source Code Reference” section warns that such a symbol “does not have a value”; C code must take its address. Declaring it as an array, extern char _sdata[];, makes that harder to get wrong. (A real startup would copy by words, not bytes, which is why the script aligns the boundaries to 4.)
.bss needs no load address at all. It is only a size in the ELF file, marked NOLOAD here, and startup code fills it with zeros.
STEP 4
When a region overflows
Drag the sliders in the figure until flash or RAM is over capacity. The linker computes the end of every section in every region; if a region’s contents pass its LENGTH, it reports by how many bytes and produces no image. That report is a gift: the overflow is known at build time, exactly, instead of as a crash on the bench. The limit it cannot check is the one it does not know about. A stack that is merely assumed to sit at the top of RAM is invisible to it; that is why the script above reserves the stack as a NOLOAD section in RAM (the figure’s 2 KiB), so that the overflow check includes it. The alternative is an explicit ASSERT, as the RP2040 SDK’s script contains (ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed")).
STEP 5
Worked example: laying out an image by hand
Take the figure’s defaults: 40 KiB of .text, 8 KiB of .rodata, 2 KiB of .data, 12 KiB of .bss, the regions above, and a 2 KiB stack reserved at the top of RAM. Every size is a multiple of 4, so alignment adds nothing.
| Section | VMA | LMA | Size |
|---|---|---|---|
.text | 0x0000_0000 | same | 40 KiB = 0xA000 |
.rodata | 0x0000_A000 | same | 8 KiB = 0x2000 |
.data | 0x2000_0000 | 0x0000_C000 | 2 KiB = 0x800 |
.bss | 0x2000_0800 | none | 12 KiB = 0x3000 |
| stack | 0x2000_7800 to 0x2000_8000 | none | 2 KiB |
Flash holds KiB of 128 (39.1 %). RAM holds KiB of 32 (50 %), leaving 16 KiB between the end of .bss at 0x2000_3800 and the stack.
Now let .text grow to 120 KiB. Flash needs KiB, and the linker reports the region overflowed by KiB, 2048 bytes: the message from the opening. Notice that .data counts against flash too, because its initial values live there. The remedies are the usual ones: -Os, garbage collection (lesson 3), moving tables to external memory, or a bigger part.
MYTHS AND FACTS
Common misconceptions
The linker knows the chip’s memory
It knows only what the script’s MEMORY block says. A wrong LENGTH produces an image that links and then fails on hardware.
.data is only in RAM
It runs from RAM (its VMA) but its initial values are stored in flash (its LMA), and they count against flash.
A linker symbol like _etext is a variable holding an address
It is an address; there is no storage. Use &_etext, or declare it as an array.
The linker will move sections to make them fit
It places them exactly as the script says and fails if a region overflows.
ALIGN only wastes space
It guarantees the alignment that word-by-word copy loops, DMA and some hardware tables require; the few padding bytes are the price.
If it links, the stack fits
The linker checks only what the script describes; actual stack depth is a run-time property (unit 3 lesson 5).
Check yourself
Answer in your head, then open the card.
The location counter is 0x0000_1236 and the next input section requires 8-byte alignment. Where does it start, and how many padding bytes are inserted?
= 0x1238: 2 padding bytes.
.text is 0x5A20 bytes and .rodata 0x0C00 bytes, placed from 0x0800_0000; .data (0x140 bytes) is “> RAM AT> FLASH”. What are .data’s VMA and LMA if RAM starts at 0x2000_0000?
VMA 0x2000_0000. LMA directly after .rodata: 0x0800_0000 + 0x5A20 + 0x0C00 = 0x0800_6620 (already a multiple of 4).
C code declares extern uint32_t _sbss; and loops for (p = (uint32_t *)_sbss; …). What is wrong?
It reads the value stored at address _sbss (whatever the first word of .bss holds) and uses that as a pointer. It must use the symbol’s address: (uint32_t *)&_sbss, or declare extern uint32_t _sbss[]; and use _sbss.
A build fails with “region RAM overflowed by 448 bytes”. RAM is 32 KiB (32 768 bytes), .data is 1024 bytes, .bss 30 144 bytes, and the script reserves a 2048-byte stack. Check the number and name two changes that would make it link.
bytes, which is bytes too many. Shrink .bss (smaller buffers, or buffers that share memory), or reduce the stack reservation if measurement shows the stack needs less. Marking constant tables const moves them from .data to .rodata, which also takes them out of RAM.
Sources (5)
- GNU ld manual, “Linker Scripts”: “MEMORY Command”, “SECTIONS Command”, “The Location Counter”, “Builtin Functions” (ALIGN, ADDR, SIZEOF, LOADADDR) — MEMORY “describes the location and size of blocks of memory in the target … The linker will not shuffle sections around to fit into the available regions”; the location counter “.” always holds the current output location
- GNU ld manual, “Output Section LMA” — “Every section has a virtual address (VMA) and a load address (LMA)”; AT> region sets the load address; the manual’s example keeps .data’s initial values at the end of .text and gives the start-up copy loop that uses the script’s symbols
- GNU ld manual, “Source Code Reference” — a linker-script symbol “is not equivalent to a variable declaration in a high level language, it is instead a symbol that does not have a value”: C code uses its address
- GNU ld manual, “Input Section and Garbage Collection” (KEEP) and “PROVIDE” — KEEP(*(.init)) protects sections from --gc-sections; PROVIDE defines a symbol only if it is referenced and not otherwise defined
- Raspberry Pi Ltd, pico-sdk 1.5.1, src/rp2_common/pico_standard_link/memmap_default.ld — a complete real script: MEMORY with FLASH 2048k, RAM 256k and two 4k scratch banks; .data “> RAM AT> FLASH”; __bss_start__/__bss_end__, __StackTop and an ASSERT on the heap limit