5.5.24 · D3Embedded Systems & Real-Time Software

Worked examples — Memory protection units (MPU) — preventing stack overflow, access faults

2,328 words11 min readBack to topic

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. 1 KB. , so . Why this step? We must express size as to read off . .
  2. 32 bytes. , so , the smallest legal region. Why this step? This is the floor case (Cell D) — you cannot go below SIZE=4. .
  3. 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.


Forecast: how many low bits must be zero for a 64-byte region?

  1. , so : the low 6 bits of the base must be zero. Why this step? Alignment rule = "size is ⇒ zero the low bits."
  2. Look at the low 6 bits (last hex digit and a half): mask is .
    • 0x...0000 & 0x3F = 0legal (green in figure).
    • 0x...400x40 & 0x3F = 0legal ( is a multiple of 64).
    • 0x...100x10 & 0x3F = 0x10 = 16 ≠ 0illegal (red in figure).
  3. 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 protected 0x00–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?

  1. Guard region: base 0x2000_0000, size , so it covers 0x2000_0000 – 0x2000_0020. Why this step? ; the half-open interval means 0x2000_0020 itself is outside the guard.
  2. The stack pointer starts at 0x2000_0400 and 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.
  3. First write inside the guard = first address . The last safe byte is 0x2000_0020; the first fault byte is 0x2000_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).
  4. On fault, MMFAR latches the offending address, so your MemManage_Handler() reads 0x2000_001F and 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.

  1. Region 0 covers 0x0000 – 0x0400: is 0x0010 inside? yes, matches.
  2. Region 1 covers 0x0000 – 0x0020: is 0x0010 inside? yes, matches. Why this step? Both match, so priority decides.
  3. 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?

  1. Both regions still match 0x2000_0010 (same intervals as Example 4).
  2. 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.
  3. 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?

  1. (i) read — RO permits reads → pass.
  2. (ii) write — RO forbids writes → fault. Why? The access kind (write) is checked against AP bits.
  3. (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.
  4. (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 .

  1. , so . Why this step? Read off to get SIZE.
  2. (parent's code used (28<<1) ✓).
  3. 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.
  4. 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?

  1. Every region ≥ 256 B splits into 8 equal subregions. bytes each. Why this step? Subregion granularity is always exactly one-eighth of the region.
  2. The 8 subregions, low→high: SR0 0x000–0x080, SR1 0x080–0x100, … SR7 0x380–0x400. Why this step? SR0 is the lowest addresses = the bottom = where a downward stack overflows.
  3. To disable SR0, set bit 0 of the 8-bit SRD: . Why this step? Each SRD bit maps to one subregion; a set bit means "this eighth is off / no access."
  4. 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.