Embedded & Bare-Metal

the embedded linker script

Imagine you are moving into an empty building and you must decide, room by room, exactly where every piece of furniture goes — and some rooms can only hold certain things (a vault for valuables, a closet for clothes). On a hosted PC the operating system's loader makes most of those decisions for you. On a microcontroller there is no loader, so YOU must tell the linker precisely where in the chip's memory each part of your program lives. The linker script is that floor plan. (See Field l for the general linker-script idea; here we focus on the embedded specifics.)

An embedded linker script has two main parts. First, a MEMORY block that names the physical regions of the chip and their exact addresses and sizes — for example FLASH starting at 0x08000000 with 256 KiB, and RAM starting at 0x20000000 with 64 KiB. Second, a SECTIONS block that places each kind of output into those regions: the vector table and program code (.text) and read-only constants (.rodata) go into FLASH; writable data (.data, .bss) and the stack go into RAM. The critical embedded twist is .data: its runtime location is RAM (where the variables live) but its load location is FLASH (where the initial values are stored so they survive power-off). The linker script expresses this with a 'virtual address vs load address' pair (in GNU ld, the AT> keyword), and it also EXPORTS symbols like _sdata, _edata, _sbss, _ebss, and _sidata that the startup code reads to know where to copy and where to zero. It can also pin a section to a fixed address — essential for placing the vector table where the reset hardware looks.

It matters because, with no OS, the linker script is the only thing that maps your abstract sections onto the real chip — get a region size wrong and the linker errors (region overflowed) or, worse, things silently land in the wrong memory. The honest caveat to internalize: the linker script and the startup code are a matched pair. The script defines the boundary symbols; the startup code USES them. If you edit one without the other — say you add a memory region but the startup loop still uses the old _ebss — your .bss zeroing or .data copy will be wrong, and the failure shows up far from its cause.

/* minimal Cortex-M linker script (GNU ld) */ MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 256K RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 64K } SECTIONS { .isr_vector : { KEEP(*(.isr_vector)) } > FLASH /* pin vector table */ .text : { *(.text*) *(.rodata*) } > FLASH .data : { _sdata = .; *(.data*) _edata = .; } > RAM AT> FLASH _sidata = LOADADDR(.data); /* flash copy of .data */ .bss : { _sbss = .; *(.bss*) _ebss = .; } > RAM }

MEMORY names the chip's regions; SECTIONS places code in FLASH and data in RAM, with .data's load copy in FLASH (AT> FLASH) and boundary symbols for the startup code.

The linker script and the startup code are one matched pair: the script EXPORTS _sdata/_sbss/_sidata and the startup code USES them. Edit one without the other and your .bss zeroing or .data copy silently goes wrong.

Also called
linker scriptlinker command fileld script連結腳本.ld 檔