diff --git a/crates/xenia-app/src/main.rs b/crates/xenia-app/src/main.rs index 08c9c3d..31cc048 100644 --- a/crates/xenia-app/src/main.rs +++ b/crates/xenia-app/src/main.rs @@ -1336,6 +1336,35 @@ fn cmd_exec_inner( } } + // milestone-2 demux BB-trace. `XENIA_AUDIT_PC_TRACE=lo:hi:tid` + // (hex addrs, decimal tid; tid 0 = all). Emits a compact `AUDIT-TRACE` + // line at every block-entry inside [lo,hi) for the matching thread — + // a BB-granularity control-flow trace. Used to compare how two thread + // instances diverge through the demux read state machine. + if let Ok(spec) = std::env::var("XENIA_AUDIT_PC_TRACE") { + if !spec.is_empty() { + let parts: Vec<&str> = spec.split(':').collect(); + if parts.len() != 3 { + return Err(anyhow::anyhow!( + "XENIA_AUDIT_PC_TRACE {spec:?}: expected lo:hi:tid" + )); + } + let parse_hex = |s: &str| -> anyhow::Result { + let h = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")).unwrap_or(s); + u32::from_str_radix(h, 16) + .map_err(|e| anyhow::anyhow!("XENIA_AUDIT_PC_TRACE addr {s:?}: {e}")) + }; + let lo = parse_hex(parts[0])?; + let hi = parse_hex(parts[1])?; + let tid: u32 = parts[2].trim().parse() + .map_err(|e| anyhow::anyhow!("XENIA_AUDIT_PC_TRACE tid {:?}: {e}", parts[2]))?; + kernel.audit_pc_trace = Some((lo, hi, tid)); + if !quiet { + tracing::info!("audit-pc-trace armed: [{:#010x},{:#010x}) tid={}", lo, hi, tid); + } + } + } + // Diagnostic. Parse `--dump-addr=0x828F3D08,...` (or // `XENIA_DUMP_ADDR=...`) into `kernel.dump_addrs`. The contents // are dumped at end-of-run by `dump_thread_diagnostic`. Pure diff --git a/crates/xenia-kernel/src/state.rs b/crates/xenia-kernel/src/state.rs index c15a6ad..7656a2a 100644 --- a/crates/xenia-kernel/src/state.rs +++ b/crates/xenia-kernel/src/state.rs @@ -328,6 +328,18 @@ pub struct KernelState { /// the pool recycles the slot. Read-only; lockstep digest unaffected. /// Settable via `XENIA_AUDIT_DEREF=:` (e.g. `4:36`). pub audit_deref: Option<(u8, u32)>, + /// milestone-2 demux trace — `(lo, hi, tid)`. When set, every + /// block-entry whose PC is in `[lo, hi)` and (if `tid != 0`) whose + /// running thread id equals `tid` emits a compact one-line + /// `AUDIT-TRACE` record with `(pc, tid, cycle, lr, r3, r4, r5, r30, + /// r31)`. Because every basic block begins at a branch target, this + /// yields a BB-granularity control-flow trace of a single thread's + /// path through a function range — exactly what's needed to see WHICH + /// branch a demux read takes (e.g. the no-data exit) and how two + /// thread instances of the same code diverge. Read-only; lockstep + /// digest unaffected. Settable via `XENIA_AUDIT_PC_TRACE=lo:hi:tid` + /// (hex addrs, decimal tid; tid 0 = all threads). + pub audit_pc_trace: Option<(u32, u32, u32)>, /// M12 — diagnostic. PCs at which to emit a structured JSONL record /// per fire, designed for diffing against xenia-canary's /// `--log_lr_on_pc` patch output. Each line carries @@ -479,6 +491,7 @@ impl KernelState { audit_mem_read_addr: None, audit_r3_dump_bytes: None, audit_deref: None, + audit_pc_trace: None, lr_trace_pcs: std::collections::HashSet::new(), lr_trace_writer: None, dump_addrs: Vec::new(), @@ -1132,6 +1145,7 @@ impl KernelState { || !self.branch_probe_pcs.is_empty() || !self.audit_pc_probe_pcs.is_empty() || !self.lr_trace_pcs.is_empty() + || self.audit_pc_trace.is_some() } /// Diagnostic. If the live PC for HW slot `hw_id` is in @@ -1230,6 +1244,25 @@ impl KernelState { /// Empty set is the common case → single `is_empty()` test on the /// hot path. pub fn fire_audit_pc_probe_if_match(&self, hw_id: u8, mem: &GuestMemory) { + // milestone-2 demux BB-trace. Independent of the pc-set probe + // below; fires at every block-entry inside the configured PC + // range for the configured thread. Read-only. + if let Some((lo, hi, want_tid)) = self.audit_pc_trace { + let ctx = self.scheduler.ctx(hw_id); + let pc = ctx.pc; + if pc >= lo && pc < hi { + let tid = self.scheduler.tid(hw_id).unwrap_or(0); + if want_tid == 0 || tid == want_tid { + println!( + "AUDIT-TRACE pc={:#010x} tid={} cycle={} lr={:#010x} \ + r3={:#010x} r4={:#010x} r5={:#010x} r30={:#010x} r31={:#010x}", + pc, tid, ctx.cycle_count, ctx.lr as u32, + ctx.gpr[3] as u32, ctx.gpr[4] as u32, ctx.gpr[5] as u32, + ctx.gpr[30] as u32, ctx.gpr[31] as u32, + ); + } + } + } if self.audit_pc_probe_pcs.is_empty() { return; }