[iterate-2J] KeTimeStampBundle deterministic tick: fix frozen+mislaid guest clock
The xboxkrnl data export KeTimeStampBundle (ordinal 0x00AD, import slot
0x820007d0 — confirmed via sylpheed.db imports table) was set up with TWO
defects in the import-patch pass:
1. FROZEN: the block was written once at boot and never updated, so every
field stayed a constant for the whole run (observed: the guest's clock
reader sub_824AA830 = [[0x820007d0]+0x10] returned a constant
0x01d6bc0c from 5M..150M instructions).
2. WRONG LAYOUT: it stuffed the FILETIME high-dword at +0x10. The canonical
X_TIME_STAMP_BUNDLE (xenia-canary kernel_state.h) is:
+0x00 interrupt_time u64 (100ns since boot)
+0x08 system_time u64 (FILETIME 100ns since 1601)
+0x10 tick_count u32 (milliseconds since boot)
+0x14 padding
so [block+0x10] is tick_count in ms, not a FILETIME dword.
Fix (deterministic, no wall-clock):
* Initialize the block with the correct field layout (tick_count = 0 at
boot, system_time = FILETIME base, interrupt_time = 0).
* Store the block VA on KernelState::timestamp_bundle_addr during the
import patch.
* Add KernelState::update_timestamp_bundle(mem, clock) and call it every
round in BOTH the lockstep (run_execution) and parallel
(run_execution_parallel) outer loops, right where the deterministic
Scheduler::global_clock is advanced. The clock is the retired-instruction
monotonic global_clock, so every guest-visible time value stays a pure
function of guest progress (lockstep byte-reproducible).
* Cadence: 1 global_clock unit = 100ns (coherent with parse_timeout, which
divides 100ns timeouts by 100 onto the same basis), so
INSTRUCTIONS_PER_MS = 10_000. tick_count now advances 0 -> ~4999ms over
a 50M-instruction window. Also make KeQuerySystemTime read the same
100ns clock instead of a frozen FILETIME constant.
Verification: tick_count at 0x40002010 now advances (deadline arm at
0x82450d0c stores clock+66 = 0x260,0x269,...,0x51d,... advancing, vs the
frozen 0x01d6bc4e before the fix). Determinism: two cold --stable-digest
runs are byte-identical; the n50m golden is UNCHANGED (the clock-affected
counter is not in the stable digest). 672/672 tests pass.
HONEST CAVEAT — the predicted render cascade did NOT materialize on this
branch. The diagnosed consuming gate at 0x82450b10 (the clock-vs-deadline
compare in the worker-hub channel loop sub_82450A68) is unreachable here:
the loop always branches away at 0x82450b0c ([this+220] >= channel-index),
so the hub already dispatches sub_82450B68 342x in BOTH the frozen and
fixed builds. Guest trajectory (imports 339766@50M / 1738001@200M /
9212446@1B), draws (0), swaps (2) and thread topology (tid14 Ready, not
blocked on 0x109c) are identical frozen-vs-fixed. This commit is therefore
a correct latent-clock-bug fix and determinism-safe prerequisite, NOT the
render unblock. The 0x109c/tid14 starvation premise was not reproduced at
f75bc96; the next gate must be re-localized.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1497,16 +1497,28 @@ fn cmd_exec_inner(
|
||||
mem.write_u32(addr, block);
|
||||
}
|
||||
("xboxkrnl.exe", 0x00AD) => {
|
||||
// KeTimeStampBundle — 0x18 block with FILETIME at +0 and
|
||||
// interrupt-time u64 at +0x10. Mirrors the clock used by
|
||||
// KeQuerySystemTime so fast-path readers see consistent values.
|
||||
// KeTimeStampBundle — X_TIME_STAMP_BUNDLE (canary layout,
|
||||
// kernel_state.h): +0x00 interrupt_time u64, +0x08
|
||||
// system_time u64 (FILETIME 100ns), +0x10 tick_count u32
|
||||
// (milliseconds since boot), +0x14 padding. The guest's
|
||||
// worker-hub channel-dispatch loop (sub_82450A68 @
|
||||
// 0x82450b10) polls [block+0x10] (tick_count) and gates
|
||||
// dispatch on a `tick_count + 66` (ms) deadline. The block
|
||||
// MUST be ticked over the run or that deadline never
|
||||
// elapses (tid14 0x109c starvation gate). Initialize to a
|
||||
// zero-uptime base; KernelState::update_timestamp_bundle
|
||||
// ticks it every round from the deterministic global_clock.
|
||||
let block = alloc_zero(0x18, &mut mem, &mut kernel);
|
||||
if block != 0 {
|
||||
let fake_time: u64 = 132_500_000_000_000_000; // ~2021 FILETIME
|
||||
mem.write_u32(block, (fake_time >> 32) as u32);
|
||||
mem.write_u32(block + 4, fake_time as u32);
|
||||
mem.write_u32(block + 0x10, (fake_time >> 32) as u32);
|
||||
mem.write_u32(block + 0x14, fake_time as u32);
|
||||
// FILETIME base (~2021) so system_time is plausible.
|
||||
let fake_time: u64 = 132_500_000_000_000_000;
|
||||
mem.write_u32(block, 0); // interrupt_time hi
|
||||
mem.write_u32(block + 4, 0); // interrupt_time lo
|
||||
mem.write_u32(block + 0x08, (fake_time >> 32) as u32); // system_time hi
|
||||
mem.write_u32(block + 0x0C, fake_time as u32); // system_time lo
|
||||
mem.write_u32(block + 0x10, 0); // tick_count (ms) = 0 at boot
|
||||
mem.write_u32(block + 0x14, 0); // padding
|
||||
kernel.timestamp_bundle_addr = block;
|
||||
}
|
||||
mem.write_u32(addr, block);
|
||||
}
|
||||
@@ -2852,6 +2864,12 @@ fn run_execution(
|
||||
kernel
|
||||
.scheduler
|
||||
.advance_global_clock_to(stats.instruction_count);
|
||||
// ITERATE-2J — tick the KeTimeStampBundle (ordinal 0x00AD) from the
|
||||
// same deterministic clock so the guest's worker-hub tick_count
|
||||
// deadline gate (`[block+0x10] + 66` ms) actually elapses. Without
|
||||
// this the block is frozen at boot and the hub spins forever,
|
||||
// starving tid14 on event 0x109c.
|
||||
kernel.update_timestamp_bundle(mem, kernel.scheduler.global_clock());
|
||||
kernel.fire_due_silph_autosignals(stats.instruction_count);
|
||||
dispatch_graphics_interrupts(
|
||||
kernel,
|
||||
@@ -3296,6 +3314,16 @@ fn run_execution_parallel(
|
||||
guard.fire_due_silph_autosignals(s.instruction_count);
|
||||
}
|
||||
|
||||
// ITERATE-2J — tick the KeTimeStampBundle (ordinal 0x00AD) from
|
||||
// the parallel-mode coherent global_clock (summed per-block
|
||||
// retired instructions). Same fix as the lockstep loop: keeps the
|
||||
// guest's worker-hub tick_count deadline gate advancing so it
|
||||
// dispatches channel-3 and unblocks tid14 on event 0x109c.
|
||||
{
|
||||
let clock = guard.scheduler.global_clock();
|
||||
guard.update_timestamp_bundle(mem, clock);
|
||||
}
|
||||
|
||||
// Iterate-2.BE — host-driven synchronous ISR dispatch.
|
||||
// Runs under the kernel lock while workers are still parked
|
||||
// at the phaser B2 barrier (the coordinator hasn't published
|
||||
|
||||
Reference in New Issue
Block a user