[iterate-2E] Add XENIA_AUDIT_DEREF pointer-chase probe
On each AUDIT-PC-PROBE fire, treat gpr[reg] as a base object, dump its first 64 bytes, follow [base+off] to a sub-object, dump that, then follow [[base+off]+0] to its vtable and dump 48 slots. Env-gated (XENIA_AUDIT_DEREF=<reg>:<off>), read-only, lockstep digest unaffected. Captures the live work-item + stream object + vtable at sub_824510E0 before the pool recycles the slot — which overturned the prior session's "infinite spin" diagnosis: the streaming read PROGRESSES 68/68 128KB chunks of a 9MB file, then the hub (tid=5) blocks INFINITE on a self-created Event/Manual (0x1060) that is never signaled. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1301,6 +1301,29 @@ fn cmd_exec_inner(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// iterate-2E — pointer-chase probe. `XENIA_AUDIT_DEREF=<reg>:<off>`
|
||||||
|
// (e.g. `4:36`). On each AUDIT-PC-PROBE fire, dumps gpr[reg] as a base
|
||||||
|
// object, the sub-object at [base+off], and that sub-object's vtable.
|
||||||
|
// Read-only; lockstep digest unaffected.
|
||||||
|
if let Ok(spec) = std::env::var("XENIA_AUDIT_DEREF") {
|
||||||
|
if !spec.is_empty() {
|
||||||
|
let (rs, os) = spec
|
||||||
|
.split_once(':')
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("XENIA_AUDIT_DEREF {spec:?}: expected <reg>:<off>"))?;
|
||||||
|
let reg: u8 = rs.trim_start_matches('r').parse()
|
||||||
|
.map_err(|e| anyhow::anyhow!("XENIA_AUDIT_DEREF reg {rs:?}: {e}"))?;
|
||||||
|
let off: u32 = if let Some(h) = os.strip_prefix("0x") {
|
||||||
|
u32::from_str_radix(h, 16)
|
||||||
|
} else {
|
||||||
|
os.parse::<u32>()
|
||||||
|
}.map_err(|e| anyhow::anyhow!("XENIA_AUDIT_DEREF off {os:?}: {e}"))?;
|
||||||
|
kernel.audit_deref = Some((reg, off));
|
||||||
|
if !quiet {
|
||||||
|
tracing::info!("audit-deref armed: r{} +0x{:x}", reg, off);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
|||||||
@@ -291,6 +291,17 @@ pub struct KernelState {
|
|||||||
/// Settable via `--audit-r3-dump-bytes` /
|
/// Settable via `--audit-r3-dump-bytes` /
|
||||||
/// `XENIA_AUDIT_R3_DUMP_BYTES`.
|
/// `XENIA_AUDIT_R3_DUMP_BYTES`.
|
||||||
pub audit_r3_dump_bytes: Option<u32>,
|
pub audit_r3_dump_bytes: Option<u32>,
|
||||||
|
/// iterate-2E — diagnostic pointer-chase. `(reg, off)`: on every
|
||||||
|
/// `AUDIT-PC-PROBE` fire, treat `gpr[reg]` as a base object pointer,
|
||||||
|
/// dump its first 64 bytes, then follow `[base+off]` to a sub-object
|
||||||
|
/// (e.g. a stream/file object held in a work item), dump ITS first 64
|
||||||
|
/// bytes, then follow `[[base+off]+0]` to the sub-object's vtable and
|
||||||
|
/// dump the first 48 u32 slots. Designed to capture the live work-item
|
||||||
|
/// + stream object + vtable at `sub_824510E0` entry (r4 = work item,
|
||||||
|
/// stream at +36, vtable[28] = the "is-read-done?" predicate) BEFORE
|
||||||
|
/// the pool recycles the slot. Read-only; lockstep digest unaffected.
|
||||||
|
/// Settable via `XENIA_AUDIT_DEREF=<reg>:<off>` (e.g. `4:36`).
|
||||||
|
pub audit_deref: Option<(u8, 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
|
||||||
@@ -418,6 +429,7 @@ impl KernelState {
|
|||||||
audit_pc_probe_pcs: std::collections::HashSet::new(),
|
audit_pc_probe_pcs: std::collections::HashSet::new(),
|
||||||
audit_mem_read_addr: None,
|
audit_mem_read_addr: None,
|
||||||
audit_r3_dump_bytes: None,
|
audit_r3_dump_bytes: None,
|
||||||
|
audit_deref: 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(),
|
||||||
@@ -1124,6 +1136,38 @@ impl KernelState {
|
|||||||
}
|
}
|
||||||
println!("{}", out);
|
println!("{}", out);
|
||||||
}
|
}
|
||||||
|
// iterate-2E — pointer-chase: dump base object (gpr[reg]), the
|
||||||
|
// sub-object it holds at [base+off], and that sub-object's vtable
|
||||||
|
// slots. Captures the live work-item + stream + vtable[28] at
|
||||||
|
// sub_824510E0 before the pool recycles the slot. Read-only.
|
||||||
|
if let Some((reg, deref_off)) = self.audit_deref {
|
||||||
|
use std::fmt::Write as _;
|
||||||
|
let base = ctx.gpr[reg as usize] as u32;
|
||||||
|
let dump64 = |label: &str, p: u32| {
|
||||||
|
let mut s = String::with_capacity(256);
|
||||||
|
let _ = write!(&mut s, "AUDIT-DEREF {} ptr={:#010x}", label, p);
|
||||||
|
let mut o: u32 = 0;
|
||||||
|
while o < 64 {
|
||||||
|
let _ = write!(&mut s, " +0x{:02x}={:#010x}", o, mem.read_u32(p.wrapping_add(o)));
|
||||||
|
o += 4;
|
||||||
|
}
|
||||||
|
println!("{}", s);
|
||||||
|
};
|
||||||
|
println!("AUDIT-DEREF-HEAD pc={:#010x} tid={} cycle={} reg=r{} off=0x{:x}", pc, tid, cycle, reg, deref_off);
|
||||||
|
dump64("item", base);
|
||||||
|
let sub = mem.read_u32(base.wrapping_add(deref_off));
|
||||||
|
dump64("sub", sub);
|
||||||
|
let vt = mem.read_u32(sub); // [sub+0] = vtable
|
||||||
|
// Dump 48 vtable slots so slot 28 (+0x70) and slot 36 (+0x90) show.
|
||||||
|
let mut s = String::with_capacity(512);
|
||||||
|
let _ = write!(&mut s, "AUDIT-DEREF vtable={:#010x}", vt);
|
||||||
|
let mut slot: u32 = 0;
|
||||||
|
while slot < 48 {
|
||||||
|
let _ = write!(&mut s, " [{}]={:#010x}", slot, mem.read_u32(vt.wrapping_add(slot * 4)));
|
||||||
|
slot += 1;
|
||||||
|
}
|
||||||
|
println!("{}", s);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// M12 — diagnostic. If the live PC for HW slot `hw_id` is in
|
/// M12 — diagnostic. If the live PC for HW slot `hw_id` is in
|
||||||
|
|||||||
Reference in New Issue
Block a user