From c03f2bc9e22ffab81a62c7eb14d7bf4f5773248d Mon Sep 17 00:00:00 2001 From: MechaCat02 Date: Thu, 7 May 2026 21:06:25 +0200 Subject: [PATCH] fix(kernel): ensure_dispatcher_object writes XObj signature + handle (canary mirror) Mirrors canary's `XObject::StashHandle` (xobject.h:253-256): on first adoption of a guest dispatcher header, stamp +0x08 with the kXObjSignature fourcc 'X','E','N','\0' and +0x0C with the stash handle (here the guest pointer itself, since our shadow table is keyed by ptr). Audit-023/024A documented divergence at addresses such as 0x828F4838 where canary stores "XEN\0" + handle but we left zeros. Lands as canary-correctness restoration; cascade impact at -n 500M is nil per the discipline gate (no sharp prediction tied to the writeback). Lockstep determinism preserved: instructions=100000003, imports=987516, swaps=2, draws=0 across 2 reruns. Co-Authored-By: Claude Opus 4.7 (1M context) --- crates/xenia-kernel/src/exports.rs | 35 ++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/crates/xenia-kernel/src/exports.rs b/crates/xenia-kernel/src/exports.rs index 40e9ab8..e42aa2c 100644 --- a/crates/xenia-kernel/src/exports.rs +++ b/crates/xenia-kernel/src/exports.rs @@ -3094,6 +3094,12 @@ fn ensure_dispatcher_object(state: &mut KernelState, mem: &GuestMemory, ptr: u32 _ => return, }; state.objects.insert(ptr, obj); + // Mirror canary `XObject::StashHandle` (xobject.h:253-256): on first + // adoption, stamp the X_DISPATCH_HEADER's wait_list with the kXObjSignature + // fourcc 'X','E','N','\0' (flink_ptr) and the stash handle (blink_ptr). + // Game code reads these to recognize already-adopted dispatchers. + mem.write_u32(ptr + 0x08, 0x58454E00); + mem.write_u32(ptr + 0x0C, ptr); } /// Set `gpr[3]` on a just-woken HW thread to reflect which handle in its @@ -4601,6 +4607,35 @@ mod tests { write_dispatcher_header(&mut mem, ptr, 2, 0); // Mutant — unsupported ensure_dispatcher_object(&mut state, &mem, ptr); assert!(!state.objects.contains_key(&ptr), "no shadow for unknown type"); + // No StashHandle stamp on an ignored dispatcher. + assert_eq!(mem.read_u32(ptr + 0x08), 0); + assert_eq!(mem.read_u32(ptr + 0x0C), 0); + } + + /// Mirror canary `XObject::StashHandle` (xobject.h:253-256): on first + /// adoption of a guest dispatcher, +0x08 must hold the 'X','E','N','\0' + /// fourcc and +0x0C must hold the stash handle. + #[test] + fn ensure_dispatcher_object_stamps_xen_signature_and_handle() { + let (mut ctx, mut mem, mut state) = fresh(); + let kevent_ptr = SCRATCH_BASE + 0x700; + write_dispatcher_header(&mut mem, kevent_ptr, 1, 0); // synchronization + // Pre-condition: zeros at +0x08 / +0x0C. + assert_eq!(mem.read_u32(kevent_ptr + 0x08), 0); + assert_eq!(mem.read_u32(kevent_ptr + 0x0C), 0); + ctx.gpr[3] = kevent_ptr as u64; + ke_set_event(&mut ctx, &mut mem, &mut state); + // Post-condition: kXObjSignature ('X','E','N','\0') + stash handle. + assert_eq!( + mem.read_u32(kevent_ptr + 0x08), + 0x58454E00, + "wait_list.flink_ptr must hold kXObjSignature 'XEN\\0'" + ); + assert_eq!( + mem.read_u32(kevent_ptr + 0x0C), + kevent_ptr, + "wait_list.blink_ptr must hold stash handle (== guest dispatcher ptr)" + ); } /// `KePulseEvent` on a manual-reset event must wake every parked waiter