ADR-005 — Dedicated exception and fatal stacks
- Status: Accepted
- Date: 2026-09-09
Context
FR-07 requires a fatal exception to use a dedicated stack so overflow does not silently lock the VM. Roadmap M4 is that path. ADR-004 already notes that a nested fault while println! holds the UART mutex can deadlock.
M3 ran the kernel and first-level current-EL exceptions on the same SP_EL1 (SPSel = 1). A nested sync exception would store another 272-byte frame on that stack. Without paging a downward overflow is not a hardware fault on QEMU virt — it smashes .bss / code with no serial evidence. M7 identity-maps RAM as one Normal block, so this is still true (no guard pages).
A data abort to an unused physical hole is QEMU-map-dependent. A nested BRK from the first-level handler is a real current-EL exception we already know how to take (FR-06).
Decision
- Thread stack on
SP_EL0. AfterVBAR_EL1is installed, copy the_startstack intoSP_EL0(MSR SP_EL0is legal at EL1), write__exc_stack_topintoSPwhileSPSelis still 1 (that isSP_EL1—MSR SP_EL1at EL1 is UNDEF), thenmsr spsel, #0. Normal kernel code uses the 64 KiB thread stack. - Exception stack on
SP_EL1. First-level current-EL exceptions (vector bank “Current EL, SP_EL0”) use the 16 KiB__exc_stack_*region automatically. The live sync slot moves from offset0x200(M3 / SP_ELx) to0x000. - Fatal stack before any nested store. Current-EL / SP_ELx slots (
0x200–0x380) loadSPfrom__fatal_stack_top(8 KiB) in asm, then callhandle_fatal_exception. Do not push a frame on the exception stack that may already be exhausted. - Raw UART on fatal / unhandled paths. Write the PL011 without
spin::Mutexso a nest duringprintln!still produces serial. - Probe is nested
BRKafter a near-empty thread SP. The hello kernel fires a healthy-stackBRK(M3 string), then sets a flag, movesSP_EL0to__stack_bottom + 64(less than the 272-byte frame), andBRKs again. The first-level handler printsexception: sync BRKfromSP_EL1, thenBRKs while SPSel = 1. That nest must printexception: fatal nestedand park. This is not an MMU stack-overflow fault and not a GIC path (M5 / FR-08).
Consequences
#[test_case]can stillbrk #0and return. Tests do not set the nest flag.scripts/qemu-smoke.shrequires the fatal marker in addition to hello + BRK. Afatal probe missedline is fail-closed.- Lower-EL and first-level FIQ/SError stubs still park. First-level IRQ is M5 / ADR-006. Nested IRQ/FIQ/SError still use the fatal stack. Taking a nested IRQ remains unprobed.
- This ADR does not claim Raspberry Pi or x86. Unmapped linker-stack guard pages are ADR-014 (a later cut; the nested-
BRKprobe here still stands).