Intuition What this page is
The parent note told you how the machinery works. Here we turn the crank on real numbers . We will place fake programs into a fake chip, compute every address by hand, and check the arithmetic. By the end you should be able to look at any linker script + any set of variables and say "the binary is N bytes, RAM use is M bytes, and .data copies from address X to address Y."
Before any symbol appears, we fix the vocabulary from the parent so you never guess:
ORIGIN (O ) = the starting physical address of a memory region. It's just "where shelf 1 is."
LENGTH (L ) = how many bytes that region holds.
VMA = the address a thing runs at (where the CPU reaches for it while executing).
LMA = the address a thing is loaded/stored at in the binary image (the "attic" copy in Flash).
. (the location counter) = a running cursor. As the linker places bytes, . advances by that many bytes. Think of it as a tape measure that only moves forward.
Every situation this topic can throw at you falls into one of these cells. The examples below are each tagged with the cell(s) they cover, and together they hit all of them.
#
Cell class
The tricky thing it tests
C1
Pure .text, no variables
LMA = VMA (code runs in place)
C2
.data present
two addresses; Flash image ≠ RAM run
C3
.bss present
size recorded, zero Flash bytes
C4
.data and .bss together
RAM stacks them; Flash holds only .data copy
C5
Zero / degenerate : empty .data or empty .bss
_sdata == _edata; copy loop runs 0 times
C6
Limiting : region exactly full / one byte over
overflow detection, <= L boundary
C7
Alignment forces a gap
. jumps forward; sizes stop being "just the sum"
C8
Word-problem : "how much Flash does adding X cost?"
reasoning about .bss vs .data growth
C9
Exam twist : forgot the copy loop / dropped KEEP
predict the runtime symptom
The two constant facts we lean on everywhere (from the parent's formula callout):
For all examples our chip is:
O F L A S H = 0x08000000 , L F L A S H = 256 K ; O R A M = 0x20000000 , L R A M = 64 K .
(1K = 1024 bytes, so 256K = 262144 bytes, 64K = 65536 bytes.)
Worked example The simplest possible program
A firmware with only machine code: .text = 4000 bytes, no globals, no constants. Where does .text load and run? What's the binary size?
Forecast: guess before reading — is the LMA the same as the VMA here?
Step 1. Place .text at the start of Flash. _stext = O_{FLASH} = \texttt{0x08000000}.
Why this step? The SECTIONS block puts .text first with > FLASH, so . begins at Flash's ORIGIN.
Step 2. Advance the location counter by the section size: . += 4000, so _etext = 0x08000000 + 4000.
Why this step? Every byte placed moves the tape measure . forward by one.
0x08000000 + 4000 = 0x08000FA0 .
(4000 = 0xFA0 .)
Step 3. VMA and LMA are equal because we used > FLASH with no AT>. Code executes directly from Flash.
Why this step? There is no relocation — the parent's third mistake ("VMA and LMA are always the same") is true only for this case .
Verify: Flash image = 4000 + 0 + 0 = 4000 bytes ≤ 262144 . RAM used = 0 (plus stack). ✓ The end address 0x08000FA0 is 0x08000000 + 4000 — decode: 0xFA0 = 15\cdot256 + 10\cdot16 + 0 = 3840+160 = 4000. ✓
Worked example The two-address dance
.text = 4000 bytes. One global: int x = 5; → .data = 4 bytes (a 32-bit int). Find _sidata (LMA), _sdata (VMA), _edata, and the binary size.
Forecast: will .data sit right after .text in Flash, in RAM, or both?
Step 1. Flash cursor is at 0x08000000 + 4000 = 0x08000FA0 after .text.
Why? Same as Example 1 — .data's load address (AT> FLASH) continues the Flash cursor.
So _sidata = LOADADDR(.data) = \texttt{0x08000FA0}.
Step 2. But .data's run address uses > RAM, so its VMA starts at RAM's ORIGIN:
_sdata = ADDR(.data) = O_{RAM} = \texttt{0x20000000}.
Why? > RAM resets the run cursor to RAM; AT> FLASH keeps the load cursor in Flash. Two independent tape measures.
Step 3. Advance by 4 bytes: _edata = 0x20000000 + 4 = 0x20000004.
Why? .data is 4 bytes, so the RAM cursor moves 4.
Step 4. At boot the copy loop runs SIZEOF(.data)/4 = 1 iteration, copying the 4-byte value 5 from 0x08000FA0 (Flash) to 0x20000000 (RAM).
Why? The parent's startup loop for(p=&_sidata,q=&_sdata; q<&_edata;) *q++=*p++; moves words until q reaches _edata.
Verify: Flash = 4000 + 4 = 4004 bytes (the value 5 is stored, so .data costs Flash). RAM = 4 bytes. Copy count = ( 0 x 20000004 − 0 x 20000000 ) /4 = 1 . ✓
.bss stores nothing in Flash
.text = 4000, no .data, one int y; (uninitialized) → .bss = 4 bytes. Find binary size and RAM use.
Forecast: does the binary get bigger than 4000 because y exists?
Step 1. .bss uses > RAM only (no AT>). So _sbss = O_{RAM} = 0x20000000, _ebss = 0x20000004.
Why? Nothing needs loading from Flash — there's no initial value to store.
Step 2. The linker records only the size (4 bytes), emitting zero Flash bytes for .bss.
Why? Storing 4 zeros in Flash is wasteful; the startup loop writes them into RAM instead (parent's .bss intuition).
Step 3. Boot zero-loop runs 4/4 = 1 iteration: *q++ = 0 writing 0x20000000.
Verify: Flash = 4000 + 0 = 4000 bytes (unchanged from Example 1 despite y existing — kills mistake #1). RAM = 4 bytes. ✓
Worked example The full RAM layout
.text = 4000, .rodata = 200, .data = 40 bytes (10 ints), .bss = 100 bytes. Compute every symbol and both totals.
Forecast: in RAM, does .bss start at 0x20000000 or after .data?
Step 1 — Flash cursor. .text then .rodata then .data's LMA all stack in Flash:
.text: 0x08000000 → 0x08000FA0 (+4000).
.rodata: 0x08000FA0 → 0x08001068 (+200, 0xC8).
.data LMA: _sidata = 0x08001068.
Why? Flash cursor never resets; every > FLASH / AT> FLASH section appends.
Step 2 — RAM cursor for .data. _sdata = 0x20000000, _edata = 0x20000000 + 40 = 0x20000028.
Why? > RAM resets run cursor to RAM ORIGIN.
Step 3 — .bss follows .data in RAM. _sbss = _edata = 0x20000028, _ebss = 0x20000028 + 100 = 0x2000008C.
Why? The RAM cursor is shared by .data and .bss — .bss continues where .data ended (only the Flash and RAM cursors are separate; within RAM everything stacks).
Verify: Flash = 4000 + 200 + 40 = 4240 bytes (.bss contributes 0). RAM = 40 + 100 = 140 bytes. `_ebss - _sdata = 0x2000008C - 0x20000000 = 0x8C = 140. ✓ C o p y l oo p : 40/4=10w or d s ; z er o l oo p : 100/4=25$ words. ✓
Worked example When a section has zero size
A program with .text = 4000 and .bss = 20, but no initialized globals (.data = 0 bytes). What do _sdata and _edata equal, and how many times does the copy loop run?
Forecast: does the boot copy loop crash or hang when there's nothing to copy?
Step 1. .data size = 0, so _sdata = _edata (the cursor doesn't move). Both equal 0x20000000.
Why? Placing zero bytes advances . by zero — start and end coincide.
Step 2. The copy loop condition is q < &_edata. Since q starts at _edata, the body runs 0 times .
Why this matters? An empty section is not a bug — the < comparison naturally skips it. No special case needed.
Step 3. .bss still zeroes: _sbss = 0x20000000, _ebss = 0x20000014 (+20). Zero loop runs 20/4 = 5 times.
Verify: Flash = 4000 (no .data image). RAM = 0 + 20 = 20 . Copy iterations = ( _ e d a t a − _ s d a t a ) /4 = 0 . ✓
Worked example The boundary of overflow
.data = 20000 bytes, .bss = 40000 bytes, stack = 4096, heap = 1024. Does it fit in 64K RAM? Then we add one more int to .bss. Now?
Forecast: 64K is 65536. Add up and guess whether the linker errors.
Step 1. RAM used = 20000 + 40000 + 4096 + 1024 = 65120 bytes.
Why? The parent's inequality: RAM = data + bss + stack + heap ≤ L R A M .
Step 2. Compare to L R A M = 65536 . Slack = 65536 − 65120 = 416 bytes. Fits. ✓
Why? 65120 ≤ 65536 , so the linker is happy.
Step 3. Add one int (4 bytes) to .bss: new .bss = 40004, total = 65124 ≤ 65536 . Still fits (slack 412).
Why the twist? The exam version usually makes stack + .bss grow into each other. To force overflow we'd need > 416 more bytes.
Step 4 (the overflow case). Suppose instead .bss jumps to 40500 (+500). Total = 65620 > 65536 . The linker aborts:
region RAM overflowed by 84 bytes.
Why 84? 65620 − 65536 = 84 . That is exactly what ld prints.
Verify: 65120 ≤ 65536 (fits). 65620 − 65536 = 84 (the overflow amount). ✓
Worked example When sizes stop being a plain sum
.text ends at Flash offset 4002 (an odd-ish size). .data (AT> FLASH) requires 4-byte alignment (. = ALIGN(4)). Where does .data's LMA land, and how many padding bytes appear in the binary?
Forecast: does .data load at 4002, or does it get bumped forward?
Step 1. After .text: Flash cursor at 0x08000000 + 4002 = 0x08000FA2.
Why? Cursor = ORIGIN + accumulated bytes.
Step 2. ALIGN(4) rounds . up to the next multiple of 4. 0xFA2 = 4002. Next multiple of 4 above 4002 is 4004.
Why align? A 32-bit load from a misaligned address faults on many cores (and the copy loop moves 4-byte words). Aligning guarantees clean word access.
padded LMA = 0 x 08000000 + 4004 = 0 x 08000 F A 4.
Step 3. Padding bytes inserted = 4004 − 4002 = 2 . These 2 bytes are real filler in the .bin.
Why it breaks naive sums: Flash image is now 4002 + 2 ( pad ) + size . d a t a , not just 4002 + size . d a t a .
Verify: ⌈ 4002/4 ⌉ ⋅ 4 = 4004 . Padding = 4004 − 4002 = 2 . ✓
Worked example Flash budget reasoning
You have 200 bytes of Flash headroom left. Your teammate wants to add either (a) static uint8_t lut[300] = {...}; (a 300-byte initialized lookup table) or (b) static uint8_t scratch[300]; (a 300-byte zeroed buffer). Which one fits?
Forecast: both are 300 bytes of RAM. Which one touches Flash?
Step 1. Option (a) is initialized → goes in .data. Its 300 bytes of initial values are stored in Flash.
Why? .data has a Flash LMA (the copy source). 300 > 200 headroom → does NOT fit in Flash.
Step 2. Option (b) is zero-initialized → goes in .bss. It costs 0 Flash bytes (only size recorded).
Why? From mistake #1: .bss emits no bytes. Flash headroom untouched → fits.
Step 3. Both cost 300 bytes of RAM at runtime, so check RAM separately — but the Flash question is decided purely by initialized vs zero.
Verify: Flash cost (a) = 300 > 200 → fails. Flash cost (b) = 0 ≤ 200 → passes. ✓
Worked example Two classic boot bugs
(a) The engineer deletes the .data copy loop from Reset_Handler. A global int mode = 3; is read in main(). What value does it hold? (b) The KEEP(*(.isr_vector)) is removed and link-time garbage collection is on. What happens at reset?
Forecast: will (a) crash, or silently misbehave?
Step 1 — case (a). Without the copy, RAM at _sdata was never written. It holds whatever value RAM powered up with — undefined garbage , not 3.
Why? The value 3 sits in Flash (LMA) but the VMA in RAM never received it. This is the parent's "works in debug, breaks in release" bug.
Symptom: mode is a random number; behaviour changes across power cycles. No crash — silent corruption.
Step 2 — case (b). Nothing references .isr_vector by symbol, so GC deletes it. Flash address 0 (mapped from 0x08000000) now holds unrelated bytes.
Why fatal? On Cortex-M reset the CPU reads the initial stack pointer and reset vector from the very first words of Flash. Garbage there → immediate hard fault / boot loop .
Symptom: dead board, never reaches main().
Verify (conceptual): (a) → mode ≠ 3 (undefined); (b) → hard fault at boot. Both match the parent's mistakes #2 and #4. ✓ (numeric checks below cover the arithmetic already used.)
Recall Quick self-test
In Example 4, what is _ebss? ::: 0x2000008C = 0x20000000 + 140.
In Example 5, how many times does the .data copy loop run? ::: Zero — _sdata == _edata.
In Example 6, by how many bytes does RAM overflow when .bss = 40500? ::: 84 bytes.
In Example 7, how many padding bytes does ALIGN(4) insert after a 4002-byte .text? ::: 2 bytes.
In Example 8, which option keeps Flash headroom? ::: (b), the zeroed .bss buffer — 0 Flash cost.
Mnemonic The one-line summary of every example
"Flash counts .text + .rodata + .data; RAM counts .data + .bss + stack + heap; .bss is free in Flash; .data is the only thing living twice."
Parent: Linker scripts — memory regions, sections (.text, .data, .bss)
The boot copy/zero loops: Startup code & Reset_Handler
Why .isr_vector must survive GC: Vector table & interrupt handling
Where .data/.bss come from: Compilation pipeline — object files & relocation
The physical tradeoff behind two memories: Flash vs RAM tradeoffs
RAM budget's other tenants: Stack vs Heap in embedded systems
What the .bin actually contains: ELF file format & sections