Memory Map, Linker Scripts & Build Process

Where does your code actually go? Learn the compile–assemble–link flow, object files and ELF, the MCU memory map (Flash, SRAM, peripherals), sections (.text, .rodata, .data, .bss, stack, heap), what the linker script does, how startup code initialises RAM, reading map files and size output, and stack sizing and overflow detection.

On a PC, the operating system loads your program anywhere in virtual memory. On a microcontroller there is no loader: the linker decides the exact address of every function and variable, and the image is burned into Flash at fixed addresses.

Understanding sections and the linker script lets you answer practical questions: Why did RAM usage grow when I initialised an array? Where is my stack? Why does a variable keep its value after reset? How do I place a function in RAM for faster execution?

An analogy

The linker is an architect assigning rooms in a building: code goes to the read-only library (Flash), variables to offices (RAM), and the stack gets a corridor that grows downward — the architect must make sure nothing overlaps.

From source to binary

The toolchain runs four stages:

Which section does my variable land in?

The declaration decides:

Stack and heap

The stack grows downward from _estack; every function call pushes a frame (return address, saved registers, locals). Recursion and large local arrays blow the stack silently, corrupting .bss/.data below it. Fill the stack with a pattern (0xDEADBEEF) at boot and measure the 'high-water mark', use the MPU or stack limit registers (Cortex-M33 MSPLIM) to trap overflow, and use -fstack-usage for static estimates.

Advanced placement

Custom sections place code in fast RAM (__attribute__((section(".ramfunc")))), put DMA buffers in specific SRAM banks, reserve a no-init area that survives soft reset (for crash logs), or split Flash into bootloader and application regions. Link-time optimisation (-flto), -ffunction-sections with --gc-sections, and -Os shrink images substantially.

Formulas

Worked example: Reading size output

Given: arm-none-eabi-size: text 12345, data 256, bss 2048

Find: Flash and static RAM.

Answer: 12.3 KB Flash, 2.25 KB RAM (+ stack/heap)

Worked example: Moving a table to Flash

Given: uint16_t sine[256] = {…}; (not const)

Find: Effect of adding const.

Answer: Saves 512 B of RAM

Worked example: Stack budget

Given: Deepest call chain 640 B; Two nested ISRs using 96 B each incl. frames

Find: Stack size with 25% margin.

Answer: Reserve ≥ 1 KB (round up to 1.5–2 KB)

A minimal GNU linker script (STM32-class MCU)

MEMORY
{
  FLASH (rx)  : ORIGIN = 0x08000000, LENGTH = 256K
  RAM   (rwx) : ORIGIN = 0x20000000, LENGTH = 64K
}

_estack = ORIGIN(RAM) + LENGTH(RAM);     /* stack starts at top of RAM */
_Min_Stack_Size = 0x1000;                /* 4 KB reserved and checked  */

SECTIONS
{
  .isr_vector : { KEEP(*(.isr_vector)) } > FLASH   /* vector table first */
  .text       : { *(.text*) *(.rodata*) } > FLASH
  _sidata = LOADADDR(.data);                        /* init values in Flash */

  .data : { _sdata = .; *(.data*) _edata = .; } > RAM AT > FLASH
  .bss  : { _sbss  = .; *(.bss*) *(COMMON) _ebss = .; } > RAM

  ._stack_check : { . = . + _Min_Stack_Size; } > RAM /* link fails if no room */
}

.data has a run address in RAM but is stored (loaded) in Flash — 'AT > FLASH'. The startup code copies it from _sidata to _sdata… and zeroes .bss before main().

Startup: initialising RAM before main()

extern uint32_t _sidata, _sdata, _edata, _sbss, _ebss;

void Reset_Handler(void)
{
    uint32_t *src = &_sidata, *dst = &_sdata;
    while (dst < &_edata) *dst++ = *src++;   /* copy .data Flash -> RAM */
    for (dst = &_sbss; dst < &_ebss; )  *dst++ = 0;   /* zero .bss      */

    SystemInit();                            /* clocks, FPU, etc.       */
    main();
    for (;;) { }                             /* main must never return  */
}

This is why a global 'int count = 5;' costs both Flash (the initial 5) and RAM, while 'int count;' only costs RAM (.bss).

Common mistakes

Applications

Key takeaways

Going further