5.5.16 · D4Embedded Systems & Real-Time Software

Exercises — Startup code — vector table, reset handler, stack initialization

2,525 words11 min readBack to topic

Before we begin, here is the memory picture every problem leans on. Look at it once, carefully.

Figure — Startup code — vector table, reset handler, stack initialization

Prerequisite links used below: Linker scripts and memory sections (.text .data .bss), ARM Cortex-M exception and interrupt model, Stack vs Heap memory layout, The C runtime and crt0, Bootloaders and VTOR relocation, Volatile, memory-mapped registers and hardware init.


L1 · Recognition

Exercise 1 (L1)

At reset a Cortex-M CPU reads two words from the vector table. The table starts at 0x0000_0000. Word at 0x0000_0000 = 0x2000_8000. Word at 0x0000_0004 = 0x0000_0101. After these two reads, what is in MSP and what is in PC?

Recall Solution

The hardware rule is fixed: word 0 → MSP, word 1 → PC.

  • MSP (the stack-top address).
  • PC (address of the Reset Handler).

Why word 0 is the stack, not code: the CPU needs a place to push registers before any instruction runs (interrupts can stack registers from cycle zero). So the designers made the very first word the stack pointer. See ARM Cortex-M exception and interrupt model.

Exercise 2 (L1)

The vector table below is written in C. Which entry is not actually a function, and why does the code still compile?

void (* const vector_table[])(void) = {
    (void (*)(void)) &_estack,   // A
    Reset_Handler,               // B
    NMI_Handler,                 // C
};
Recall Solution

Entry A is not a function — it is the stack-top address _estack. It compiles because the array's element type is "pointer to function taking void returning void", and we cast the address &_estack to that type just to satisfy C's type checker. The hardware never calls word 0; it copies the 32-bit number into MSP. Casting a number to a function-pointer type does not make it code.


L2 · Application

Exercise 3 (L2)

RAM runs from 0x2000_0000 to 0x2000_4FFF (20 KB). The stack is full-descending. What value does the linker assign to _estack, and where does the first pushed word land?

Recall Solution

_estack = highest RAM address + 1 .

Full-descending means a push first decrements SP by 4, then writes. Starting from 0x2000_5000: So the first pushed word occupies bytes 0x2000_4FFC..0x2000_4FFF — the very top usable slot. Look at the orange arrow in the figure: the stack grows downward into RAM. See Stack vs Heap memory layout.

Exercise 4 (L2)

.data occupies RAM _sdata = 0x2000_0000 to _edata = 0x2000_0010 (exclusive of the end). Each word is 4 bytes. How many words does the copy loop run, and (using the parent's index formula) which Flash address feeds RAM word at 0x2000_000C if _sidata = 0x0800_1000?

Recall Solution

Word count: span in bytes bytes words.

Flash source for RAM 0x2000_000C: the parent's rule is Here , so the offset bytes. Why: the n-th word of RAM .data is copied from the n-th word of the Flash image; the offset into RAM equals the offset into Flash. See Linker scripts and memory sections (.text .data .bss).


L3 · Analysis

Exercise 5 (L3)

A student's reset handler copies .data correctly but the .bss-zeroing loop is deleted. The program has int counter; ... counter++; used as a loop bound. It works on their board but hangs on a factory-fresh board. Explain precisely why, in terms of what .bss guarantees.

Recall Solution

.bss holds zero-initialized globals. C guarantees int counter; starts at 0. The reset handler manufactures that guarantee by clearing the .bss RAM region.

With the loop deleted, counter holds whatever was in RAM at power-up — which is undefined. On the student's board that RAM happened to already read 0 (common after a warm reset), so counter began at 0 by luck. On a fresh board, RAM powered up with garbage (say 0xDEAD_BEEF), so counter++ starts from a huge number and the loop bound is wrong → hang.

The fix: restore the zeroing loop for (dst=&_sbss; dst<&_ebss;) *dst++ = 0;. It converts "undefined" into the promised 0. See The C runtime and crt0.

Exercise 6 (L3)

.bss runs from _sbss = 0x2000_0100 to _ebss = 0x2000_0300. The zeroing loop writes one 32-bit word per iteration. How many iterations run, and how many bytes are zeroed?

Recall Solution

Span bytes.

  • Bytes zeroed .
  • Iterations words.

Why divide by 4: the loop increments a uint32_t* pointer, and dst++ on a uint32_t* advances by sizeof(uint32_t) = 4 bytes. Each iteration writes one word.

Exercise 7 (L3)

The vector table places IRQ handlers starting at offset 0x40. Each entry is one word (4 bytes). At what byte offset does the handler for IRQ5 live, and what memory address is that if VTOR = 0x0800_0000?

Recall Solution

IRQ0 is at offset 0x40. Entry IRQn sits at bytes. With the table relocated so VTOR = 0x0800_0000: Why VTOR matters: a bootloader can move the table off 0x0000_0000 and point VTOR at the application's table, so the CPU indexes from the new base. See Bootloaders and VTOR relocation.


L4 · Synthesis

Exercise 8 (L4)

Design the ordering. You must write a reset handler that (a) copies .data, (b) zeros .bss, (c) initializes a memory-mapped clock register that RAM-based code needs, (d) runs C++ constructors, (e) calls main. Two of these have a strict ordering constraint with each other beyond the obvious. Give a correct full order and justify the one non-obvious constraint.

Recall Solution

A correct order:

  1. (c) clock/hardware init — often first, so the rest runs at full speed and any wait-states are configured. (Uses Volatile, memory-mapped registers and hardware init.)
  2. (a) copy .data
  3. (b) zero .bss
  4. (d) C++ constructors (__libc_init_array)
  5. (e) call main

The non-obvious constraint: (d) constructors must come after both (a) and (b). A C++ constructor may read or write a global object — an initialized global lives in .data, a default one in .bss. If constructors ran before the copy/zero, they would touch garbage RAM, and then step (a)/(b) would overwrite whatever the constructor wrote. So .data/.bss setup must finish before any user code (including constructors) executes.

Why (c) can go first: the clock register is a hardware register, not a C global — it does not depend on .data/.bss, so it is safe (and often required) early.

Exercise 9 (L4)

Write the copy-.data loop as a formula sequence for a .data of 3 words with _sdata = 0x2000_0000, _sidata = 0x0800_2000. List every (destination, source) pair the loop produces.

Recall Solution

Using with source , stepping 4 bytes per word:

word RAM dest () offset Flash source
0 0x2000_0000 0x0 0x0800_2000
1 0x2000_0004 0x4 0x0800_2004
2 0x2000_0008 0x8 0x0800_2008

Why the parallel step: both pointers advance by the same 4 bytes each iteration (*dst++ = *src++), so RAM offset and Flash offset stay locked together — that is exactly what the formula expresses.


L5 · Mastery

Exercise 10 (L5)

A device has RAM 0x2000_00000x2000_1FFF (8 KB). Layout from the low end up: .data = 256 bytes, .bss = 512 bytes, then the heap, then a gap, then the stack growing down from _estack. The designer wants a guaranteed _Min_Stack_Size = 1 KB and _Min_Heap_Size = 1 KB. Compute _estack, the byte offset where .bss ends, and how many free bytes remain between the top of the reserved heap and the bottom of the reserved stack. State the failure mode if that free region is exhausted.

Recall Solution

Constants: 8 KB bytes; 1 KB bytes.

  • .data occupies 0x2000_0000 .. 0x2000_00FF (256 bytes). Ends at offset 0x100.
  • .bss occupies the next 512 bytes: 0x2000_0100 .. 0x2000_02FF. .bss ends at offset , i.e. address 0x2000_0300.
  • Reserved heap = 1024 bytes above .bss: 0x2000_0300 .. 0x2000_06FF, ending at offset .
  • _estack = top of RAM + 1 .
  • Reserved stack = 1024 bytes below _estack: 0x2000_1C00 .. 0x2000_1FFF, its bottom at offset .
  • Free gap between reserved-heap end and reserved-stack bottom:

Failure mode if the gap is exhausted: with no MMU guard page, a growing stack silently descends past _estack - 1024 into the heap/.bss and corrupts memory — symptoms appear in distant variables, not at the overflow site. Mitigations: linker _Min_Stack_Size check (which is what reserves the 1 KB above), the MSPLIM register on Cortex-M33, or a fill-pattern watermark to detect how far the stack ever reached. See Stack vs Heap memory layout.

Exercise 11 (L5)

Prove numerically that placing main directly at vector-table offset 0x04 (skipping the reset handler) leaves an initialized global wrong. Take int g = 42; at RAM 0x2000_0000, with 42 stored in Flash at _sidata, and assume power-up RAM at 0x2000_0000 reads 0x0000_0000. What value does g have in main in each case (with vs without the copy loop)?

Recall Solution
  • With reset handler (copy loop runs): the loop writes Flash's 42 into 0x2000_0000. So g == 42. Correct.
  • Without it (main at 0x04): the copy never happens; g holds whatever power-up RAM contained = 0x0000_0000 = 0. So g == 0, not 42. Wrong.

If power-up RAM had held garbage instead of 0, g would be that garbage — even worse. This is exactly why the reset handler is the minimum required glue: it is the only thing that copies .data and zeros .bss. See The C runtime and crt0.


Recall

Word 0 of the vector table goes into which register? ::: MSP (the Main Stack Pointer). For full-descending stack with _estack = 0x2000_5000, first push lands where? ::: 0x2000_4FFC. Flash source for RAM .data word at offset d from _sdata? ::: _sidata + d. Why must C++ constructors run after .data/.bss setup? ::: They are user code that may touch globals; running earlier hits undefined RAM and gets clobbered.