ADR-020 — High-VA fn-pointer rewrite + live identity .text tear
- Status: Accepted (live
.textafter the boot stub;.rodata/.data/ heap still identity-mapped) - Date: 2026-09-11
Context
ADR-019 jumps the post-MMU continuation to its TTBR1 alias and unmaps a 16 KiB dedicated identity text range. A first attempt to unmap live .text after _start Failed: the first writeln! / println! took an unhandled current-EL sync.
This ADR investigates that failure and takes the largest honest Verified cut toward tearing live identity .text.
Probed root cause (this cloud VM, main 24d94e6 hello ELF, then this branch):
- rustc
dyn Writevtables live in.rodata. ThePl011table isdrop=0 / size=8 / align=8 / write_str / write_char / write_fmtwith identity method addresses (write_charat0x4008_1084, immediately after the vectors page). - The hello
.rodataheld a handful of other identity.textwords (Debug::fmt,PadAdapterasWrite)..datahad none..texthad no 8-byte identity literal pools. - AArch64 codegen used
ADRP(PC-relative), notMOVZ/MOVKof0x4008. After the high-VA jump, newfn()values are already high; tables linked asR_AARCH64_ABS64stay identity until rewritten. The ELF isET_EXEC/relocation-model: static— no leftover reloc records to apply. .textand.rodatashared page0x400a4000on the ADR-019 layout (vtable at0x400a4338). Unmapping “all of.text” without a page split would also drop the vtable.
relocation-model: pic was considered and not taken: static + a post-jump patcher matches the existing -kernel / 0x4008_0000 contract and does not need a dynamic linker.
Decision
- Page-split
.text/.rodata. Linker__text_end/__rodata_startare 4 KiB aligned so live.textand fmt tables do not share a page. - High-VA fn-pointer rewrite. After
ident: jump, walk.rodata(through__ident_tear_start) and.data. Every 8-byte word that is an identity address in.textor__ident_tear_*is rewritten to its TTBR1 alias. RO pages are made writable only for that store, then restored (SCTLR.WXNmakes a writable page XN — they are not fetched). Serialident: reloc n=NwithN >= 1. APl011 as dyn Writefat-pointer read must see a highwrite_str. - Live identity
.texttear. Unmap[0x4008_1000, __text_end)from kernel and user TTBR0 (TLBI VAAE1each low VA). High twins stay PXN-clear. The0x4008_0000page (_start+exception_vectors) stays identity-mapped. Serialident: live lo=… hi=… pages=NwithN >= 8. - Keep
.rodata/.data/ heap identity-mapped. String literals and rewritten vtables still live there. Do not yank them here. - Keep
_start/ QEMU-kernelat0x4008_0000. Do not change default-cpu. Do not claim PAN. - Fail-closed probe. Existing
ident: jump/range/split/fault/high/text/no el0/okstay. Newident: reloc/ident: live.scripts/qemu-smoke.shgreps those strings and rejectsident: reloc missed/ident: live missed.#[test_case]covers the rewrite and the live unmap. - Still Planned. Unmap identity
.rodata/.data/ heap after those accesses are proven high-only; PAN on-cpu cortex-a57; umbrella EL0 isolation. - NFR-10 text is revised in place (ID unchanged). Do not mint NFR-15+.
Honesty
Say “rustc vtable / fn-pointer words were rewritten to high aliases” or “live identity .text after the boot stub was unmapped while println! still ran” only when the serial / tests pass. Do not say:
- the kernel has moved to the high half
- identity mappings were fully torn down (
.rodata/.data/ heap stay) - “secure OS” / “hardened” / “EL0 isolated”
- PAN (still unclaimed on
-cpu cortex-a57)
Consequences
paging::rewrite_identity_fn_ptrs/paging::tear_live_identity_textown the cut. ADR-019 remains the high-VA jump + dedicated 16 KiB range.- A later ADR may unmap
.rodataonce string / table accesses are high-only, then.data/ heap. That work is not this cut.