Linker scripts
Contract
| Field | Bound contract |
|---|---|
| Trigger | A bare-metal image needs a GNU ld script: MEMORY and SECTIONS, VMA versus LMA for .data, the symbols startup code copies between, a function placed in a named region, weak default handlers, or a region overflow error. |
| Authority | Reversible local: writes only the linker script and the startup source in the project directory the user names; rollback is reverting those files in version control. No remote mutation. |
| Side effect | New or edited .ld and startup files. The image layout changes on the next link. |
| Done | The ELF links, readelf -S shows .data with an LMA inside flash and a VMA inside RAM, .bss and the stack fit the RAM region, and the startup code copies .data and zeroes .bss between the script's symbols before main. |
Inputs
- Flash and RAM origins and lengths from the datasheet, plus any extra region (CCM, TCM, backup SRAM).
- The vector table section name the startup file emits (
.isr_vectorin the CMSIS templates). - Whether the C runtime is newlib (needs
__libc_init_arrayand the.init_arraysections) or none. - Functions or buffers that must live in a specific region.
Procedure
Write
MEMORYand the skeletonSECTIONS.ENTRYnames the reset symbol;KEEPon the vector table stops--gc-sectionsfrom removing it;_estackat the top of RAM is where the startup file points the stack. Done when: the script has oneMEMORYblock with every region from the datasheet and the link succeeds with-T script.ld.ENTRY(Reset_Handler) MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K } SECTIONS { .text : { KEEP(*(.isr_vector)) *(.text .text.*) *(.rodata .rodata.*) . = ALIGN(4); _etext = .; } > FLASH .data : { . = ALIGN(4); _sdata = .; *(.data .data.*) . = ALIGN(4); _edata = .; } > RAM AT > FLASH _sidata = LOADADDR(.data); .bss : { _sbss = .; *(.bss .bss.*) *(COMMON) . = ALIGN(4); _ebss = .; } > RAM _estack = ORIGIN(RAM) + LENGTH(RAM); }Separate VMA from LMA for
.data. The VMA is the address the code runs at (RAM); the LMA is where the bytes sit in the image (flash).> RAM AT > FLASHsets both;AT(_etext)after the section name is the equivalent explicit form.LOADADDR(.data)gives startup code the flash copy's address. Done when:arm-none-eabi-readelf -S firmware.elfshows.datawithAddrin RAM andarm-none-eabi-objdump -hshows itsLMAin flash.Copy
.dataand zero.bssin the reset handler, between the script's symbols. With newlib, call__libc_init_array()beforemainso C++ constructors and.init_arrayentries run. Done when: a global initialized to a nonzero value reads that value inmain, and a zero-initialized global reads zero.extern uint32_t _sdata, _edata, _sidata, _sbss, _ebss; void Reset_Handler(void) { uint32_t *src = &_sidata, *dst = &_sdata; while (dst < &_edata) *dst++ = *src++; for (dst = &_sbss; dst < &_ebss; ) *dst++ = 0; __libc_init_array(); main(); for (;;); }The startup file itself is the subject of
baremetal-startup; this skill owns the symbols it consumes.Place code or data in a named region. Give the input section a name with
__attribute__((section(".name"))), collect it into an output section with> REGION AT > FLASH, and copy it at startup like.datawhen the region is RAM. Done when:objdump -hshows the section in the intended region and the copy loop covers it.MEMORY { FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K CCM (rwx) : ORIGIN = 0x10000000, LENGTH = 64K } .fast_code : { _sfast = .; *(.fast_code .fast_code.*) _efast = .; } > CCM AT > FLASH _sifast = LOADADDR(.fast_code);__attribute__((section(".fast_code"))) void critical_isr_handler(void) { }CCM on STM32F4 is not on the DMA bus; place DMA buffers in SRAM.
Use
KEEP,ALIGN,PROVIDE, and the fill expression where each applies.KEEPprotects sections--gc-sectionswould drop (vector table,.init_array).ALIGN(8)before the stack satisfies the AAPCS 8-byte stack alignment.PROVIDEdefines a symbol only if no object defines it, which makes it the default a program can override. The=0xFFfill after an output section writes the erased-flash value into gaps. Done when: everyKEEPguards a section the code never references directly, and the stack region starts 8-byte aligned.PROVIDE(_stack_size = 0x400); .stack (NOLOAD) : { . = ALIGN(8); . += _stack_size; _stack_top = .; } > RAM .text : { *(.text .text.*) . = ALIGN(4); } > FLASH = 0xFFGive every exception a weak default handler.
PROVIDE(NMI_Handler = Default_Handler)in the script, or__attribute__((weak))in C, binds the vector to a spin loop until the application defines the real symbol. Done when: a firmware with noSysTick_Handlerlinks, and one that defines it calls the definition.PROVIDE(NMI_Handler = Default_Handler); PROVIDE(HardFault_Handler = Default_Handler); PROVIDE(SysTick_Handler = Default_Handler);__attribute__((weak)) void Default_Handler(void) { for (;;); } void SysTick_Handler(void) { tick_count++; } /* overrides the default */Check sizes and addresses after every layout change.
--print-memory-usage(pass as-Wl,--print-memory-usage) prints region use at link time;-Wl,-Map=firmware.mapwrites the symbol map for the big-symbol hunt. Done when: every region is under 100 percent and.data's LMA is inside flash.arm-none-eabi-size -A firmware.elf arm-none-eabi-objdump -h firmware.elf arm-none-eabi-readelf -S firmware.elfreferences/linker-script-anatomy.mdhas a complete STM32F407 script with the ARM unwind and.init_arraysections and the table of built-in functions.
Failure and recovery
| Error | Cause | Fix |
|---|---|---|
region `FLASH' overflowed by N bytes |
Image larger than flash | -Os, -ffunction-sections -fdata-sections with -Wl,--gc-sections, LTO; find the big symbols in the map file. |
region `RAM' overflowed |
.data, .bss, heap, and stack exceed RAM |
Shrink buffers, cut _stack_size or the heap, move constants to const so they land in .rodata. |
undefined reference to '_estack' |
Startup expects a symbol the script does not define | Define _estack = ORIGIN(RAM) + LENGTH(RAM);. |
.data reads garbage at boot |
LMA equals VMA, or startup copies the wrong range | Add AT > FLASH; copy from LOADADDR(.data) to _sdata up to _edata. |
| Vector table missing from the image | --gc-sections removed it |
Wrap it in KEEP. |
cannot open linker script file |
Wrong path | Pass -T path/to/script.ld or add -L dir. |
Output
A linker script and startup symbols in the named directory that link the image into the datasheet's regions, with readelf -S and objdump -h output confirming .data LMA in flash and VMA in RAM and every region within its length.