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>
158 lines
8.1 KiB
Diff
158 lines
8.1 KiB
Diff
diff --git a/crates/xenia-kernel/src/exports.rs b/crates/xenia-kernel/src/exports.rs
|
|
index a4dfa7d..56232fb 100644
|
|
--- a/crates/xenia-kernel/src/exports.rs
|
|
+++ b/crates/xenia-kernel/src/exports.rs
|
|
@@ -46,7 +46,13 @@ pub fn register_exports(state: &mut KernelState) {
|
|
state.register_export(Xboxkrnl, 0x81, "KeQueryBasePriorityThread", ke_query_base_priority_thread);
|
|
state.register_export(Xboxkrnl, 0x82, "KeQueryIdealProcessor", ke_query_ideal_processor);
|
|
state.register_export(Xboxkrnl, 0x83, "KeQueryPerformanceFrequency", ke_query_performance_frequency);
|
|
- state.register_export(Xboxkrnl, 0x84, "KeQuerySystemTime", ke_query_system_time);
|
|
+ // Canary declares `void KeQuerySystemTime_entry(lpqword_t time_ptr, ...)`
|
|
+ // (xboxkrnl_threading.cc:459); the time is delivered via the OUT
|
|
+ // pointer, not via gpr[3]. Phase A's `kernel.return.return_value`
|
|
+ // must be 0 (canary literal) — not r3 (which for ours is the input
|
|
+ // arg `time_ptr` left untouched). See `register_void_export` doc in
|
|
+ // state.rs.
|
|
+ state.register_void_export(Xboxkrnl, 0x84, "KeQuerySystemTime", ke_query_system_time);
|
|
state.register_export(Xboxkrnl, 0x85, "KeRaiseIrqlToDpcLevel", stub_return_zero);
|
|
state.register_export(Xboxkrnl, 0x88, "KeReleaseSemaphore", ke_release_semaphore);
|
|
state.register_export(Xboxkrnl, 0x89, "KeReleaseSpinLockFromRaisedIrql", ke_release_spinlock_from_raised_irql);
|
|
diff --git a/crates/xenia-kernel/src/state.rs b/crates/xenia-kernel/src/state.rs
|
|
index b256fe7..b076ff7 100644
|
|
--- a/crates/xenia-kernel/src/state.rs
|
|
+++ b/crates/xenia-kernel/src/state.rs
|
|
@@ -50,6 +50,17 @@ pub const HMODULE_XAM: u32 = 0xFFFE_0002;
|
|
/// Central kernel state tracking all guest OS state.
|
|
pub struct KernelState {
|
|
exports: HashMap<(ModuleId, u32), (&'static str, KernelExportFn)>,
|
|
+ /// Phase A: kernel exports whose canary signature is `void` (no
|
|
+ /// dword_result_t / pointer_result_t). For symmetry with canary's
|
|
+ /// `if constexpr (std::is_void<R>::value)` trampoline branch
|
|
+ /// (see `xenia-canary/src/xenia/kernel/util/shim_utils.h`), the
|
|
+ /// Phase A `kernel.return` event for these exports emits
|
|
+ /// `return_value=0` instead of `gpr[3]` (which for void fns is
|
|
+ /// just the input arg pointer left untouched). Without this,
|
|
+ /// e.g. `KeQuerySystemTime` — declared `void` in canary, taking a
|
|
+ /// `lpqword_t time_ptr` — would report ours's r3=time_ptr but
|
|
+ /// canary's literal 0, producing a spurious diff. Cvar-OFF inert.
|
|
+ void_exports: std::collections::HashSet<(ModuleId, u32)>,
|
|
/// M2.4: bump allocator for kernel handles. `AtomicU32` so concurrent
|
|
/// HLE calls under M3 can `fetch_add` without a lock. `Relaxed` is
|
|
/// fine — the allocated value is a fresh ID with no prior payload to
|
|
@@ -264,6 +275,23 @@ pub struct KernelState {
|
|
pub dump_addrs: Vec<u32>,
|
|
/// `--dump-section=BASE:LEN:PATH` end-of-run snapshot, page-gated by `is_mapped`.
|
|
pub dump_section: Option<(u32, u32, std::path::PathBuf)>,
|
|
+ /// Phase B initial-state snapshot — directory under which a
|
|
+ /// `ours/{cpu_state,memory,kernel,vfs,config}.json` + `manifest.json`
|
|
+ /// snapshot is written at the moment immediately before the first
|
|
+ /// guest PPC instruction of the XEX entry_point. `None` (default) =
|
|
+ /// disabled, zero overhead. See
|
|
+ /// `xenia-rs/audit-runs/phase-b-state-equivalence/`.
|
|
+ pub phase_b_snapshot_dir: Option<std::path::PathBuf>,
|
|
+ /// Phase B: after writing the snapshot, exit the process immediately
|
|
+ /// so re-runs are byte-deterministic. Default false.
|
|
+ pub phase_b_snapshot_and_exit: bool,
|
|
+ /// Phase B: include raw bytes in `memory.json`'s `section_contents`.
|
|
+ /// Default false — per-region SHA-256 is enough for the routine diff.
|
|
+ pub phase_b_dump_section_content: bool,
|
|
+ /// Phase B: the XEX entry_point address — captured by the app at
|
|
+ /// `install_initial_thread` time and consulted by the snapshot hook
|
|
+ /// to validate the firing thread is the entry thread.
|
|
+ pub entry_pc: u32,
|
|
}
|
|
|
|
impl KernelState {
|
|
@@ -288,6 +316,7 @@ impl KernelState {
|
|
scheduler.set_reservation_table(Some(reservations.clone()));
|
|
let mut state = Self {
|
|
exports: HashMap::new(),
|
|
+ void_exports: std::collections::HashSet::new(),
|
|
next_handle: AtomicU32::new(0x1000),
|
|
scheduler,
|
|
next_tls_index: AtomicU32::new(0),
|
|
@@ -331,6 +360,10 @@ impl KernelState {
|
|
lr_trace_writer: None,
|
|
dump_addrs: Vec::new(),
|
|
dump_section: None,
|
|
+ phase_b_snapshot_dir: None,
|
|
+ phase_b_snapshot_and_exit: false,
|
|
+ phase_b_dump_section_content: false,
|
|
+ entry_pc: 0,
|
|
};
|
|
crate::exports::register_exports(&mut state);
|
|
crate::xam::register_exports(&mut state);
|
|
@@ -377,6 +410,22 @@ impl KernelState {
|
|
self.exports.insert((module, ordinal), (name, func));
|
|
}
|
|
|
|
+ /// Register a kernel export whose canary signature is `void`.
|
|
+ /// See `KernelState::void_exports` doc. Identical semantics to
|
|
+ /// `register_export` except the Phase A `kernel.return` payload's
|
|
+ /// `return_value` field is emitted as 0 instead of `gpr[3]`,
|
|
+ /// matching canary's `EmitReturn(name, 0)` branch.
|
|
+ pub fn register_void_export(
|
|
+ &mut self,
|
|
+ module: ModuleId,
|
|
+ ordinal: u32,
|
|
+ name: &'static str,
|
|
+ func: KernelExportFn,
|
|
+ ) {
|
|
+ self.exports.insert((module, ordinal), (name, func));
|
|
+ self.void_exports.insert((module, ordinal));
|
|
+ }
|
|
+
|
|
/// AUDIT-038 — install a host directory as the backing store for the
|
|
/// `cache:` mount. The directory is unconditionally cleared (and then
|
|
/// re-created) on entry so two consecutive runs see byte-identical
|
|
@@ -514,7 +563,49 @@ impl KernelState {
|
|
metrics::counter!("kernel.calls", "name" => name).increment(1);
|
|
tracing::trace!(target: "probe_calls", "hw={} call={} r3={:#x} r4={:#x} r5={:#x} lr={:#x}",
|
|
r.hw_id, name, ctx.gpr[3], ctx.gpr[4], ctx.gpr[5], ctx.lr);
|
|
+ // Phase A event log — see crates/xenia-kernel/src/event_log.rs.
|
|
+ // Hot path: `is_enabled` is a relaxed atomic-bool load.
|
|
+ let phase_a_on = crate::event_log::is_enabled();
|
|
+ let (phase_a_tid, phase_a_cycle) = if phase_a_on {
|
|
+ let tid = self.scheduler.thread(r).tid;
|
|
+ let cycle = ctx.cycle_count;
|
|
+ (tid, cycle)
|
|
+ } else {
|
|
+ (0u32, 0u64)
|
|
+ };
|
|
+ if phase_a_on {
|
|
+ let module_name = match module {
|
|
+ ModuleId::Xboxkrnl => "xboxkrnl.exe",
|
|
+ ModuleId::Xam => "xam.xex",
|
|
+ ModuleId::Xbdm => "xbdm.xex",
|
|
+ };
|
|
+ crate::event_log::emit_import_call(
|
|
+ phase_a_tid,
|
|
+ phase_a_cycle,
|
|
+ module_name,
|
|
+ ordinal as u16,
|
|
+ name,
|
|
+ );
|
|
+ crate::event_log::emit_kernel_call(phase_a_tid, phase_a_cycle, name);
|
|
+ }
|
|
+ let is_void = self.void_exports.contains(&(module, ordinal));
|
|
func(&mut ctx, mem, self);
|
|
+ if phase_a_on {
|
|
+ // Mirror canary's `if constexpr (std::is_void<R>::value)`
|
|
+ // trampoline branch: void exports emit literal 0; non-void
|
|
+ // emit post-call gpr[3]. Without this, void exports that
|
|
+ // take a pointer arg (e.g. `KeQuerySystemTime`) would
|
|
+ // report ours=r3=arg_ptr vs canary=0 — a Phase A diff
|
|
+ // that is purely an emitter-framing asymmetry, not an
|
|
+ // engine semantic divergence.
|
|
+ let return_value = if is_void { 0 } else { ctx.gpr[3] };
|
|
+ crate::event_log::emit_kernel_return(
|
|
+ phase_a_tid,
|
|
+ ctx.cycle_count,
|
|
+ name,
|
|
+ return_value,
|
|
+ );
|
|
+ }
|
|
true
|
|
} else {
|
|
metrics::counter!("kernel.unimplemented").increment(1);
|