UNIT 04 · LESSON 4 OF 6

Linker Scripts and Memory Sections

What does that file say, and how does it turn a pile of sections into an image with every address decided?

INTERACTIVEWhat a linker script decides
Flash and RAM regions with the output sections a linker script places in themFLASH 128 KiBRAM 32 KiB.text0x0000_0000.rodata0x0000_A000.data (load)0x0000_C000.data (run)0x2000_0000.bss0x2000_0800stack0x2000_7800blue .text and stack · green .rodata · teal .data (both copies) · orange .bssFlash 50 KiB of 128 KiB (39.1 %), RAM 16 KiB of 32 KiB (50.0 %) including the stack.
Flash and RAM regions with the output sections a linker script places in themFLASH 128 KiBRAM 32 KiBblue .text and stack · green .rodata · teal .data(both copies) · orange .bss.text 0x0000_0000 .rodata 0x0000_A000.data load 0x0000_C000, run 0x2000_0000.bss 0x2000_0800 stack 0x2000_7800Flash 50 KiB of 128 KiB (39.1 %), RAM 16 KiB of 32KiB (50.0 %) including the stack.

Try this

Flash 50 KiB of 128 KiB (39.1 %), RAM 16 KiB of 32 KiB (50.0 %) including the stack. .data: run address 0x2000_0000, load address 0x0000_C000.

MEMORY names the regions (here an illustrative 128 KiB flash at 0x0000_0000 and 32 KiB RAM at 0x2000_0000); SECTIONS says which input sections go where. .data runs from RAM (its VMA) but its initial values are stored in flash after .rodata (its LMA, “> RAM AT> FLASH”). A 2 KiB stack is reserved at the top of RAM. The linker refuses to produce an image that overflows a region.

What you will be able to do
  • Read a MEMORY block and a SECTIONS block and say which output section lands in which region at which address.
  • Compute addresses produced by the location counter, input-section alignment and ALIGN().
  • Explain the difference between a section’s virtual address (VMA) and load address (LMA), using .data as the example.
  • Use symbols defined in a linker script from C, and explain why such a symbol has an address but no value.
  • Interpret a region-overflow failure and decide what to change.
Before you start
  • Sections, symbols and garbage collection (lesson 3).
  • Startup code copying .data and zeroing .bss (unit 3, lessons 5 and 6).
Steps in this lesson
  1. Describing the memory
  2. Placing the sections
  3. Two addresses for .data
  4. When a region overflows
  5. Worked example: laying out an image by hand
  6. Common misconceptions

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:

ALIGN(x,n)=⌈xn⌉×n\text{ALIGN}(x, n) = \left\lceil \frac{x}{n} \right\rceil \times n

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.

INTERACTIVEThe location counter and alignment
Input sections placed along an address line with alignment paddingaddresses from 0x100a.o(.text): 0x100, 6 bytes, ends at 0x106b.o(.text): ⌈0x106 / 4⌉ × 4 = 0x108 (2 padding bytes). = ALIGN(8): 0x110 → 0x110 (0 padding bytes).data starts at 0x110blue a.o · teal b.o · dashed padding · orange .data
Input sections placed along an address line with alignment paddingaddresses from 0x100a.o(.text): 0x100, 6 bytes, ends at 0x106b.o(.text): ⌈0x106 / 4⌉ × 4 = 0x108 (2 paddingbytes). = ALIGN(8): 0x110 → 0x110 (0 padding bytes).data starts at 0x110blue a.o · teal b.o · dashed padding · orange .data
Alignment of b.o(.text)
. = ALIGN(n) before .data
b.o(.text) at 0x108 after 2 padding bytes; .data at 0x110 after 0 more.

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.

↑ This step uses the figure at the top of the page.

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.

SectionVMALMASize
.text0x0000_0000same40 KiB = 0xA000
.rodata0x0000_A000same8 KiB = 0x2000
.data0x2000_00000x0000_C0002 KiB = 0x800
.bss0x2000_0800none12 KiB = 0x3000
stack0x2000_7800 to 0x2000_8000none2 KiB

Flash holds 40+8+2=5040 + 8 + 2 = 50 KiB of 128 (39.1 %). RAM holds 2+12+2=162 + 12 + 2 = 16 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 120+8+2=130120 + 8 + 2 = 130 KiB, and the linker reports the region overflowed by 130−128=2130 - 128 = 2 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?

⌈0x1236/8⌉×8\lceil \text{0x1236} / 8 \rceil \times 8 = 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.

1024+30 144+2048=33 2161024 + 30\,144 + 2048 = 33\,216 bytes, which is 33 216−32 768=44833\,216 - 32\,768 = 448 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)
  1. 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
  2. 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
  3. 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
  4. 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
  5. 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