[AUDIT-059 R-D2] Phase D auto-signal POC confirms audit-049 wedge diagnosis

Hook NtCreateEvent for the silph::UImpl tid=13 chain (entry=0x821748F0,
start_context=0x4024a840, frame-1 LR=0x821CB15C inside sub_821CB030+0x128)
and auto-signal the resulting handle after XENIA_SILPH_UI_AUTOSIGNAL_DELAY
instructions. Env-gated; default off.

SR4 verdict B (partial unwedge):
- handle 0x1078 signal_attempts 0->1
- tid=13 Blocked(WaitAny[0x1078]) -> Ready pc=0x824a9108
- ExCreateThread 10 -> 12 (new silph::UImpl tid=14, worker tid=15)
- New downstream wedges 0x1084 + 0x1088
- cxx_throw runtime_error on tid=5 inside R26 dispatcher
  (BST not-registered instance lhs=0x715a7af0)
- VdSwap stays 1; no draws (POC is diagnostic, not final fix)

Confirms Phase C diagnosis end-to-end. The real signaler must (a) drive
NtSetEvent on the silph KEVENT AND (b) register the dispatcher's BST
instance upstream; this POC only does (a).

Reading-error class #20: ctx.lr at kernel export entry is the thunk
wrapper's return slot, NOT the guest caller's post-bl PC. Walk back-chain
1 step to get frames[1].lr.

Reading-error class #21: --parallel and lockstep have SEPARATE outer
loops in main.rs (run_execution_parallel line 2928 vs run_execution
line 2706). Per-round hooks must be wired in BOTH paths.

Files:
- crates/xenia-cpu/src/scheduler.rs: GuestThread.start_entry/start_context
  fields + spawn() population + current_thread_entry_and_ctx() helper
- crates/xenia-kernel/src/state.rs: AutoSignalPending struct, env-parsed
  silph_autosignal_delay, pending Vec, last_cycle_hint, set_now_cycle_hint,
  maybe_register_silph_autosignal (walks back-chain), fire_due_silph_autosignals
- crates/xenia-kernel/src/exports.rs: hook in nt_create_event
- crates/xenia-app/src/main.rs: fire-site + cycle hint in both outer loops
- audit-runs/audit-059-handle-disambiguation/round-D2-autosignal-poc/FINDINGS.md

Tests 655/655 green. Default behavior byte-identical when env unset.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-11 18:38:38 +02:00
parent 481591fdb2
commit db90ad0f7d
5 changed files with 334 additions and 0 deletions

View File

@@ -117,6 +117,14 @@ pub struct GuestThread {
/// Axis 3 instruction budget. Decremented per retired step on this
/// thread; on zero, slot rotates within same-priority tier.
pub quantum_remaining: u32,
/// SpawnParams.entry — the BL target the trampoline jumped to.
/// Persisted so kernel exports can filter syscalls by spawning
/// chain (e.g. the silph UI auto-signal POC). 0 for the initial
/// thread (uses `install_initial_thread`, not `spawn`).
pub start_entry: u32,
/// SpawnParams.start_context — initial r3 at spawn. Persisted for
/// the same filtering reason as `start_entry`.
pub start_context: u32,
}
impl GuestThread {
@@ -136,6 +144,8 @@ impl GuestThread {
affinity_mask: 0xFF,
ideal_processor: None,
quantum_remaining: QUANTUM_DEFAULT,
start_entry: 0,
start_context: 0,
}
}
}
@@ -500,6 +510,17 @@ impl Scheduler {
self.current.expect("no current thread")
}
/// `(start_entry, start_context)` of the currently-running thread.
/// Returns None if there is no current thread or its ref is stale.
/// Used by `KernelState::maybe_register_silph_autosignal` to filter
/// `NtCreateEvent` calls by spawning chain.
pub fn current_thread_entry_and_ctx(&self) -> Option<(u32, u32)> {
let r = self.current?;
let slot = self.slots.get(r.hw_id as usize)?;
let t = slot.runqueue.get(r.idx as usize)?;
Some((t.start_entry, t.start_context))
}
// ----- Guest-thread lookup -----
/// Find the `ThreadRef` of the (non-Exited) thread with `tid`.
@@ -614,6 +635,8 @@ impl Scheduler {
t.priority = params.priority;
t.affinity_mask = mask;
t.ideal_processor = params.ideal_processor;
t.start_entry = params.entry;
t.start_context = params.start_context;
// M3.7 — populate the inter-thread reservation handle + slot id
// so the interpreter can route lwarx/stwcx through the table.
t.ctx.hw_id = slot_id;