Memory ROM and Cold Code - Mainkill1/SpiritBoot GitHub Wiki
SpiritBoot ROM placement and cold-code lifetime audit
Exact owned source and limits
This source audit uses merged main d4350b60, reconstructed tree 1277f2b7bf45a0992ef56d45d55815396d46ac8d. Its exact release64 sizes are recorded in Memory-Function-and-Table-Ledger, and its live image page mappings are in Memory-Resident-Page-Atlas. The original source report and identity checks remain in the lab; no private reference implementation was inspected.
All source references below use that reconstructed owned tree unless explicitly labeled prototype patch. The inventory's size proofs use production release64 compilation or generated declarations; no fresh build, test, rig, or performance campaign was run. No private Complex files were read.
Live GitHub PR #118 was checked during this review: open, unmerged, not draft, head c86a241be45cabc8d6db89db52b9929745c9153a, base ecf3536311bb679582228aef6e9cf11b7d305a2f, mergeable false. Its body explicitly holds qualification for unresolved PGR2 guest failures. Its reconstructed prototype tree is 7c915d3e522f9ef903388ebd5e29ac32d44154e2, not current main's tree. The dedicated product checkout and its ordered patch were checked against that exact head. This report relies on the verified patch, not an assumed current-main implementation.
Both owned physical layouts
boot/nxldr/boot-layout.h:25β58 defines separate images with the same kernel VA and different PAs. These addresses are placements, not resident byte totals.
| Owner / mapping | 64 MiB | 128 MiB |
|---|---|---|
| Kernel VA | 0x84000000 |
0x84000000 |
| Prepared kernel PA | 0x03400000 |
0x04000000 |
| Maximum image VA span | 4 MiB | 4 MiB |
| Loader PA interval | 0x03800000β0x03820000 |
0x07d00000β0x07f00000 |
| Initial low PTs | 0x03bc0000 |
0x07f00000 |
| Initial kernel-alias PTs | low-PT base + 0x4000 |
same offset |
| Initial PCR PT | low-PT base + 0x15000 |
same offset |
| Page directory | PA 0x0000f000 |
same |
| PCR backing | PA 0x00030000, 16 pages |
same |
| LoaderBlock / auxiliary | PA 0x00040000 / 0x00041000 |
same |
boot/nxldr/paging.c:44β114 creates transient low identity maps, the kernel-image alias, recursive page-table aperture, and PCR mapping. After boot, MM paints low RAM aliases separately from the kernel-image PDE: ntoskrnl/xb/mm/init.c:740 onward. The readonly replacement PTEs at the kernel VA must survive that paint; replacing its PDE with a RAM identity large page would lose ROM backing. Kernel virtual placement being stable is what preserves absolute references and callback addresses across memory capacities.
The measured release64 image retains 102 pages / 417,792 bytes (408 KiB), including its header page:
| Resident section | Exact virtual content bytes | Backing pages | Page-tail padding |
|---|---|---|---|
.text |
328,884 | 81 | 2,892 |
.data |
2,064 | 1 | 2,032 |
.rdata |
25,864 | 7 | 2,808 |
.bss |
43,944 | 11 | 1,112 |
PAGE |
2,500 | 1 | 1,596 |
INIT is 89,664 content bytes / 22 image pages; .rsrc is 2,760 / one image page, already discardable. These are not additional retained runtime pages. No matching release128 byte inventory was supplied in this directory; this audit establishes its placement from the header, not identical 128 MiB emitted sizes.
What the prototype actually places in ROM
Prototype patch lines 118β208 add prepare_rom_kernel_image: select readable sections that are neither writable nor discardable, copy whole zero-padded page spans uncompressed into flash, retain their original RVAs, put a checked residency table in header-page padding at offset 0x800, and zero corresponding RAM-image contents before compressing that prepared RAM image. It is not executing an XZ stream. Compressed mutable/boot content still has to be decoded into RAM; executable ROM pages are separately stored raw.
The prototype's optional build uses a 512 KiB flash image, rather than default release's 256 KiB compressed-only profile. Immutable pages must fit beside loader, compressed RAM payload and reset area; page selection is not equivalent to free physical flash capacity.
The loader validates the ROM table/framing and checks each raw span's CRC before installing mappings (patch loader.c hunk around lines 272β313). rom-kernel.h checks fixed PE32 image base, section/page bounds, readonly ownership, ordered nonoverlapping spans and actual flash capacity. PagingInit maps replacement pages at their original kernel VAs to physical flash with readonly PTE flags 1, retaining writable and exception pages in RAM (patch lines 342β363).
Current-main section flags would nominate 81 .text + 7 .rdata + 1 PAGE pages = 89 pages before writable-code exclusions. Excluding the known prefetch page leaves 88 pages / 360,448 bytes (352 KiB) as a section-policy upper bound, not a new measured main recovery or compatibility result. It assumes the prototype is correctly refreshed onto this tree, every nominated page is actually immutable, flash packing succeeds, and retirement succeeds. It must not inherit PR118's old-tree qualification or failures as a current-tree measurement.
Relocation, patching and compatibility contracts
Fixed VAs avoid runtime relocation writes
boot/nxldr/pe.c:54β107 validates the prepared image and returns NXBL_IMAGE_BASE + EntryRva; it deliberately neither copies sections nor applies runtime relocation fixups. The builder has already arranged content at RVAs. Main and the prototype require image base 0x84000000. Mapping different physical backing at the same VA preserves existing relative branch displacements and absolute virtual addresses; putting the functions at new ROM virtual addresses would require a substantially different relocation and pointer contract.
Concrete writable-code exception
ntoskrnl/ke/i386/kiinit.c:122β123 writes byte 0x90 to RtlPrefetchMemoryNonTemporal when XMM support is enabled. The current inventory locates that routine at VA 0x84002454, so the complete 0x84002000β0x84003000 page must remain RAM under the prototype policy. Patch gen-flash-bin.py resolves the exact symbol from the unstripped image and excludes its containing page (around patch lines 286β299). Readonly section flags alone therefore do not establish physical immutability. This is one positively identified write, not a guarantee that all future instrumentation/debugger/self-modifying paths are covered; release-only admission is explicit.
Interrupt dispatch code is another concrete patch destination: ntoskrnl/ke/i386/irqobj.c:370β385 copies KiInterruptTemplate into mutable KINTERRUPT storage and patches that copy with the object's address. It does not write the template's original code bytes. Native/title interrupt object storage must remain RAM; the template can remain readonly.
Exports and pointer copies
ntoskrnl/xb/gen-exports.py:202β215 emits NxkExportDirectory; xb/fix-edata.py fills its RVAs post-link before image packaging, not during boot. XBE import resolution overwrites the title's own thunk table with implementation addresses (xb/xbe.c:3516 onward). Moving the export table to readonly physical ROM at the same VA does not itself require runtime patching. Public data exports are different: mutable exported storage must stay RAM. Public ordinals, routine addresses/calling conventions and data semantics remain ABI roots regardless of whether a particular trace called them.
Private class dispatch/initialization tables, PCIIDE channel interface copies, DPC/timer routines, and shutdown callbacks may already hold code pointers. Same-VA ROM backing keeps those pointers valid. Discarding their targets instead would require actual final-consumer/in-flight-return proofs; the storage boot seal only changes its named public driver slots. The prior PnP audit establishes that retained private copies remain. ROM relocation is therefore a backing change, not a callback retirement.
Reset and reclaimed shadows
Main reset is a hardware SMC reset, preceded by shutdown notifications and persistence publication where permitted (hal/halx86/xbox/reboot.c:24β60). The reset vector/bootstrap runs from ROM with paging reset (boot/nxldr/startup.S:123β125,262β284,514β542); loader page tables must then be rebuilt. Runtime MM also maintains a separate UC identity flash/MMIO mapping over 0xf8000000β0xffc00000 (xb/mm/init.c:676β683), distinct from original-VA kernel ROM PTEs. Reset-path ROM availability must not depend on page tables donated during the previous boot.
Prototype NxkMmRetireRomPages verifies all replacement PTEs before one-time donation. It restores any warm-persistent data into the original RAM shadows through RAM aliases before returning those RAM PFNs to the supply (patch ntoskrnl/xb/mm/init.c hunk, lines 530β599). ROM PFNs never enter the RAM allocator. Reclaimed shadows can become title allocations; they are not permanently dead just because kernel code stopped occupying them.
The next boot's decompression still writes the prepared RAM image, including zeroed shadow pages. Prototype NxWarmDonated consequently admits ROM-shadow pages into warm staging (patch warm-stage hunk), and the builder checks staging capacity including INIT, retired workspace and ROM shadows. Main xb/mm/persistence.c:180β212 restores through checked writable aliases. Missing that staging/restore contract would corrupt persistent owners on reset even if every runtime ROM call worked. Table validity, stable VA mappings, raw ROM content and persistence restoration must all be reestablished on each boot.
Concrete readonly objects and coldness limits
These exact owned-object sizes come from sizeof_proved_tables in the current inventory, not adjacent symbol envelopes:
| Object group | Exact bytes / current VA | Owned consumer and assessment |
|---|---|---|
Unicode case data NxkrnlNlsCase |
4,870 at 0x84053c20 |
xb/nls-stubs.c:59β115 reads the trie; lazy NxkCaseUpper/Lower pointers are writable globals pointing inside it. Namespace/case-insensitive matching retains this runtime consumer. Readonly placement candidate; not proved cold or discardable. |
NTSTATUS conversion tables plus error_table |
5,184 total | sdk/lib/rtl/error.c:51β78 range-searches then reads indexed DWORDs. The 240-byte descriptor table contains readonly pointers into the component tables. Ordinal301 exposes RtlNtStatusToDosError. Uncommon errors can reach otherwise unobserved rows: no per-table deletion proof. |
Boot font VidpFontData |
1,248 at 0x840534c0 |
drivers/base/bootvid/i386/xbox/bootvid.c:375β407 reads font bytes in DisplayCharacter. It does not test framebuffer retirement before reading them. Shared console.c:92,147 callers remain. Readonly placement candidate; INIT discard not justified by framebuffer retirement alone. |
| Export directory + 378 slots | 1,552 at 0x840582e0 |
Generated declaration size, public ordinal compatibility; post-link fill. Readonly at runtime. ABI metadata is not unused merely because a title resolves few imports. |
Together these objects occupy 12,854 exact bytes; this is not a page recovery claim. They share .rdata pages with other symbols/alignment. Their current page intersections are font page 0x84053000, Unicode pages 0x84053000β0x84055000, status group pages 0x84055000β0x84058000, and export page 0x84058000 (half-open intervals). Replacing these pages safely requires preserving all their co-residents. Repacking tables would change addresses and require updating every pointer/relocation/generated metadata consumer; raw byte removal need not cross a page boundary. A whole readonly-section mapping policy would replace seven pages, including content not proved to belong to these four groups.
Mutable allocator metadata, interrupt shadows, boot/idle and double-fault stacks are not ROM candidates. PmStageDescriptors is already INIT (20,200 bytes), not future retained savings. Readonly does not imply cold; cold does not imply unused; absent coverage does not erase a public or address-taken root.
A missed assembly specialization, with a concrete remaining boundary
Xbox's IDT generation in ntoskrnl/ke/i386/trap.s:83β100 emits only vectors 0x30β0x3f and advertises a 64-entry IDT. But handler generation at trap.s:111β118 still uses unconditional REPEAT 208, emitting handlers through vector255. Current COFF positions and the owned assembly repeat count identify the extra vector64β255 handler range as 0x84001278β0x840017b8, 1,344 bytes (192 fixed seven-byte stubs). This explains some βunreachableβ assembly symbols without inventing compiler function boundaries, and is a concrete source-level specialization gap missed by the function-extent list.
It is not yet a complete removal proof: KiGetVectorDispatch computes KiStartUnexpectedRange + (Entry - PRIMARY_VECTOR_BASE) * KiUnexpectedEntrySize (ke/i386/irqobj.c:43β48), which static direct-call graphs cannot see. XeKeInitializeInterrupt forwards a title-supplied Vector (xb/interrupt.c:101β120); the generic KeConnectInterrupt validation (irqobj.c:407β415) does not clamp Vector to the Xbox IDT limit. Valid PIC vectors require only the first16 stubs, but a bounded vector-admission/consumer audit must close invalid-vector behavior before changing the table. This source review recommends no code change.
Deleting all 1,344 bytes from today's .text without compensating layout growth would leave 327,540 bytes, below the 327,680-byte / 80-page boundary. It could therefore remove one 4 KiB image page on a refreshed link, but final padding/alignment and downstream section placement must be checked before claiming physical recovery. This is a small concrete code-size opportunity, not a major RAM fix.
The inventory has no retained function-extent row marked static_root_unreachable. Its static executable-symbol list includes assembler interior labels and the stubs above; those are not equivalent to unused function allocations. No new safely unnecessary public/address-taken retained root was established here.
Wiki additions supported by this review
Memory-Lifetime-and-Reduction-Plan.md currently summarizes ROM feasibility but omits the concrete prefetch write/page, post-link versus runtime export writes, warm shadow overwrite/staging contract, exact readonly groups, font's surviving read path, and mismatch between reduced IDT generation and unreduced handlers. These are appropriate source-backed additions. Keep PR118's old-tree experimental status separate from current main's 88-page section-policy bound. Do not add a cold-unused-function or qualified ROM-speed claim; this review establishes neither.