Understanding the Linker Script & Memory Map (Map File)
When working in bare-metal embedded development, we don’t have an operating system managing memory for us. Every instruction and variable must be placed at the correct physical memory address. This is where the linker script and map file become essential.
In this article, we will walk through a real linker script used for the Tiva TM4C123GH6PM microcontroller, understand what each section means, and then see how the map file helps us verify memory placement. Finally, we relate everything to a simple main.c application that toggles an LED.
1. The Linker Script: Defining How Code and Data Are Placed in Memory
The linker script tells the linker where to place different parts of the program in Flash and RAM.
Target and Entry Point
OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(Reset_Handler)
This configures the output file format, target CPU architecture, and specifies that execution begins at Reset_Handler, which is part of the startup code.
Defining Physical Memory Regions
MEMORY {
ROM (rx) : ORIGIN = 0x00000000, LENGTH = 256K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 32K
}
ROM = Flash memory (read + execute) RAM = volatile memory (read + write + execute)
This defines the real memory layout of the MCU.
Stack and Heap Allocation
STACK_SIZE = 2048;
HEAP_SIZE = 0;
We allocate 2 KB stack and disable heap, since malloc is not used.
Interrupt Vector Table in Flash
.isr_vector : {
KEEP(*(.isr_vector))
. = ALIGN(4);
} >ROM
The vector table must be at the beginning of Flash so the CPU can fetch reset and exception addresses.
Code and Constants in Flash
.text : {
. = ALIGN(4);
*(.text)
*(.text*)
*(.rodata)
*(.rodata*)
. = ALIGN(4);
} >ROM
_etext = .;
.text holds program instructions. .rodata holds constant data. _etext marks where this section ends.
Stack Placement in RAM
.stack : {
__stack_start__ = .;
. = . + STACK_SIZE;
__stack_end__ = .;
} >RAM
Reserves dedicated stack space.
Initialized Variables (.data) in RAM but Stored in Flash
.data : AT(_etext) {
__data_start = .;
*(.data)
*(.data*)
__data_end__ = .;
} >RAM
.data contains global variables with initial values. They are stored in Flash and copied to RAM at startup.
Uninitialized Variables (.bss) in RAM
Recommended by LinkedIn
.bss : {
__bss_start__ = .;
*(.bss)
*(.bss*)
*(COMMON)
__bss_end__ = .;
} >RAM
.bss contains all variables without initial values and is zero-initialized by the startup code.
2. Relating This to the Application (main.c)
const uint8_t a;
const uint8_t b;
These are const, so they go to .rodata in Flash.
uint8_t x = 'A';
uint32_t y = 1;
These have initial values, so they go to .data (copied to RAM).
uint8_t c;
uint16_t d;
No initial value → placed in .bss and zero-initialized.
The LED blink logic executes from .text, and GPIO registers are accessed through memory-mapped I/O.
This demonstrates how different variable types map directly to physical memory regions.
3. The MAP File: Your Verification Tool
After linking, the compiler generates a .map file that shows:
• Where each symbol and section is located in memory
• How much Flash and RAM is used
• Start and end addresses of each section
This file is critical for:
• Confirming .text, .data, .bss, heap, and stack fit within limits
• Ensuring vector table is correctly placed at address 0x00000000
• Debugging startup code issues
• Avoiding silent memory corruption or unexpected resets
Every firmware engineer should inspect the map file before flashing the MCU.
Key Takeaways
Full Startup Implementation & Notes
Great share! If anyone wants quick access to curated experts, here’s a direct link: https://gopluto.ai/user-query/lesson-understanding-memory-ad29