Linker Scripts: ENTRY, MEMORY & SECTIONS-Embedded C Training Institute in Hyderabad

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

bedde

ARM Cortex-M Programming← PREV_LECNEXT_LEC →

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.

Pages 311–320Linker ScriptMEMORY CommandSECTIONS CommandLocation Counter

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 header
  • MEMORY{} — declares memory regions with addresses and sizes
  • SECTIONS{} — maps input sections to output sections and assigns addresses
  • KEEP() — prevents linker from removing unused sections (important for vector table)
  • ALIGN() — aligns the location counter to a boundary
  • AT>() — 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

LetterMeaning
RRead-only sections allowed
WRead/write sections allowed
XExecutable sections allowed
AAllocated sections
I or LInitialized 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 .o files 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

What happens if a section doesn’t fit in the memory region?

The linker emits an error: “region FLASH overflowed by X bytes”. This is extremely useful — it tells you exactly how much your code exceeds the available flash. You can then reduce code size (smaller libraries, -Os optimization, removing unused features) or check if you have the right memory sizes in the MEMORY command.

Why do we use LOADADDR(.data) instead of _etext = . ?

When using > SRAM AT> FLASH, the location counter (.) is tracking the VMA (SRAM address) after .text. LOADADDR(.data) explicitly returns the LMA (FLASH address) of the .data section — which is what we need to copy from. Using . = after .text gives the VMA, which is in SRAM, not the flash address we want for the load copy.

What is -Wl,–gc-sections and why should I use it?

-Wl,--gc-sections tells the linker to remove (“garbage collect”) any sections not reachable from the entry point. Combined with -ffunction-sections -fdata-sections (which places each function/variable in its own section), this can significantly reduce binary size by eliminating unused library functions. Always use KEEP(.isr_vector) in your linker script when gc-sections is enabled, to prevent the vector table from being removed.

Quick Knowledge Check

1. What does KEEP(*(.isr_vector)) do in a linker script?

  1. Encrypts the vector table for security
  2. Prevents –gc-sections from removing the vector table
  3. Aligns the vector table to 256 bytes
  4. Copies the vector table to SRAM
Check Answer
Correct answer: B) Prevents –gc-sections from removing the vector table
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?

  1. Flash should be erased before writing
  2. The .data section’s initial values are stored (loaded) in FLASH
  3. .data variables are constant and read from flash directly
  4. Flash memory is used instead of SRAM for this section
Check Answer
Correct answer: B) The .data section’s initial values are stored (loaded) in FLASH
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?

  1. A global variable tracking number of linker errors
  2. The current output address, auto-updated as sections are placed
  3. The program counter register value at reset
  4. A pointer to the start of the text section
Check Answer
Correct answer: B) The current output address, auto-updated as sections are placed
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.

EmbeddedPathashala — Embedded Systems Programming on ARM Cortex-M3/M4

Need Help or More Information?

Questions about this free C programming course, embedded C training or career guidance? Get in touch.

2 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *