Linker Scripts, Sections, and Memory Maps
TL;DR
- You’ll learn what the linker really does: it places code/data into addresses according to rules and produces an ELF where virtual addresses match the target memory map.
- You’ll learn how
.text,.rodata,.data,.bss, and startup code relate to VMA (Virtual Memory Address) and LMA (Load Memory Address). - You’ll write a linker script for a small bare-metal RV32 program, generate a link map (
.map), and convert the ELF into a raw.bin.
1. The linker’s job (a practical definition)
Given:
- object files (
.o) containing sections and relocations, - libraries (optional),
- a memory layout,
The linker produces:
- an executable ELF with final addresses,
- resolved symbols,
- and (optionally) a map file that explains every placement decision.
2. Sections you must know
| Section | Meaning | File bytes? | Memory bytes? |
|---|---|---|---|
.text | code | yes | yes |
.rodata | read-only constants | yes | yes |
.data | initialized globals | yes | yes |
.bss | zero-initialized globals | no | yes |
.bss has no bytes in the file because it’s represented as “allocate N bytes and fill with zeros”.3. VMA vs LMA (why there can be two addresses)
- VMA (Virtual Memory Address): where the bytes live when the program runs.
- LMA (Load Memory Address): where the bytes are stored in the image that loads them.
In bare-metal firmware, a common pattern is:
- code runs from RAM,
- but
.datais stored in flash and copied into RAM at boot.
That implies:
.dataVMA is in RAM,.dataLMA is in flash.
We’ll use a simpler single-region RAM layout first, then explain the “copy down” idea.
4. Hands-on: a bare-metal RV32 program with explicit placement
Create:
| |
Create a linker script:
/* src/ld_demo.ld */
OUTPUT_ARCH(riscv)
ENTRY(_start)
MEMORY {
RAM (rwx) : ORIGIN = 0x80000000, LENGTH = 16M
}
SECTIONS {
. = ORIGIN(RAM);
.text : {
*(.text .text.*)
} > RAM
.rodata : {
*(.rodata .rodata.*)
} > RAM
.data : {
*(.data .data.*)
} > RAM
.bss : {
*(.bss .bss.*)
*(COMMON)
} > RAM
}
Build:
| |
5. Read the map file like a detective
Open the map:
| |
Things to find:
- the address range assigned to
.text - where
.dataended up - where
.bssstarts - symbol addresses for
_start,g_counter,g_init
A map file is an answer key to “why is this at this address?”.
6. Verify section addresses with readelf
| |
You should see:
_startat or near0x80000000(depending on alignment)g_initin.datag_counterin.bss
7. ELF → raw binary and the “no addresses” trap
Create a .bin:
| |
Now compare sizes:
| |
.bin has no symbol table, no section headers, and no idea where it belongs in memory. You must supply the load address externally (boot ROM, bootloader, SoC docs, etc.).8. Alignment and why it matters
Two common alignment realities:
- instructions and data have natural alignments (word aligned, etc.)
- linkers align sections so code and data remain efficient
If you ever see weird gaps, it’s often alignment.
You can force alignment explicitly:
.text : {
. = ALIGN(16);
*(.text .text.*)
} > RAM
9. A preview of startup code (what we’re skipping for now)
In real bare-metal systems, there’s usually a startup routine that:
- sets up the stack pointer
- copies
.datafrom ROM/flash into RAM - zeros
.bss - then calls
main
We’re intentionally keeping early examples minimal so you can see placement without extra machinery.
Exercises
- Change
ORIGIN(RAM)from0x80000000to0x80200000. Verify_startmoved. - Add a new
const char msg[] = "hi";and confirm it appears in.rodata. - Generate a disassembly and confirm that loads/stores to
g_counteruse its absolute address (or a sequence that computes it). - Try
-Wl,--gc-sectionsalong with-ffunction-sections -fdata-sectionsand observe what unused code/data is removed.
How to test your answers
- Use
readelf -Sandreadelf -sto validate placement. - Use the map file as the ground truth.
Summary
You learned how linker scripts define a memory map, how sections are placed, and how to validate placement with map files and readelf.
Next: floating point, endianness, and bit packing-we’ll explain why double constants sometimes show up as two .word values and how to verify them with Python.