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>
This commit is contained in:
103
audit-runs/phase-c1-keQuerySystemTime/investigation.md
Normal file
103
audit-runs/phase-c1-keQuerySystemTime/investigation.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# Phase C+1 — KeQuerySystemTime divergence investigation
|
||||
|
||||
## Step 1 — Locate KeQuerySystemTime
|
||||
|
||||
**Canary** — `xenia-canary/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc:459-473`:
|
||||
|
||||
```cpp
|
||||
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.
|
||||
|
||||
**Ours** — `xenia-rs/crates/xenia-kernel/src/exports.rs:489-496`:
|
||||
|
||||
```rust
|
||||
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 trampoline** — `xenia-canary/src/xenia/kernel/util/shim_utils.h:603-622`:
|
||||
|
||||
```cpp
|
||||
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 emitter** — `xenia-rs/crates/xenia-kernel/src/state.rs:563-571` (pre-fix):
|
||||
|
||||
```rust
|
||||
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.
|
||||
Reference in New Issue
Block a user