
Linker Scripts
- 288 installs
- 155 repo stars
- Updated June 27, 2026
- mohitmishra786/low-level-dev-skills
Author and debug custom linker scripts that place code, data, and BSS into flash, RAM, and peripheral regions for bare-metal, RTOS, and kernel-linked binaries.
About
Guides creation and troubleshooting of GNU ld and LLD linker scripts for low-level Rust, C, and C++ projects. Covers section ordering, vector tables, heap and stack sizing, PROVIDE symbols, and reading map files to fix overlap, missing symbols, and boot failures on embedded and custom-kernel targets.
- Memory region and section placement
- ENTRY, PROVIDE, and symbol alias patterns
- Flash versus RAM layout for embedded targets
- Linker script debugging with map files
- Cross-toolchain GNU ld and LLD compatibility
Linker Scripts by the numbers
- 288 all-time installs (skills.sh)
- +34 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #43 of 121 Rust skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/mohitmishra786/low-level-dev-skills --skill linker-scriptsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 288 |
|---|---|
| repo stars | ★ 155 |
| Last updated | June 27, 2026 |
| Repository | mohitmishra786/low-level-dev-skills ↗ |
What it does
Author and debug custom linker scripts that place code, data, and BSS into flash, RAM, and peripheral regions for bare-metal, RTOS, and kernel-linked binaries.
Files
Linker Scripts
Purpose
Guide agents through writing and modifying GNU ld linker scripts for embedded targets: MEMORY and SECTIONS commands, VMA vs LMA for code relocation, startup .bss/.data initialization, placing sections in specific regions, and using PROVIDE/KEEP/ALIGN directives.
Triggers
- "How do I write a linker script for my MCU?"
- "How do I place a function in a specific flash/RAM region?"
- "What's the difference between VMA and LMA in a linker script?"
- "How does .bss and .data initialization work at startup?"
- "Linker error: region 'FLASH' overflowed"
- "How do I use weak symbols in a linker script?"
Workflow
1. Linker script anatomy
/* Minimal Cortex-M linker script */
ENTRY(Reset_Handler) /* entry point symbol */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}
SECTIONS
{
.text : /* code section */
{
KEEP(*(.isr_vector)) /* interrupt vector must be first */
*(.text)
*(.text.*)
*(.rodata)
*(.rodata.*)
. = ALIGN(4);
_etext = .; /* end of flash content */
} > FLASH
.data : AT(_etext) /* VMA = RAM, LMA = FLASH */
{
_sdata = .;
*(.data)
*(.data.*)
. = ALIGN(4);
_edata = .;
} > RAM
.bss :
{
_sbss = .;
*(.bss)
*(.bss.*)
*(COMMON)
. = ALIGN(4);
_ebss = .;
} > RAM
/* Stack at top of RAM */
_estack = ORIGIN(RAM) + LENGTH(RAM);
}2. VMA vs LMA
- VMA (Virtual Memory Address): where the section runs at runtime
- LMA (Load Memory Address): where the section is stored in the image (flash)
For .data: stored in flash (LMA), copied to RAM at startup (VMA).
/* AT() sets LMA explicitly */
.data : AT(ADDR(.text) + SIZEOF(.text))
{
_sdata = .;
*(.data)
_edata = .;
} > RAM /* VMA goes in RAM */LMA of .data is automatically placed after .text if you use AT(_etext).
3. Startup .bss / .data initialization
The C runtime must copy .data from flash to RAM and zero .bss before main():
// startup.c or startup.s equivalent in C
extern uint32_t _sdata, _edata, _sidata; // _sidata = LMA of .data
extern uint32_t _sbss, _ebss;
void Reset_Handler(void) {
// Copy .data from flash to RAM
uint32_t *src = &_sidata;
uint32_t *dst = &_sdata;
while (dst < &_edata) *dst++ = *src++;
// Zero-initialize .bss
dst = &_sbss;
while (dst < &_ebss) *dst++ = 0;
// Call C++ constructors
// (call __libc_init_array() if using newlib)
main();
for (;;); // should never return
}Linker script provides _sidata (LMA of .data):
.data : AT(_etext)
{
_sdata = .;
*(.data)
_edata = .;
} > RAM
_sidata = LOADADDR(.data); /* LMA of .data for startup code */4. Placing code in specific regions
/* Place time-critical code in RAM for faster execution */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
RAM (rwx): ORIGIN = 0x20000000, LENGTH = 128K
CCM (rwx): ORIGIN = 0x10000000, LENGTH = 64K /* Cortex-M4 CCM */
}
.fast_code : AT(_etext)
{
_sfast = .;
*(.fast_code) /* sections marked __attribute__((section(".fast_code"))) */
_efast = .;
} > CCM /* runs from CCM RAM */// Mark a function to go in fast_code section
__attribute__((section(".fast_code")))
void critical_isr_handler(void) {
// runs from CCM RAM
}5. KEEP, ALIGN, PROVIDE
/* KEEP — prevent garbage collection of section */
KEEP(*(.isr_vector)) /* linker gc won't remove interrupt table */
KEEP(*(.init))
KEEP(*(.fini))
/* ALIGN — advance location counter to alignment boundary */
. = ALIGN(8); /* align to 8 bytes */
/* PROVIDE — define symbol only if not already defined (weak default) */
PROVIDE(_stack_size = 0x400); /* default 1KB stack; override in code */
/* Symbols for stack */
.stack :
{
. = ALIGN(8);
. += _stack_size;
_stack_top = .;
} > RAM
/* FILL — fill unused bytes */
.text :
{
*(.text)
. = ALIGN(4);
FILL(0xFF) /* fill flash gaps with 0xFF (erased state) */
} > FLASH6. Weak symbols
/* In linker script — provide weak default ISR */
PROVIDE(NMI_Handler = Default_Handler);
PROVIDE(HardFault_Handler = Default_Handler);
PROVIDE(SysTick_Handler = Default_Handler);// In C — weak default handler
__attribute__((weak)) void Default_Handler(void) {
for (;;); // spin — override this in application
}
// Override by defining a non-weak symbol with the same name
void SysTick_Handler(void) {
tick_count++;
}7. Common linker errors
| Error | Cause | Fix |
|---|---|---|
region 'FLASH' overflowed | Binary too large for flash | Enable LTO, -Os, remove unused code; --gc-sections |
region 'RAM' overflowed | Too much RAM used | Reduce stack size, use static buffers, check .bss size |
undefined reference to '_estack' | Missing linker script symbol | Define _estack in linker script |
no rule to process file | .ld extension not recognized | Pass with -T script.ld |
cannot find linker script | Wrong path | Use -L dir -T name.ld |
Data section .data at wrong address | LMA not set | Add AT(_etext) after .data section definition |
# Analyze section sizes
arm-none-eabi-size firmware.elf
arm-none-eabi-size -A firmware.elf # verbose per-section
# Show all sections and their addresses
arm-none-eabi-objdump -h firmware.elf
# Check if .data LMA is in flash range
arm-none-eabi-readelf -S firmware.elf | grep -A2 "\.data"For linker script anatomy details, see references/linker-script-anatomy.md.
Related skills
- Use
skills/embedded/openocd-jtagfor flashing to addresses defined in linker script - Use
skills/embedded/freertosfor FreeRTOS heap placement in specific RAM regions - Use
skills/binaries/linkers-ltofor linker LTO and symbol flags - Use
skills/binaries/elf-inspectionto inspect section sizes and addresses
Linker Script Anatomy Reference
Source: https://sourceware.org/binutils/docs/ld/Scripts.html
Table of Contents
1. Complete STM32 Example 2. Location Counter Operations 3. Output Section Attributes 4. Built-in Functions
Complete STM32 Example
/* STM32F407 — 1MB Flash, 192KB RAM (128KB + 64KB CCM) */
ENTRY(Reset_Handler)
_Min_Heap_Size = 0x800; /* 2KB heap minimum */
_Min_Stack_Size = 0x400; /* 1KB stack minimum */
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K
CCMRAM (xrw) : ORIGIN = 0x10000000, LENGTH = 64K
}
SECTIONS
{
/* Vector table first in flash */
.isr_vector :
{
. = ALIGN(4);
KEEP(*(.isr_vector))
. = ALIGN(4);
} > FLASH
/* Code and read-only data */
.text :
{
. = ALIGN(4);
*(.text)
*(.text*)
*(.glue_7)
*(.glue_7t)
*(.eh_frame)
KEEP(*(.init))
KEEP(*(.fini))
. = ALIGN(4);
_etext = .;
} > FLASH
/* ARM exception unwinding tables */
.ARM.extab : { *(.ARM.extab* .gnu.linkonce.armextab.*) } > FLASH
.ARM : {
__exidx_start = .;
*(.ARM.exidx*)
__exidx_end = .;
} > FLASH
/* C++ constructors/destructors */
.preinit_array :
{
PROVIDE_HIDDEN(__preinit_array_start = .);
KEEP(*(.preinit_array*))
PROVIDE_HIDDEN(__preinit_array_end = .);
} > FLASH
.init_array :
{
PROVIDE_HIDDEN(__init_array_start = .);
KEEP(*(SORT(.init_array.*)))
KEEP(*(.init_array*))
PROVIDE_HIDDEN(__init_array_end = .);
} > FLASH
.fini_array :
{
PROVIDE_HIDDEN(__fini_array_start = .);
KEEP(*(SORT(.fini_array.*)))
KEEP(*(.fini_array*))
PROVIDE_HIDDEN(__fini_array_end = .);
} > FLASH
_sidata = LOADADDR(.data); /* LMA of .data */
.data :
{
. = ALIGN(4);
_sdata = .;
*(.data)
*(.data*)
*(.RamFunc) /* Functions to run from RAM */
*(.RamFunc*)
. = ALIGN(4);
_edata = .;
} > RAM AT > FLASH /* Alternative syntax for AT() */
/* CCM section (no init required — must be initialized manually) */
.ccmram :
{
. = ALIGN(4);
_sccmram = .;
*(.ccmram)
*(.ccmram*)
. = ALIGN(4);
_eccmram = .;
} > CCMRAM AT > FLASH
.bss :
{
_sbss = .;
__bss_start__ = _sbss;
*(.bss)
*(.bss*)
*(COMMON)
. = ALIGN(4);
_ebss = .;
__bss_end__ = _ebss;
} > RAM
/* User heap */
._user_heap_stack :
{
. = ALIGN(8);
PROVIDE(end = .);
PROVIDE(_end = .);
. = . + _Min_Heap_Size;
. = . + _Min_Stack_Size;
. = ALIGN(8);
} > RAM
/* Top of stack = top of RAM */
_estack = ORIGIN(RAM) + LENGTH(RAM);
/* Discard unwanted sections */
/DISCARD/ :
{
libc.a ( * )
libm.a ( * )
libgcc.a ( * )
}
}Location Counter Operations
/* . = current location counter (VMA) */
. = 0x08000000; /* set absolute address */
. = ALIGN(4); /* round up to 4-byte boundary */
. += 256; /* reserve 256 bytes */
/* Useful in MEMORY-relative expressions */
_stack_top = ORIGIN(RAM) + LENGTH(RAM);
_flash_end = ORIGIN(FLASH) + LENGTH(FLASH);Output Section Attributes
.section_name [address] [(type)] :
[AT(lma)]
[ALIGN(align) | ALIGN_WITH_INPUT]
[SUBALIGN(subsection_align)]
[constraint]
{
output-section-command
} [> region] [AT > lma_region] [:phdr :phdr ...] [= fillexp]Type modifiers:
| Type | Meaning |
|---|---|
NOLOAD | Section not loaded (e.g., .noinit BSS variant) |
DSECT | Dummy section (no output) |
INFO | Section contains link information |
OVERLAY | Overlay section |
/* NOLOAD — section exists at runtime address but not in image */
.noinit (NOLOAD) :
{
*(.noinit)
} > RAMBuilt-in Functions
| Function | Description |
|---|---|
ADDR(section) | Returns VMA of named section |
LOADADDR(section) | Returns LMA of named section |
SIZEOF(section) | Size in bytes of named section |
ALIGNOF(section) | Alignment of named section |
DEFINED(symbol) | 1 if symbol defined, 0 otherwise |
MAX(a,b) / MIN(a,b) | Arithmetic max/min |
ALIGN(exp, align) | Round exp up to align boundary |
ABSOLUTE(expr) | Force absolute (non-relative) value |
BLOCK(expr) | Synonym for ALIGN (legacy) |
DATA_SEGMENT_ALIGN(max,page) | For executable data segments |
PROVIDE(sym = expr) | Define sym only if not already defined |
PROVIDE_HIDDEN(sym = expr) | Same but hidden visibility |
KEEP(pattern) | Don't remove with --gc-sections |
SORT(pattern) | Sort matching sections by name |
SORT_BY_ALIGNMENT(pattern) | Sort by alignment (largest first) |