Files
xenia-rs/audit-runs/phase-c1-keQuerySystemTime/investigation.md
MechaCat02 ef93a4fa14 handoff: VSync/event-wedge fixes + iterate 2.A–2.BC research notes
Source changes (dormant parity infra, retained from iterate 2.AI/2.AO):
- xenia-kernel/exports.rs: nt_create_event manual_reset polarity +
  related event wiring
- xenia-gpu/mmio_region.rs: D1MODE_VBLANK_VLINE_STATUS hardcode parity

Also lands the audit-runs/ analysis notes (.md/.txt/.json digests) for the
iterate 2.x VSync/0x10e8/0x1004 wedge investigation. Raw trace dumps
(.jsonl/.gz/.csv/.stdout) and agent worktrees (.claude/) are gitignored as
regenerable local artifacts — see memory + HANDOFF for the running findings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 07:19:08 +02:00

4.3 KiB

Phase C+1 — KeQuerySystemTime divergence investigation

Step 1 — Locate KeQuerySystemTime

Canaryxenia-canary/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc:459-473:

void KeQuerySystemTime_entry(lpqword_t time_ptr, const ppc_context_t& ctx) {
  if (time_ptr) {
    uint32_t ts_bundle = ctx->kernel_state->GetKeTimestampBundle();
    uint64_t time = Clock::QueryGuestSystemTime();
    xe::store_and_swap<uint64_t>(
        &ctx->TranslateVirtual<X_TIME_STAMP_BUNDLE*>(ts_bundle)->system_time,
        time);
    *time_ptr = time;
  }
}
DECLARE_XBOXKRNL_EXPORT1(KeQuerySystemTime, kThreading, kImplemented);

Signature: void, takes lpqword_t time_ptr OUT-param.

Oursxenia-rs/crates/xenia-kernel/src/exports.rs:489-496:

fn ke_query_system_time(ctx: &mut PpcContext, mem: &GuestMemory, _state: &mut KernelState) {
    let time_ptr = ctx.gpr[3] as u32;
    if time_ptr != 0 {
        let fake_time: u64 = 132_500_000_000_000_000; // ~2021 FILETIME
        mem.write_u32(time_ptr, (fake_time >> 32) as u32);
        mem.write_u32(time_ptr + 4, fake_time as u32);
    }
}

Signature: fn(ctx, mem, state) — all ours exports are uniform; ours has no static type-system distinction between void and value-returning kernel exports.

Step 2 — Re-read the Phase A divergent event

From audit-runs/phase-a-diff-harness/diff-report.md:

canary [113] kernel.return KeQuerySystemTime
  return_value: 0, status: "0x00000000"

ours   [113] kernel.return KeQuerySystemTime
  return_value: 1880095840, status: "0x700ffc60"

Key observation: 1880095840 == 0x700FFC60. That is a stack-address-shaped value matching ours's stack_cursor: AtomicU32::new(0x7100_0000) region. It is the input arg pointer time_ptr left in r3.

Step 3 — Canonical semantic

The divergence is NOT in the engine's implementation of KeQuerySystemTime. Both engines write the system time to the OUT pointer; both engines decline to put anything meaningful in r3 (canary because the C++ fn is declared void, so the trampoline never calls result.Store(ppc_context); ours because ke_query_system_time simply doesn't touch ctx.gpr[3]).

The divergence is in the Phase A emitter:

Canary trampolinexenia-canary/src/xenia/kernel/util/shim_utils.h:603-622:

if constexpr (std::is_void<R>::value) {
  KernelTrampoline(fn, ...);
  if (phase_a_on) {
    phase_a_bridge::EmitReturn(export_entry->name, 0);    // LITERAL 0 for void
  }
} else {
  auto result = KernelTrampoline(fn, ...);
  result.Store(ppc_context);
  if (phase_a_on) {
    phase_a_bridge::EmitReturn(
        export_entry->name,
        static_cast<uint64_t>(ppc_context->r[3]));         // r3 for non-void
  }
}

Ours emitterxenia-rs/crates/xenia-kernel/src/state.rs:563-571 (pre-fix):

func(&mut ctx, mem, self);
if phase_a_on {
    crate::event_log::emit_kernel_return(
        phase_a_tid, ctx.cycle_count, name,
        ctx.gpr[3],                                         // ALWAYS r3
    );
}

→ Ours had no void-vs-non-void branch. For void exports that take a pointer arg (like KeQuerySystemTime), ours emitted r3 = input arg pointer untouched, while canary emitted literal 0. Pure framing asymmetry; not an engine bug.

Resolution

Path B (schema annotation), implemented as additive register_void_export API in KernelState. Only KeQuerySystemTime is marked for this session per "do not widen scope". Other void exports surfaced in the diff report (e.g. RtlInitAnsiString) are out-of-scope but trivially addressable in future sessions by extending the registry annotations.

Other divergences (catalog only — do NOT fix this session)

The diff report shows two more void-emitter divergences that the same registry pattern will trivially resolve:

  • RtlInitAnsiString (idx=2) — void in canary (xboxkrnl_rtl.cc:217).
  • KeRaiseIrqlToDpcLevel (idx=11) — NOT void: canary dword_result_t returns old_irql (typically 2). Ours has it stubbed to stub_return_zero. THIS one is a real engine bug, not an emitter issue.

And KeSetEvent at idx=5: canary returns 1, ours returns 0 — real engine divergence (ours's KeSetEvent return value bug).

Phase C+1 scope is only KeQuerySystemTime per the brief. The above are listed for the next session.