5.5.16 · D5Embedded Systems & Real-Time Software

Question bank — Startup code — vector table, reset handler, stack initialization

1,804 words8 min readBack to topic

Before you start, three plain-word reminders so no term below is unearned:


True or false — justify

Every entry in the Cortex-M vector table is a function pointer the CPU can jump to.
False — entry 0 is the initial stack-top value, a plain 32-bit number the hardware loads into MSP. It only has function-pointer type in C so the array is uniform; the hardware never jumps to it.
The reset handler must run before main() because the C standard assumes an environment main() cannot build for itself.
True — int g=42; assumes g already holds 42 and int z; assumes z is 0, but nothing in main's own code copies .data or clears .bss; that work must already be finished when main starts.
On a full-descending stack, pushing a value first writes the value, then decrements the stack pointer.
False — full-descending means decrement first, then write. SP points at the last used slot, so to make a new slot you must move SP down before storing (see Stack vs Heap memory layout).
_estack equals the lowest address of RAM.
False — _estack is the highest RAM address + 1 (the end), because a descending stack starts at the top and grows downward toward the heap.
If you zero .bss but forget to copy .data, the program still behaves correctly.
False — initialized globals like int g=42; would read back Flash-time garbage (whatever RAM held at power-up), because nothing put 42 into their RAM slot.
Storing zeros for .bss variables in the Flash image would be harmless, just slightly wasteful.
Mostly true but the point is the waste: it bloats the binary with runs of zeros and lengthens Flash programming; that is exactly why .bss is defined as "clear at runtime" rather than "load from Flash."
The vector table can only ever live at address 0x00000000.
False — after reset a bootloader can set VTOR (the Vector Table Offset Register) to point the table elsewhere, e.g. into an application region higher in Flash.
If main() returns, execution simply stops cleanly.
False — there is no OS to return to, so returning from main runs off into undefined memory. The reset handler ends with while(1){} to trap that case deliberately.

Spot the error

void (*vt[])(void) = { Reset_Handler, &_estack, ... }; — what is wrong with this ordering?
The two entries are swapped: word 0 must be the stack-top &_estack and word 1 the Reset_Handler. As written the hardware loads a code address into MSP and jumps to a stack address as if it were code — instant fault.
A student places vector_table in .data instead of .isr_vector. Why does the chip never boot?
.data lives in RAM and is only populated by the reset handler — but the hardware reads the vector table from its fixed boot location before any code runs, so at reset that table isn't there yet. It must sit at the boot address in Flash.
The copy loop reads while (dst < &_edata) *dst++ = *src++; but src was initialized to &_sdata instead of &_sidata. What breaks?
It copies RAM onto RAM instead of copying the Flash init image into RAM, so .data variables get whatever undefined RAM already held — the initial values from Flash are never used.
for (dst=&_sbss; dst < &_ebss; ) *dst++ = 0; is replaced with dst < &_ebss; dst++ in the header and *dst = 0; in the body. Any bug?
No bug — that is just the same loop written with the increment in the for header instead of the body. Both walk every word of .bss and store 0; watch only that you don't accidentally increment twice.
Someone sets the reset vector directly to main to "save a jump." What silently breaks?
.data is never copied and .bss never cleared, so initialized globals hold garbage and zero-init globals hold random RAM — C's startup guarantees are quietly violated even though the code compiles and links (see the crt0 note).
__libc_init_array() is called before the .data copy loop. Why is that dangerous?
Static/C++ constructors may read or write initialized globals; if .data hasn't been copied yet, those constructors see garbage. Environment setup (.data/.bss) must complete before any constructor runs.

Why questions

Why does the hardware load the stack pointer before fetching the first instruction?
Because on Cortex-M the hardware itself pushes registers onto the stack when an exception arrives, so MSP must be valid from cycle zero — even the very first interrupt would corrupt memory otherwise (see ARM Cortex-M exception and interrupt model).
Why is a vector table used instead of the CPU searching for the right handler?
A table is an O(1) indexed lookup — exception number n directly picks table[n], giving the fastest possible, fully-deterministic dispatch with no scanning, which real-time code demands.
Why must global variables that need init values keep those values in Flash rather than RAM?
RAM is volatile and holds undefined garbage at power-up, so the only place a value can survive being powered off is non-volatile Flash; the reset handler is what bridges that value from Flash into its RAM home.
Why is a missing .bss-clear a classic "heisenbug"?
RAM contents at power-up are undefined; on one board they may happen to be 0 (code works) and on an identical board they are garbage (code crashes). The behaviour depends on luck, so it appears and vanishes across boards and power cycles.
Why is stack overflow on a bare-metal MCU usually not a clean error?
There is no MMU guard page by default, so the descending stack silently overwrites .bss and the heap; the corruption surfaces later as a distant, unrelated symptom rather than an immediate fault.
Why must the vector table array be declared const?
So it is placed in read-only Flash at the boot address; a non-const table would land in RAM and again not exist when the hardware reads it at reset — plus its addresses never change, so RAM would be wasted.
Why do we mark hardware registers volatile during early init but the same idea is not needed for the .data copy loop?
volatile tells the compiler a memory-mapped register can change outside program flow, so reads/writes must not be optimized away; ordinary RAM in the copy loop has no such side effects, so plain pointers suffice.

Edge cases

What happens if .data is empty (no initialized globals)?
Then _sdata == _edata, so the copy loop's condition is false immediately and it runs zero times — perfectly correct, no special case needed.
What if .bss is empty?
_sbss == _ebss, the zeroing loop runs zero iterations, and nothing is cleared because there is nothing to clear — the loop degenerates gracefully.
What if _sidata (the Flash source) is not word-aligned but you copy with uint32_t*?
An unaligned 32-bit access can fault or run slowly depending on the core; the linker normally aligns these symbols to 4 bytes, but if you override alignment you must copy byte-wise or fix the alignment.
If a peripheral interrupt fires during the reset handler, before its handler address is written, what governs the outcome?
The handler address in the vector table is already fixed in Flash from build time, so it points at valid code regardless of how far the reset handler has progressed — the danger is instead that the data that handler touches (its .data/.bss) may not be initialized yet, so interrupts are typically kept disabled until setup completes.
What if RAM size is declared larger in the linker script than the chip physically has?
_estack points past real memory; the first stack push writes to a non-existent address, causing a bus fault or silent loss — the linker cannot detect this, so the RAM size in the script must match the datasheet.
At the exact moment main() is entered, how many bytes has the stack grown?
Just the branch-with-link return address (and any saved registers), so SP sits a few words below _estack — the stack is essentially empty, ready for main's locals to descend from the top.

Recall Self-check: three you should be able to answer without peeking

Word 0 vs word 1 of the vector table? ::: Word 0 = initial MSP (stack-top value); word 1 = Reset_Handler address (goes into PC). Copy-then-zero order and why? ::: Copy .data from Flash to RAM, then clear .bss; both must finish before main or any constructor runs so C's global guarantees hold. Why does _estack sit at the high end of RAM? ::: The stack is full-descending, so it starts at the top and grows down toward the heap, giving one well-defined collision failure mode.