[iterate-4A] intro-video RE: XENIA_AUDIT_PC_TRACE (per-tid PC-range r3/r4/r5/r30/r31 trace)
Observe-only diagnostic (lockstep digest unaffected) added during the milestone-2 video investigation. `XENIA_AUDIT_PC_TRACE=lo:hi:tid` logs AUDIT-TRACE r3/r4/r5/r30/r31 for blocks in [lo,hi) on a given tid — used to capture the demux registrar's stream-id (r4) at sub_82509E40. Handoff (full detail in memory cont.10ff-10ii): - Video decode is GUEST SOFTWARE (decoder vt 0x82009f70, 615 fns/30k instr, zero host/video imports) — no host codec to build; fix is upstream. - Video IS registered/routed/queued (refutes "not selected"); the engine never reaches ready-state, pump sub_825078D8 bails 5262x, begin-playback sub_825076F0 never fires -> 2000ms readiness timeout. - CANARY oracle measured: video producer sub_824FF678 drives demux SM sub_825211A0 72x+ (loops/pulls continuously); OURS drives it 1x then the pull loop sub_824FA8A0 exits on no-data (assembly sub_8250A2B0 returns 0x8050000B = incomplete unit). Root: ours delivers INCOMPLETE video units where canary delivers complete ones. - NEXT: compare ours-vs-canary fragment chain [slot+24] for the video stream (missing end-fragment / fragment-header mis-parse). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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<u32> {
|
||||||
|
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
|
// Diagnostic. Parse `--dump-addr=0x828F3D08,...` (or
|
||||||
// `XENIA_DUMP_ADDR=...`) into `kernel.dump_addrs`. The contents
|
// `XENIA_DUMP_ADDR=...`) into `kernel.dump_addrs`. The contents
|
||||||
// are dumped at end-of-run by `dump_thread_diagnostic`. Pure
|
// are dumped at end-of-run by `dump_thread_diagnostic`. Pure
|
||||||
|
|||||||
@@ -328,6 +328,18 @@ pub struct KernelState {
|
|||||||
/// the pool recycles the slot. Read-only; lockstep digest unaffected.
|
/// the pool recycles the slot. Read-only; lockstep digest unaffected.
|
||||||
/// Settable via `XENIA_AUDIT_DEREF=<reg>:<off>` (e.g. `4:36`).
|
/// Settable via `XENIA_AUDIT_DEREF=<reg>:<off>` (e.g. `4:36`).
|
||||||
pub audit_deref: Option<(u8, u32)>,
|
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
|
/// M12 — diagnostic. PCs at which to emit a structured JSONL record
|
||||||
/// per fire, designed for diffing against xenia-canary's
|
/// per fire, designed for diffing against xenia-canary's
|
||||||
/// `--log_lr_on_pc` patch output. Each line carries
|
/// `--log_lr_on_pc` patch output. Each line carries
|
||||||
@@ -479,6 +491,7 @@ impl KernelState {
|
|||||||
audit_mem_read_addr: None,
|
audit_mem_read_addr: None,
|
||||||
audit_r3_dump_bytes: None,
|
audit_r3_dump_bytes: None,
|
||||||
audit_deref: None,
|
audit_deref: None,
|
||||||
|
audit_pc_trace: None,
|
||||||
lr_trace_pcs: std::collections::HashSet::new(),
|
lr_trace_pcs: std::collections::HashSet::new(),
|
||||||
lr_trace_writer: None,
|
lr_trace_writer: None,
|
||||||
dump_addrs: Vec::new(),
|
dump_addrs: Vec::new(),
|
||||||
@@ -1132,6 +1145,7 @@ impl KernelState {
|
|||||||
|| !self.branch_probe_pcs.is_empty()
|
|| !self.branch_probe_pcs.is_empty()
|
||||||
|| !self.audit_pc_probe_pcs.is_empty()
|
|| !self.audit_pc_probe_pcs.is_empty()
|
||||||
|| !self.lr_trace_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
|
/// 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
|
/// Empty set is the common case → single `is_empty()` test on the
|
||||||
/// hot path.
|
/// hot path.
|
||||||
pub fn fire_audit_pc_probe_if_match(&self, hw_id: u8, mem: &GuestMemory) {
|
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() {
|
if self.audit_pc_probe_pcs.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user