In this lesson:
- what a linker script does: define the memory map and place sections
- the
MEMORYandSECTIONScommands - VMA vs LMA, the key idea behind copying
.dataat boot - the symbols the script hands to startup code
- the gotchas: forgetting
AT>, region overflow,KEEP()the vector table
This ties together sections (C module), the memory map and boot (Embedded Fundamentals), and the linker (toolchain).
What a linker script is
The linker must decide where every piece of your program lives in the chip's address space, code in flash, variables in RAM, the vector table at the very start. A linker script (.ld) is the file that tells it: the chip's memory regions and how to place output sections into them. On a desktop the default script suffices; on bare metal you supply one matching your MCU (the memory map from Embedded Fundamentals), or nothing boots.
MEMORY: the regions
The MEMORY command declares the chip's physical memory regions with their start address and size:
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K /* code + constants */
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K /* data, stack, heap */
}(rx) = readable/executable (flash), (rwx) = read/write/execute (RAM). The origins are the chip's actual addresses (STM32 flash at 0x08000000, SRAM at 0x20000000). If your output overflows a region, the linker errors, region FLASH overflowed by N bytes, a useful "it doesn't fit" check.
SECTIONS: placing the output
The SECTIONS command says which output sections go in which region. The sections (from earlier modules): .text (code), .rodata (const data), .data (initialized globals), .bss (zero-init globals).
SECTIONS
{
.text : {
KEEP(*(.isr_vector)) /* vector table FIRST β and KEEP so GC won't drop it */
*(.text*) /* all code */
*(.rodata*) /* const data */
_etext = .; /* mark end of text (used as .data's load source) */
} > FLASH
.data : {
_sdata = .; /* start of .data in RAM (VMA) */
*(.data*)
_edata = .; /* end of .data in RAM */
} > RAM AT > FLASH /* runs in RAM, but STORED in FLASH β the key line */
.bss : {
_sbss = .;
*(.bss*) *(COMMON)
_ebss = .;
} > RAM
}The vector table is placed first via KEEP(*(.isr_vector)) (KEEP prevents --gc-sections from garbage-collecting it since "nothing calls it"). The all-important line is .data ... > RAM AT > FLASH, next.
VMA vs LMA: why .data is copied at boot
This is the concept the whole script hinges on. Every section has two addresses:
- VMA (Virtual Memory Address), where the section runs (lives at runtime).
- LMA (Load Memory Address), where the section is stored in the image.
For most sections VMA = LMA (code runs from flash, stored in flash). But .data is special: initialized globals must be writable, so they run in RAM (VMA in RAM), yet RAM is volatile and starts undefined, so their initial values must be stored in flash (LMA in flash). > RAM AT > FLASH sets exactly that: VMA = RAM, LMA = FLASH.
FLASH RAM
βββββββββββββββ βββββββββββββββ
β .isr_vector β β β
β .text β β .data (VMA) ββββ runs here
β .rodata β startup β .bss (VMA) β
β .data (LMA) βββ copies ββββββΆβ ... β
β (init vals)β at boot β heap β β
βββββββββββββββ β stack β β
βββββββββββββββ
_sidata (LMA) βββββββββββββββΆ _sdata.._edata (VMA): startup copies this rangeThe startup code (boot lesson) reads the .data initial values from flash (LMA) and copies them into RAM (VMA) at reset, then zeroes .bss. The linker script defines the boundary symbols the startup loop uses:
_sidata,.data's load address in flash (the source),_sdata/_edata,.data's run range in RAM (the destination),_sbss/_ebss, the.bssrange to zero.
// Startup uses the linker-script symbols (Embedded Fundamentals boot lesson):
extern uint32_t _sidata, _sdata, _edata, _sbss, _ebss;
for (uint32_t *d=&_sdata, *s=&_sidata; d<&_edata; ) *d++ = *s++; // copy .data: FLASHβRAM
for (uint32_t *b=&_sbss; b<&_ebss; ) *b++ = 0; // zero .bssSo .data initialized globals work only because the linker stored their values in flash and startup copied them to RAM. Forget the AT > FLASH (or the copy) and your globals are garbage.
Other script duties
ENTRY(Reset_Handler) /* the program's entry symbol */
_estack = ORIGIN(RAM) + LENGTH(RAM); /* initial stack pointer = top of RAM */ENTRY names the start symbol; _estack (top of RAM) becomes the first vector-table entry (the initial SP, boot lesson). Scripts also place special regions (a separate CCM RAM, a no-init section that survives reset, a bootloader/app split via different ORIGINs).
Gotchas
- VMA/LMA: forgetting
AT > FLASH. If.datahas no separate flash load address (or startup doesn't copy it), initialized globals come up as garbage, they were never loaded into RAM. The> RAM AT > FLASH+ the startup copy are a matched pair. - Region overflow.
region 'FLASH' overflowed by N bytes(or RAM) means your image doesn't fit. It's the linker doing you a favor, reduce code/data, enable--gc-sections, or pick a bigger part; checksize. KEEP()the vector table (and other "unreferenced" must-keeps). With--gc-sections, the linker drops sections nothing references, and nothing in C references the vector table, so withoutKEEP(*(.isr_vector))it gets garbage-collected and the chip won't boot.- Symbol names must match the startup code. The script's
_sdata/_edata/_sbss/_ebss/_sidata(or your chosen names) must be exactly what startup expects. A mismatch copies the wrong range, partial/incorrect global init. - Stack/heap placement & alignment. Put the stack at the top of RAM growing down; ensure regions are aligned (often 4/8 bytes). A wrong
_estackor unaligned section causes immediate faults at boot.
TL;DR
- A linker script (
.ld) tells the linker the chip's memory map and how to place sections; bare metal requires one that matches your MCU or it won't boot. MEMORYdeclares regions (FLASH at its origin, RAM at its origin, with lengths);SECTIONSplaces.text/.rodatain FLASH and.data/.bssin RAM. Overflowing a region is a (helpful) link error.- VMA vs LMA is the crux:
.dataruns in RAM (VMA) but its init values are stored in flash (LMA) via> RAM AT > FLASH, because RAM starts undefined, so startup copies.dataflashβRAM and zeroes.bssat every reset. - The script defines the boundary symbols (
_sidata,_sdata/_edata,_sbss/_ebss,_estack) andENTRYthat the startup code uses. - Gotchas: forgetting
AT > FLASH(garbage globals), region overflow (doesn't fit),KEEP()the vector table (else GC drops it), matching symbol names, and correct stack placement/alignment.