[2.BF] Synthetic silph::WorkerCtx spawn (round 18 — opt-in landing)

Adds infrastructure to synthesise the silph::WorkerCtx that AUDIT-058/059
identified as never reached by ours' static-init chain (real chain entry
sits in audit-059 round 9's wrong-vtable wedge at sub_82172BA0+0x1E8).
Ctx layout follows round 5's live hexdump from canary:

  +0x00   vtable = 0x8200A1E8
  +0x04   self
  +0x08   intrusive list head -> self
  +0x0C   init flag = 1
  +0x10   packed byte field
  +0x18   2x float ~1.0 (UI rates)
  +0x24   flag = 1
  +0x28..+0x30  3x foreign-arena pointers (left NULL — see below)
  +0x54..+0x84  4x X_KEVENT auto-reset, state=0
  +0x94..+0xC4  4x X_KEVENT manual-reset, state=1 (pre-signaled)
  +0x210..+0x250  4-entry intrusive work-ring, empty

Worker spawn mirrors AUDIT-048's audio-worker pattern in
xaudio_register_render_driver: per-worker allocate_thread_image +
state.scheduler.spawn with r3 = ctx_ptr. Trigger fires at the first
dat/* VFS open (ours' earliest is dat/files.tbl), which is when canary
runs the equivalent chain.

ROUND 18 OUTCOME — opt-in only:

With workers spawned Ready (XENIA_SILPH_SYNTH=1), boot CRASHES at
cycle ~5.5M with PC=0 on hw=1, just after worker_3 (entry 0x825065B8)
spawns. Per task constraints this is STOP-and-report: the ctx fields
+0x28/+0x2C/+0x30 (foreign heap pointers — canary's 0x30057018,
0xBCE25640, 0xBE568F00, distinct arenas per audit-059 round 7) are
left NULL, and the worker bodies plausibly dereference one of them.
Synthesising those is a fresh investigation (round 19+).

With workers spawned Suspended (XENIA_SILPH_SYNTH=suspend), boot
completes normally (11 spawns, VdSwap=1, KeSetEvent=2,
KeReleaseSemaphore=1 — matches default baseline). The ctx remains
materialised in guest memory at the logged VA for downstream probing.

Default (env var unset): no synth, no regression.

Files:
  crates/xenia-kernel/src/silph_synth.rs   (new, 225 LOC)
  crates/xenia-kernel/src/lib.rs           (+1 LOC, register module)
  crates/xenia-kernel/src/exports.rs       (+37 LOC, hook in open_vfs_file)
  crates/xenia-kernel/src/state.rs         (+18 LOC, 4 silph_synth_* fields)

Tests: cargo test --release --workspace = 765 pass / 0 fail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-07 20:44:29 +02:00
parent 9340ff4592
commit b5885b8560
4 changed files with 281 additions and 0 deletions

View File

@@ -980,6 +980,43 @@ fn open_vfs_file(
// see a null handle later and trigger `XamShowDirtyDiscErrorUI`.
let path = crate::path::object_attributes_to_vfs_path(mem, obj_attrs_ptr)
.unwrap_or_default();
// AUDIT-2.BF — synthetic silph::WorkerCtx spawn. AUDIT-058/059
// identified that ours never activates the 6-level static caller
// ladder that ends in `sub_825070F0`, so the four worker threads
// it would normally spawn (entries 0x82506528/58/88/B8) never run.
// Canary's chain originally fires right after `DiscImageDevice::
// ResolvePath("\\dat\\movie")` (audit-058); ours never opens
// `dat/movie` because tid=13 wedges before reaching it. We
// therefore trigger on the first `dat/*` open — the earliest
// such open in ours is `dat/files.tbl` (immediately preceding
// tid=12/13 spawn at audit-059 round 1).
//
// **Round 18 finding** (this commit): when the workers are
// spawned runnable, they fault almost immediately (`PC=0` at
// cycle ~5.5M on the hw thread carrying worker_3), preempting
// ours' boot before the normal guest threads even spawn. The
// ctx layout from audit-059 round 5 is incomplete — at least
// one of `[+0x28]`/`[+0x2C]`/`[+0x30]` (the three foreign-
// arena pointers) must be populated for the worker bodies to
// run. Synthesising those is a fresh investigation (round 19+).
//
// Until then the synth path is **opt-in**: set
// `XENIA_SILPH_SYNTH=1` to enable the runnable spawn (will
// crash boot), or `XENIA_SILPH_SYNTH=suspend` to spawn but keep
// them in `Blocked(Suspended)` (lets boot complete with the
// ctx materialised in memory for downstream probes). Default:
// disabled — preserves the existing boot trajectory.
if !state.silph_synth_done && path.starts_with("dat/") {
match std::env::var("XENIA_SILPH_SYNTH").as_deref() {
Ok("1") | Ok("run") | Ok("runnable") => {
let _ = crate::silph_synth::spawn_silph_workers(state, mem, false);
}
Ok("suspend") | Ok("suspended") => {
let _ = crate::silph_synth::spawn_silph_workers(state, mem, true);
}
_ => {}
}
}
if path.is_empty() && obj_attrs_ptr == 0 {
if handle_out != 0 {
mem.write_u32(handle_out, 0);