Worked examples — Memory protection units (MPU) — preventing stack overflow, access faults
This page drills the MPU parent topic into concrete numbers. We will compute base addresses, RASR size fields, region-match winners, and fault decisions by hand — so that no MPU register value ever feels like magic.
Before we compute anything, two symbols that the parent used but never spelled out digit-by-digit:
The scenario matrix
Every MPU question you will ever face is one of these cells. The examples below are each tagged with the cell(s) they cover.
| Cell | Case class | What is tricky about it |
|---|---|---|
| A | Encode a size → SIZE field |
the offset |
| B | Legal (aligned) base | low bits must be zero |
| C | Degenerate: misaligned base | hardware silently re-aligns |
| D | Zero / smallest region (32 B guard) | the floor case, |
| E | Limiting: whole-address-space region (4 GB) | the ceiling case, |
| F | Overlap — highest region number wins | priority direction |
| G | Overlap done backwards | guard silently ignored |
| H | Access-type violation (write to RO, exec on XN, user on privileged) | permission vs. access-kind |
| I | Word problem: stack overflow catch, byte-exact | where the first fault lands |
| J | Exam twist: subregion disable carves a hole | 1/8th granularity |
Example 1 — Encode three region sizes (Cell A, D, E)
Forecast: guess each SIZE value before reading. (Hint: use .)
- 1 KB. , so . Why this step? We must express size as to read off . .
- 32 bytes. , so , the smallest legal region.
Why this step? This is the floor case (Cell D) — you cannot go below
SIZE=4. . - 4 GB. , so , the entire 32-bit address space. Why this step? Ceiling case (Cell E) — one region covering everything. .
Verify: decode back with : ✓, ✓, bytes GB ✓. The parent's code used (9<<1) for 1 KB and (4<<1) for 32 B — matches, and the <<1 is because SIZE sits in bits 1–5 of RASR.
Example 2 — Is this base legal for this size? (Cell B, C)
Forecast: how many low bits must be zero for a 64-byte region?
- , so : the low 6 bits of the base must be zero. Why this step? Alignment rule = "size is ⇒ zero the low bits."
- Look at the low 6 bits (last hex digit and a half): mask is .
0x...00→00 & 0x3F = 0→ legal (green in figure).0x...40→0x40 & 0x3F = 0→ legal ( is a multiple of 64).0x...10→0x10 & 0x3F = 0x10 = 16 ≠ 0→ illegal (red in figure).
- For the illegal one the hardware ANDs away the low 6 bits: effective base .
Why this step? This is Cell C — the silent shift the parent warned about. You thought you protected
0x10–0x50; you actually protected0x00–0x40.
Verify: general fix base = addr & ~(size-1): ✓. And 0x40 = 64 is exactly the size, so region (b) covers 0x40–0x80, adjacent and clean.
Example 3 — The 32-byte guard band, byte-exact (Cell D, I, word problem)
Forecast: the stack starts near 0x2000_0400 and grows down. Where's the tripwire?
- Guard region: base
0x2000_0000, size , so it covers0x2000_0000 – 0x2000_0020. Why this step? ; the half-open interval means0x2000_0020itself is outside the guard. - The stack pointer starts at
0x2000_0400and marches down:...0x3FC, 0x3F8, .... Every push writes to a lower address. Why this step? Stacks on ARM grow toward lower addresses — the growth direction decides which end the guard protects. - First write inside the guard = first address . The last safe byte is
0x2000_0020; the first fault byte is0x2000_001F(the byte just below). Why this step? This is the tripwire: Cell I — byte-exact overflow detection. The parent's guard wins because it is the higher-numbered region (Region 1 vs Region 0). - On fault,
MMFARlatches the offending address, so yourMemManage_Handler()reads0x2000_001Fand knows exactly which task overran (Cell H, permission = no-access).
Verify: how much stack does the app get? bytes usable, ✓. A 2048-byte array clearly can't fit in 992 → fault guaranteed ✓.
Example 4 — Overlap: which region wins? (Cell F)
Forecast: both regions cover 0x2000_0010. Guess the winner.
- Region 0 covers
0x0000 – 0x0400: is0x0010inside? → yes, matches. - Region 1 covers
0x0000 – 0x0020: is0x0010inside? → yes, matches. Why this step? Both match, so priority decides. - ARM rule: highest-numbered matching region wins → Region 1 wins → no access → fault. Why this step? This is exactly the parent's guard trick (Cell F): the restrictive rule sits in the higher slot.
Verify: address 0x2000_0300 (outside guard, inside stack): matches only Region 0 → RW → write allowed ✓. So the guard protects 0x00–0x20 and leaves 0x20–0x400 writable — correct 32-byte fence.
Example 5 — The same overlap, numbered backwards (Cell G)
Forecast: did swapping the numbers change anything?
- Both regions still match
0x2000_0010(same intervals as Example 4). - Highest-numbered matching region = Region 1 = the broad RW stack → wins. Why this step? Priority follows the number, not the intent. Cell G — the classic silent bug.
- Result: the write is allowed. The guard is completely ignored. Overflows go undetected → silent corruption returns.
Verify: contrast with Example 4 — identical addresses, opposite outcome, purely because the winning region flipped from "no access" to "RW". Rule to memorize: restrictive region must have the higher number. ✓ (Consistency: Example 4 faulted, this one passes; the only change was the region index.)
Example 6 — Permission vs. access-kind table (Cell H)
Forecast: RO = read-only, XN = execute-never. Which three fault?
- (i) read — RO permits reads → pass.
- (ii) write — RO forbids writes → fault. Why? The access kind (write) is checked against
APbits. - (iii) execute — XN means "execute-never"; an instruction fetch here → fault. Why? This is the shellcode defense: even if data is corrupted, you can't run it.
- (iv) privileged write — still RO. Read-only means read-only for everyone; privilege only gates the "unprivileged-allowed" axis, not the RW axis → fault. Why this step? Two independent axes: RW vs RO and privileged vs user. Don't conflate them.
Verify: count of faults = 3 of 4 (only the read passes). Cross-check the privilege axis is orthogonal: a privileged read would also pass (RO allows reads regardless of privilege).
Example 7 — Peripheral protection, big region (Cell E, H)
Forecast: MB .
- , so .
Why this step? Read off to get
SIZE. - (parent's code used
(28<<1)✓). - Alignment: a region needs the low 29 bits of the base zero. , low 29 bits are zero → aligned ✓. Why this step? Confirm before trusting the region.
- A user task doing
*UART_TXREG = data(a write from unprivileged mode) hits the privileged-only rule → fault (Cell H).
Verify: does the region actually reach the top of the block? , i.e. covers up to 0x5FFF_FFFF inclusive ✓. Exactly the peripheral window.
Example 8 — Subregion disable carves a hole (Cell J, exam twist)
Forecast: 1 KB split into 8 equal subregions — how big is each?
- Every region ≥ 256 B splits into 8 equal subregions. bytes each. Why this step? Subregion granularity is always exactly one-eighth of the region.
- The 8 subregions, low→high: SR0
0x000–0x080, SR10x080–0x100, … SR70x380–0x400. Why this step? SR0 is the lowest addresses = the bottom = where a downward stack overflows. - To disable SR0, set bit 0 of the 8-bit
SRD: . Why this step? EachSRDbit maps to one subregion; a set bit means "this eighth is off / no access." - The hole is
0x2000_0000 – 0x2000_0080(128 bytes) — a guard band without needing a second region. Why this step? Exam twist (Cell J): the parent hinted you can use subregion disable instead of a separate higher-numbered guard region.
Verify: hole size ✓. SRD value disables exactly one subregion. If instead you wanted the top eighth (SR7), you'd set bit 7: ✓.
Recall Quick self-test
A 256-byte region needs how many low base bits zero? ::: (since ).
SIZE field for a 4 KB region? ::: , so .
Two regions overlap; the restrictive one is Region 3, the broad one Region 5. Who wins at an overlapping address? ::: Region 5 (higher number) — the restrictive one is ignored; a bug.
Usable stack bytes after a 32 B guard on a 1 KB stack? ::: bytes.
Which access faults on an XN region? ::: an instruction fetch / execute from it.
Related: RTOS task switching reprograms MPU regions per task; Memory faults and exception handling covers what MemManage_Handler() does with MMFAR; Memory-mapped IO is why peripherals need privileged-only regions.