intro-video RE: deep demux-core instrumentation + handoff hygiene
Investigation probes for the ADV.wmv intro-video deadlock (feeder's demux source-read 0x825211a0 returns no-data 0x80500000 where canary returns 0). - xenia-kernel/state.rs: extend AUDIT-DEREF (fire_audit_pc_probe_if_match) to deep-dump the demux-state object (to +0xfc), its buffer descriptor [sub+4], the byte-source sub-objects ([sub+0]/[+0x30]/[+0x94] and [src0+0x2c]), and to WALK the windowed-buffer cached-block linked list (AUDIT-BLK: per-block 64-bit start/size/dataptr) exactly as mapper 0x82522118 does. Read-only, env-gated (XENIA_AUDIT_DEREF); lockstep digest unaffected. - xenia-app/main.rs: XENIA_DUMP_SLOTS observe-only scheduler runqueue dump. - xenia-app/tests/sylpheed_oracles.rs: resolve ISO via SYLPHEED_ISO, then the repo-root sylpheed.iso symlink, then default; fix path typo. - .gitignore: exclude .claude/ (71k files / 66GB agent worktrees) and the local investigation artifacts (audit-runs/, exit-thread-state.json, zq_*.py). Findings (full chain in handoff notes): all ASF header metadata parses correctly (packet size 16415, count 4728, 2 streams); cursors correct (cur=5868, limit=77.6M); the windowed buffer works and slides (512B blocks); no-data originates deep in the byte-read/parse chain, not from an empty buffer. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
8
.gitignore
vendored
8
.gitignore
vendored
@@ -3,9 +3,17 @@ target/
|
||||
*.xiso
|
||||
*.db
|
||||
|
||||
# Claude Code project data: transcripts, subagent logs, and (huge) agent git
|
||||
# worktrees. ~71k files / 66 GB — must never be scanned by git or the editor.
|
||||
.claude/
|
||||
|
||||
# Audit reports / pre-pass findings (local artifacts, not source)
|
||||
audit-out/
|
||||
audit-*.md
|
||||
audit-runs/
|
||||
exit-thread-state.json
|
||||
zq_dis.py
|
||||
zq_fn.py
|
||||
|
||||
# Run logs from stress harnesses and ad-hoc captures
|
||||
*.stdout
|
||||
|
||||
@@ -4276,6 +4276,34 @@ fn dump_thread_diagnostic(
|
||||
}
|
||||
use xenia_kernel::objects::KernelObject;
|
||||
|
||||
// STEP-10 diagnostic (observe-only, env-gated `XENIA_DUMP_SLOTS=1`).
|
||||
// Prints each scheduler slot's full runqueue with the fields needed to
|
||||
// distinguish "Blocked(Suspended) forever" from "Ready but never picked":
|
||||
// tid, handle, state, suspend_count, idx, and the slot's running_idx.
|
||||
// Read-only — no guest state mutation, no scheduler-behavior change — so
|
||||
// the lockstep digest is unaffected. Not gated on `--quiet` so the
|
||||
// documented headless invocation still emits it.
|
||||
if std::env::var("XENIA_DUMP_SLOTS").as_deref() == Ok("1") {
|
||||
println!("\n=== SLOT DUMP (XENIA_DUMP_SLOTS) ===");
|
||||
for (hw_id, slot) in kernel.scheduler.slots.iter().enumerate() {
|
||||
println!(" hw={} running_idx={:?} depth={}", hw_id, slot.running_idx, slot.runqueue.len());
|
||||
for (idx, t) in slot.runqueue.iter().enumerate() {
|
||||
println!(
|
||||
" idx={} tid={} handle={:?} state={:?} suspend_count={} pri={} mask={:#04x} pc={:#010x}",
|
||||
idx,
|
||||
t.tid,
|
||||
t.thread_handle.map(|h| format!("{:#06x}", h)),
|
||||
t.state,
|
||||
t.suspend_count,
|
||||
t.priority,
|
||||
t.affinity_mask,
|
||||
t.ctx.pc,
|
||||
);
|
||||
}
|
||||
}
|
||||
println!("=== END SLOT DUMP ===\n");
|
||||
}
|
||||
|
||||
// Toolkit-audit fix (2026-06-21): only the ALWAYS-ON thread/waiter table
|
||||
// is suppressed by `--quiet`. The explicitly-armed diagnostics below
|
||||
// (`--trace-handles`, `--trace-handles-focus`, `--dump-addr`) are
|
||||
|
||||
@@ -24,10 +24,25 @@
|
||||
|
||||
use std::process::Command;
|
||||
|
||||
const ISO_DEFAULT: &str = "/home/fabi/RE Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja).iso";
|
||||
const ISO_DEFAULT: &str = "/home/fabi/RE - Project Sylpheed/Project Sylpheed - Arc of Deception (USA, Europe) (En,Ja).iso";
|
||||
|
||||
/// Resolve the Sylpheed ISO, in priority order:
|
||||
/// 1. `SYLPHEED_ISO` env var (explicit override),
|
||||
/// 2. the repo-root `sylpheed.iso` symlink (the documented per-machine
|
||||
/// setup — see HANDOFF §0b), so a standard checkout runs without env vars,
|
||||
/// 3. a last-resort absolute default.
|
||||
/// Returns the first existing path, else the default string so the caller's
|
||||
/// existence check still produces the "SKIPPING" message.
|
||||
fn iso_path() -> String {
|
||||
std::env::var("SYLPHEED_ISO").unwrap_or_else(|_| ISO_DEFAULT.to_string())
|
||||
if let Ok(p) = std::env::var("SYLPHEED_ISO") {
|
||||
return p;
|
||||
}
|
||||
// Repo root is two levels up from this crate's manifest dir.
|
||||
let symlink = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../sylpheed.iso");
|
||||
if symlink.exists() {
|
||||
return symlink.to_string_lossy().into_owned();
|
||||
}
|
||||
ISO_DEFAULT.to_string()
|
||||
}
|
||||
|
||||
fn run_oracle(label: &str, max_instr: u64, golden_rel: &str) {
|
||||
|
||||
@@ -1307,20 +1307,68 @@ impl KernelState {
|
||||
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 dumpn = |label: &str, p: u32, len: u32| {
|
||||
let mut s = String::with_capacity(256 + (len as usize) * 4);
|
||||
let _ = write!(&mut s, "AUDIT-DEREF {} ptr={:#010x}", label, p);
|
||||
let mut o: u32 = 0;
|
||||
while o < 64 {
|
||||
while o < len {
|
||||
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);
|
||||
dumpn("item", base, 64);
|
||||
let sub = mem.read_u32(base.wrapping_add(deref_off));
|
||||
dump64("sub", sub);
|
||||
// STEP 42 — dump the demux-state object deep (to +0xfc): cursors
|
||||
// [+0x10/0x18/0x20], state [+0x50], source handle [+0x94], count
|
||||
// [+0x98], gate flags [+0xb8/bc/cc/d4]. Then follow the buffer
|
||||
// descriptor [sub+4] (bounds at +0x10/+0x14) which defines the
|
||||
// window of file bytes available to the demux at the cursor.
|
||||
dumpn("sub", sub, 0x100);
|
||||
let bufdesc = mem.read_u32(sub.wrapping_add(4));
|
||||
dumpn("bufdesc", bufdesc, 0x40);
|
||||
// STEP 43 — follow the byte-source sub-objects that actually hold
|
||||
// the windowed file bytes + read position: [sub+0] (byte-source /
|
||||
// embedded vtable obj 0x40553640), [sub+0x30] (0x40688560),
|
||||
// [sub+0x94] (source handle 0x41073e40). One holds the data ptr +
|
||||
// cursor; an empty/short buffer here is the no-data root.
|
||||
let src0 = mem.read_u32(sub);
|
||||
dumpn("src0", src0, 0x80);
|
||||
dumpn("src30", mem.read_u32(sub.wrapping_add(0x30)), 0x80);
|
||||
dumpn("src94", mem.read_u32(sub.wrapping_add(0x94)), 0x80);
|
||||
// STEP 44 — the byte-source's buffer/cursor holder [src0+0x2c]
|
||||
// (=0x40930f60): vtable[4]=0x82524080 reads its file-offset cursor
|
||||
// [+0x28], cached-range [+0x80]/[+0x88], flags [+0x40], and maps
|
||||
// offsets to memory blocks via 0x82522118 (the windowed/chunk
|
||||
// buffer). An empty cached range / null block ptr here = the data
|
||||
// byte-source never fed → 0x80500000. Follow its block ptr [+0x18].
|
||||
let cur = mem.read_u32(src0.wrapping_add(0x2c));
|
||||
dumpn("src0_2c", cur, 0x100);
|
||||
// STEP 45 — walk the cached-block linked list exactly as the
|
||||
// mapper 0x82522118 does: container=[cur+0x10]; node=[container+8];
|
||||
// each node: block=[node+0] (block[+8]=start off64, block[+4]=size),
|
||||
// next=[node+8]. Print each block's 64-bit start + size so we can
|
||||
// see whether ANY cached block covers file offset 5868 (the first
|
||||
// ASF data packet). Empty list / no covering block = the root.
|
||||
let container = mem.read_u32(cur.wrapping_add(0x10));
|
||||
dumpn("blkcont", container, 0x20);
|
||||
let mut node = mem.read_u32(container.wrapping_add(8));
|
||||
let mut k = 0;
|
||||
while node != 0 && k < 8 {
|
||||
let block = mem.read_u32(node);
|
||||
let start_hi = mem.read_u32(block.wrapping_add(8));
|
||||
let start_lo = mem.read_u32(block.wrapping_add(0xc));
|
||||
let size = mem.read_u32(block.wrapping_add(4));
|
||||
let dptr = mem.read_u32(block);
|
||||
println!(
|
||||
"AUDIT-BLK k={} node={:#010x} block={:#010x} start={:#010x}_{:#010x} size={:#010x} dataptr={:#010x}",
|
||||
k, node, block, start_hi, start_lo, size, dptr,
|
||||
);
|
||||
node = mem.read_u32(node.wrapping_add(8));
|
||||
k += 1;
|
||||
}
|
||||
println!("AUDIT-BLK-END count={}", k);
|
||||
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);
|
||||
|
||||
Reference in New Issue
Block a user