2026-09-02 - (/loop) The reporter could not survive an execute fault; fixed, and the ctype work turns out to be a 7x advance

Third tick on one thread, and it ends with the measurement the first two could not get.

The crash, diagnosed (D471)

D470 established that PPSA02664's worker died before writing a trace, but not why. A probe either side of emit in the vectored handler answered it in one run:

fault-probe: code=0xc0000005 at=0x7fff0001 rip=0x7fff0001 - before emit   <- guest jumped to a placeholder
fault-probe: code=0xc0000005 at=0x7fff0000 rip=0x7ffa932bcc54             <- the reporter, faulting

The reporter copies the bytes around the faulting instruction into the report (raw bytes, not a disassembly - principle 1). Both byte windows assumed "the page holding rip is mapped, because the guest was executing in it". That is true of a data fault and false of an execute one: when the guest jumps somewhere that is not code, rip is the unmapped address. bytes_before(0x7fff0001) reads backwards from 0x7fff0000 - exactly the second fault's address - and a fault inside a fault handler ends the process instead of being reported.

Fix: a readable(address, len) using VirtualQuery, which describes a mapping without dereferencing it and allocates nothing. Requires MEM_COMMIT, rejects PAGE_NOACCESS/PAGE_GUARD, and requires the window to sit inside one region. Both windows ask before reading. Verified: one fault instead of two, emit returns, and a trace is written.

Why it had never fired: every fault before this was a data fault. The execute fault only exists because _Getpctype now works and the guest runs far enough to fail differently. A new kind of fault found a hole a hundred runs of the old kind never touched - and the invariant was written down honestly in a SAFETY note and was still wrong, because it was stated about "the faulting instruction" while only ever being true of one class of fault.

What it made visible

First honest measurement of D468, against the last recorded pre-ctype state:

imports calls fault
before ctype (01:50) 39 1,544 image+0xb14be3, dereferencing _Getpctype's placeholder
after ctype 68 10,905 0x7fff0001, an instruction fetch

Seven times the calls, twenty-nine more distinct imports. The ctype table was a large advance and I spent a morning reporting it as same - nothing moved.

One honesty note on the verdict line: it still prints same, because the tool compares against the immediately previous run and the first fixed run had already overwritten the baseline. The delta above is against the recorded 01:50 state, stated explicitly rather than read off a verdict that is comparing two post-fix runs.

The new wall, and it is legible

Il2CppUserAssemblies::0x6f8b9da539afc9af, called 222 times, arg0 pointing at "il2cpp_init" (with "il2cpp_monitor_pulse" following it in the same string table). It is a name resolver: it answers our placeholder, and the guest calls what it was handed - hence an instruction fetch from 0x7fff0001 rather than a read through it. Naming and implementing it is the next step; the hardware capture's 5,400 kernel exports are the obvious naming oracle to try first.

State

clippy --tests fully clean, including the debt flagged for two ticks: enter in orbistoun-worker/src/lib.rs was 113/100 lines after an earlier session's spawned-thread TLS work. Rather than flag it a third time it is now split, and the split is not lint-appeasement: the extracted arm_diagnostics is the stretch whose order is load-bearing and whose mistakes are invisible - a stack fill after a poke silently erases it (D229, D185) - so naming it says that it is a sequence rather than a stretch of setup. The refusal reason is returned rather than written, keeping the halt in enter beside every other way entry can be declined. Verified by running both PPSA02664 and PPSA04263 through the guest-entry path afterwards, not by the tests alone.

fmt clean, worker/report/libc tests pass, identity scan clean, all probes removed (report.rs carries only the readable fix), nothing committed.

One number not to over-read: the run after the refactor reported FURTHER on 69 distinct imports against the previous run's 68. That is one import of run-to-run variation between two consecutive post-fix runs, not an effect of the refactor - the advance worth reporting is the 39 -> 68 in the table above.