Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.
bedde
Linker Scripts: ENTRY, MEMORY & SECTIONS
Write your first GNU linker script from scratch. Control exactly where code and data land in flash and SRAM using ENTRY, MEMORY, and SECTIONS commands.
1. What is a Linker Script?
A linker script is a text file that tells the linker:
- What memory regions the target MCU has (flash, SRAM) and their addresses and sizes.
- How to merge sections from multiple object files into output sections.
- Where each output section should be placed in memory.
Without a linker script, the linker would not know that flash starts at 0x08000000 or that SRAM starts at 0x20000000. The script is the bridge between generic object code and the specific memory map of your MCU.
File extension: .ld — pass it to GCC with -T linker.ld
Language: GNU Linker Command Language (not C)
Key Linker Script Commands
ENTRY()— sets the entry point symbol in the ELF headerMEMORY{}— declares memory regions with addresses and sizesSECTIONS{}— maps input sections to output sections and assigns addressesKEEP()— prevents linker from removing unused sections (important for vector table)ALIGN()— aligns the location counter to a boundaryAT>()— specifies the load address (LMA) vs virtual address (VMA)
2. ENTRY Command
The ENTRY command sets the “entry point address” field in the ELF file header. This tells the debugger (GDB, OpenOCD) which function is the first to execute after reset.
Linker Script
/* Set the entry point — first function to run after reset */
ENTRY(Reset_Handler)
ENTRY is not strictly required for the MCU to boot (the vector table controls that), but it is needed when you use GDB to debug — GDB uses the entry point to know where to set the initial program counter.
3. MEMORY Command
The MEMORY command describes the physical memory regions available on your target. The linker uses this to assign addresses and check that sections fit within available space.
Linker Script — MEMORY Syntax
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}
Memory Attribute Letters
| Letter | Meaning |
|---|---|
R | Read-only sections allowed |
W | Read/write sections allowed |
X | Executable sections allowed |
A | Allocated sections |
I or L | Initialized sections |
! | Invert meaning of following attributes |
STM32F407VGT6 Memory Regions
Linker Script — STM32F407 MEMORY
MEMORY
{
/* Flash: 1 MB starting at 0x08000000 */
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
/* SRAM1: 112 KB at 0x20000000 */
SRAM1 (rwx) : ORIGIN = 0x20000000, LENGTH = 112K
/* SRAM2: 16 KB at 0x2001C000 */
SRAM2 (rwx) : ORIGIN = 0x2001C000, LENGTH = 16K
}
4. SECTIONS Command
The SECTIONS command is the most important part of a linker script. It defines:
- Which output sections to create in the final ELF.
- Which input sections from
.ofiles go into each output section. - Which memory region each output section maps to (VMA and LMA).
Linker Script — SECTIONS Template
SECTIONS
{
/* ─── FLASH ──────────────────────────── */
.text :
{
KEEP(*(.isr_vector)) /* Vector table MUST be first */
*(.text) /* All code from all .o files */
*(.text.*) /* Inlined/templated functions */
*(.rodata) /* Read-only data (constants) */
*(.rodata.*)
} > FLASH
/* ─── .data (LMA = FLASH, VMA = SRAM) ── */
_etext = .; /* end of .text = start of .data load copy */
.data : AT>FLASH
{
_sdata = .; /* start of .data in SRAM */
*(.data) /* All initialized globals */
*(.data.*)
. = ALIGN(4);
_edata = .; /* end of .data in SRAM */
} > SRAM1
/* ─── .bss (SRAM only, no flash space) ─ */
.bss :
{
_sbss = .;
*(.bss)
*(.bss.*)
*(COMMON) /* Un-sectioned globals */
. = ALIGN(4);
_ebss = .;
} > SRAM1
}
KEEP(*(.isr_vector)) — Without KEEP, if -Wl,–gc-sections is used, the linker may remove the vector table because no code explicitly “references” it. KEEP forces the section to be included regardless.
VMA vs LMA with AT>
The .data section has two addresses:
- VMA (runtime)
> SRAM1— where the code accesses variables at runtime. - LMA (load)
AT> FLASH— where the initial values are stored in flash.
The startup code copies from LMA to VMA. Without AT> FLASH, the initial values would have no place to live in flash.
5. Location Counter (.)
The location counter . (a dot) is a special symbol automatically maintained by the linker. It always holds the current output address as the linker processes the SECTIONS command. It increases as sections are added.
Linker Script — Location Counter Examples
SECTIONS
{
/* Set location counter manually */
. = 0x08000000;
.text : {
*(.text)
/* After this, . = 0x08000000 + size_of_.text */
} > FLASH
/* Capture current . as a named symbol */
_etext = .;
/* Align to 4-byte boundary before .data */
. = ALIGN(4);
.data : AT>FLASH
{
_sdata = .; /* capture start = 0x20000000 */
*(.data)
_edata = .; /* capture end */
} > SRAM1
/* ALIGN example: if . = 0x20000001, ALIGN(4) makes it 0x20000004 */
. = ALIGN(4);
}
Important: The location counter (.) only exists inside the SECTIONS command. It cannot be used in MEMORY or elsewhere. It tracks the VMA (runtime address), not the LMA (load address).
6. Linker Script Symbols
Linker script symbols like _sdata, _edata, and _etext are not variables. They are names for addresses. To use them in C code, you must declare them as extern and always take their address — never read their value directly.
Linker Script — Symbol Definition
/* This creates a symbol named _sdata whose value IS the address */
_sdata = .;
/* This is NOT the same as: uint32_t _sdata = some_value; */
C — Using Linker Symbols
/* Declare as extern to reference in C */
extern uint32_t _sdata; /* DO NOT: uint32_t x = _sdata; */
extern uint32_t _edata; /* DO: uint32_t *p = &_sdata; */
extern uint32_t _etext; /* The address IS the value you want */
void copy_data_section(void)
{
uint32_t *src = &_etext; /* address in FLASH where .data init values start */
uint32_t *dst = &_sdata; /* address in SRAM where .data runtime lives */
while (dst < &_edata) {
*dst++ = *src++; /* copy word by word */
}
}
7. Complete Minimal Linker Script
Here is a complete, working linker script for STM32F407VGT6 (1 MB flash, 128 KB SRAM):
stm32f407.ld — Complete Linker Script
/* STM32F407VGT6 Linker Script */
ENTRY(Reset_Handler)
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}
SECTIONS
{
.text :
{
KEEP(*(.isr_vector))
*(.text .text.*)
*(.rodata .rodata.*)
. = ALIGN(4);
} > FLASH
_etext = LOADADDR(.data);
.data :
{
_sdata = .;
*(.data .data.*)
. = ALIGN(4);
_edata = .;
} > SRAM AT> FLASH
.bss :
{
_sbss = .;
*(.bss .bss.*)
*(COMMON)
. = ALIGN(4);
_ebss = .;
} > SRAM
/* Stack top = end of SRAM */
_estack = ORIGIN(SRAM) + LENGTH(SRAM);
}
Shell — Link with Custom Script
arm-none-eabi-gcc \
-mcpu=cortex-m4 -mthumb \
-T stm32f407.ld \
-nostdlib \
-Wl,--gc-sections \
-Wl,-Map=firmware.map \
-o firmware.elf \
main.o startup.o led.o
8. Programming Exercises
Exercise 1 — Verify Section Placement
After linking with the script above, run arm-none-eabi-objdump -h firmware.elf. Verify that .text VMA is 0x08000000, .data VMA is 0x20000000, and .data LMA is immediately after .text in flash.
Exercise 2 — Add a Custom .noinit Section
Some variables should survive a software reset but not power-on (e.g., boot flags). Add a .noinit section that the startup code does NOT zero. Place it in SRAM after .bss.
Linker Script Addition
.noinit (NOLOAD) :
{
*(.noinit)
} > SRAM
C — Using .noinit
/* This variable survives software reset but is not cleared */
uint32_t __attribute__((section(".noinit"))) boot_magic;
Exercise 3 — Map File Analysis
Build the project with -Wl,-Map=firmware.map. Open the map file and find: (a) the address of Reset_Handler, (b) the total size of .text, (c) which object file contributed the most code.
9. FAQ
Quick Knowledge Check
1. What does KEEP(*(.isr_vector)) do in a linker script?
- Encrypts the vector table for security
- Prevents –gc-sections from removing the vector table
- Aligns the vector table to 256 bytes
- Copies the vector table to SRAM
Check Answer
KEEP() tells the linker to include this section even if –gc-sections is enabled and no code explicitly references it.
2. In .data : { ... } > SRAM AT> FLASH, what does AT> FLASH mean?
- Flash should be erased before writing
- The .data section’s initial values are stored (loaded) in FLASH
- .data variables are constant and read from flash directly
- Flash memory is used instead of SRAM for this section
Check Answer
AT> FLASH sets the LMA (load address) to FLASH. The startup code copies from LMA (flash) to VMA (SRAM).
3. What is the location counter (.) in a linker script?
- A global variable tracking number of linker errors
- The current output address, auto-updated as sections are placed
- The program counter register value at reset
- A pointer to the start of the text section
Check Answer
The location counter (.) tracks the current VMA as the linker places sections. You can read it, set it, and use it to define boundary symbols.
Need Help or More Information?
Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments