Worked examples — Startup code — vector table, reset handler, stack initialization
Everything here builds on the parent topic. When we touch how the linker hands us addresses, that's Linker scripts and memory sections (.text .data .bss); when we touch how the CPU stacks registers, that's the ARM Cortex-M exception and interrupt model.
Before we start: the three symbols every example uses
We keep meeting three families of names. Let's pin them to a picture so no symbol is ever a mystery.

The scenario matrix
Every case class this topic can throw at you, and which worked example covers it:
| # | Case class | Concrete stimulus | Example |
|---|---|---|---|
| A | Normal load — both sections non-empty | .data has 2 words, .bss has 1 word |
Ex 1 |
| B | Empty .data (degenerate copy) |
no initialized globals → _sdata == _edata |
Ex 2 |
| C | Empty .bss (degenerate zero) |
every global is initialized → _sbss == _ebss |
Ex 2 |
| D | Stack boundary / first push | trace MSP after entry and first push |
Ex 3 |
| E | Sign / direction of growth | full-descending SP, does it overflow up or down? | Ex 3 |
| F | Collision limit — stack vs heap | how much can the stack grow before it hits .bss? |
Ex 4 |
| G | Relocated table (VTOR ≠ 0) — bootloader case | app's table at 0x0800_4000, what does hardware read? |
Ex 5 |
| H | Real-world word problem | a sensor buffer as a global — where does its data come from? | Ex 6 |
| I | Exam twist — the .bss-not-cleared heisenbug |
predict the bug's symptom | Ex 7 |
| J | Exam twist — misaligned _estack |
_estack odd/not word-aligned, what faults? |
Ex 8 |
We now hit every cell.
Setup. 20 KB RAM: 0x2000_0000 … 0x2000_4FFF, so _estack = 0x2000_5000. The CPU is full-descending (SP points at the last item used; a push first decrements by 4, then writes). Where does the first pushed word land?
Forecast: at 0x2000_5000? at 0x2000_4FFC? somewhere else?
- Hardware loads MSP. Word @
0x0000_0000=0x2000_5000→ MSP. Why this step? On Cortex-M the very first thing hardware does is set the stack pointer, before any code runs (see figure). - First push happens. SP SP , then the word is written there. Why decrement first? "Full-descending" means SP always points at valid data; to add new data we must first make room below by subtracting.
- Direction check. Each further push subtracts 4 → addresses go down toward
0x2000_0000. Why down? So the stack and a heap growing up from the low end move toward each other, giving one predictable collision point.

Verify: the highest byte a full-descending stack ever writes is _estack - 1 = 0x2000_4FFF — the last real RAM byte, never 0x2000_5000 (which is outside RAM). Off-by-one is correct: initial value is end+1 precisely so the first subtract lands on the last valid word. ✓
Setup. Same 20 KB RAM. .data + .bss together occupy 0x2000_0000 … 0x2000_07FF (2 KB). The heap starts right after at 0x2000_0800 and (for this snapshot) is empty. _estack = 0x2000_5000. What is the maximum stack depth in bytes before it corrupts .bss/heap?
Forecast: 18 KB? 20 KB? less?
- Identify the two frontiers. Stack top-of-growth reaches down from
0x2000_5000; the region below it that must stay safe ends at the top of used low memory0x2000_0800. Why this step? A collision is exactly when the descending SP crosses into occupied low memory. - Compute the gap. bytes KB. Why subtract? The free window between "stack start" and "first occupied byte below" is the maximum descent.
- Convert to entries. words of 32-bit pushes. Why /4? Each push moves SP by one word = 4 bytes.
Verify: (data/bss/heap) KB total RAM. Accounts for every byte. If the linker's _Min_Stack_Size guard were set to 18 KB, this would just fit; 18 KB + 1 byte would fail the link — a clean compile-time catch instead of a silent overflow. ✓
Setup. A bootloader lives at 0x0800_0000. It jumps to an application whose vector table is at 0x0800_4000. The app's table word 0 = 0x2000_5000, word 1 = 0x0800_4101 (Reset_Handler, LSB set for Thumb). The bootloader sets VTOR = 0x0800_4000, then triggers the app's reset path. After VTOR is set, which stack pointer and which entry point does the app use?
Forecast: does the CPU still read 0x0000_0000, or the new table?
- VTOR is the table's base.
VTOR(Vector Table Offset Register) tells the CPU where entry 0 lives. AfterVTOR = 0x0800_4000, "word 0" means[0x0800_4000]. Why this step? Without VTOR the table is fixed at0x0000_0000; VTOR lets two independent tables (boot + app) coexist. - Load the app's SP. The bootloader manually does
MSP = *(uint32_t*)0x0800_4000 = 0x2000_5000. Why manually? A software jump doesn't re-trigger the hardware reset sequence, so the bootloader must copy word 0 into MSP itself. - Jump to the app's reset handler.
PC = *(uint32_t*)0x0800_4004 = 0x0800_4101; the CPU masks the LSB to get the real address0x0800_4100and stays in Thumb state. Why mask? On Cortex-M the low bit is the Thumb flag, not part of the address; the real entry is0x0800_4100.
Verify: entry address = 0x0800_4101 & ~1 = 0x0800_4100; the app's SP = 0x2000_5000 matches Ex 3's _estack. The +0x4000 offset shifts entry 1's file location but the SP value is data, unchanged. ✓
Setup. A firmware has a global calibration array:
static const float k[3] = {1.0f, 0.5f, 0.25f}; used read-only, and a working buffer static float acc[3]; accumulated at runtime. Which array is copied by the reset handler, which is zeroed, and what does acc[0] read in the first line of main?
Forecast: name each array's section first.
- Classify
k. It'sconstand initialized →.rodatain Flash. It is not copied to RAM (it's read directly from Flash). Why? Const data never changes, so keeping it in Flash saves RAM; the reset handler doesn't touch.rodata. - Classify
acc. Uninitialized (no= {...}) →.bss. Why? No initial values means storing zeros in Flash would be wasteful;.bssis the zero-cleared region. - Predict
acc[0]inmain. The.bssloop wrote0x0000_0000to all ofacc→acc[0] == 0.0f. Why? The bit pattern0x00000000is exactly IEEE-754+0.0, so a zeroed float reads as0.0.
Verify: k stays in Flash (0 RAM copies for it); acc occupies bytes of .bss, all set to 0; acc[0] == 0.0f. Had acc been .data with initializer {9,9,9}, it'd instead be copied from _sidata. ✓
Setup. Startup code copies .data correctly but the .bss loop is missing. A global static int errors; (in .bss) is used as if (errors > 0) reboot();. On the author's board it never reboots; on a colleague's identical board it reboots at random. Explain and predict.
Forecast: which line "randomly" triggers?
errorsstarts as garbage. With no zeroing,errorsholds whatever the RAM cell contained at power-up — undefined. Why this step? SRAM power-up state is not guaranteed 0; it depends on the silicon and the last power state.- Author's luck. That cell happened to be
0on their board, soerrors > 0is false → no reboot. Why? Undefined ≠ random-every-time; a given chip may reliably power up as 0, hiding the bug. - Colleague's failure. A different die powers up with
errors = 0x0000_0004(say) →errors > 0true → spurious reboot. Why? Same source code, different physical garbage → the classic non-reproducible heisenbug.
Verify: with the fix, .bss zeroing forces errors = 0 on every board → errors > 0 deterministically false at start. The symptom vanishes because "undefined" became the C-guaranteed 0. ✓
_estack
Setup. A student edits the linker script and sets _estack = 0x2000_4FFE (not divisible by 4). RAM ends at 0x2000_4FFF. On the first exception, what happens?
Forecast: works? HardFault? silent?
- State the alignment rule. Cortex-M requires SP to be 8-byte aligned at an exception boundary (and word-aligned always);
0x2000_4FFEis not even word-aligned (). Why this step? The hardware auto-stacks registers using SP; a misaligned SP means unaligned 32-bit writes. - Predict the fault. The first push/auto-stack to an unaligned address raises a UsageFault (or escalates to HardFault if UsageFault is disabled). Why? The exception model treats an unaligned stack as an error, not a wraparound.
- The fix. Round
_estackdown to a word boundary:0x2000_5000(or the nearest ). Why? , satisfying both the word and the 8-byte rule.
Verify: → misaligned, faults. → aligned, safe. The corrected value is exactly the "end+1 rounded to 8" convention. ✓
Recall Did every matrix cell get hit?
Normal load (A) ::: Ex 1 Empty .data / empty .bss (B, C) ::: Ex 2 First push & growth direction (D, E) ::: Ex 3 Stack↔heap collision limit (F) ::: Ex 4 Relocated table via VTOR (G) ::: Ex 5 Real-world const-vs-bss (H) ::: Ex 6 .bss heisenbug symptom (I) ::: Ex 7 Misaligned _estack fault (J) ::: Ex 8
Copy Zero Call, top-down stack"
Copy .data → Zero .bss → Call main; and the stack lives at the top, growing down.
Connections used here
- Linker scripts and memory sections (.text .data .bss) — source of every
_s…/_e…symbol. - ARM Cortex-M exception and interrupt model — the auto-stacking and alignment rules in Ex 3 & Ex 8.
- Stack vs Heap memory layout — the collision geometry in Ex 4.
- Bootloaders and VTOR relocation — the relocated table in Ex 5.
- Volatile, memory-mapped registers and hardware init — how
VTORitself is written. - The C runtime and crt0 — the bigger picture the reset handler slots into.