[Audit] --audit-r3-dump-bytes: dump N bytes at r3 when probe fires

AUDIT-059 round 15 — diagnostic. When `--audit-r3-dump-bytes=N` is set,
every `--audit-pc-probe-hex` fire emits a paired `AUDIT-R3-DUMP` line
with N bytes of guest memory from r3 as u32 lanes (4-byte aligned, cap
256B). Sized for the 80-byte stack-local struct at sub_82452DC0's
`r31+96` (probe sub_8245B000 entry where r3 IS the struct ptr).

Settable via `XENIA_AUDIT_R3_DUMP_BYTES` env. Read-only; lockstep digest
unaffected (empty-set fast path in fire_audit_pc_probe_if_match).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-06-07 19:39:22 +02:00
parent bcd018659b
commit 9340ff4592
2 changed files with 70 additions and 0 deletions

View File

@@ -271,6 +271,15 @@ enum Commands {
/// `--audit-mem-read-hex=828E1F08`.
#[arg(long)]
audit_mem_read_hex: Option<String>,
/// AUDIT-052 — number of bytes (4-byte aligned, max 256) to
/// dump from `r3` on every `--audit-pc-probe-hex` fire. Emits a
/// paired `AUDIT-R3-DUMP` line with the u32 lanes. Designed for
/// the 80-byte stack-local struct at `sub_82452DC0` (`r31+96`)
/// when probing `sub_8245B000` entry — where `r3` IS the struct
/// pointer. Read-only; lockstep digest unaffected. Settable via
/// `XENIA_AUDIT_R3_DUMP_BYTES`. Example: `--audit-r3-dump-bytes=80`.
#[arg(long)]
audit_r3_dump_bytes: Option<u32>,
},
/// Browse XISO disc image contents
Browse {
@@ -436,6 +445,7 @@ fn main() -> Result<()> {
lr_trace_out,
audit_pc_probe_hex,
audit_mem_read_hex,
audit_r3_dump_bytes,
} => cmd_exec(
&path,
max_instructions,
@@ -464,6 +474,7 @@ fn main() -> Result<()> {
lr_trace_out.as_deref(),
audit_pc_probe_hex.as_deref(),
audit_mem_read_hex.as_deref(),
audit_r3_dump_bytes,
),
Commands::Browse { path } => cmd_browse(&path),
Commands::Info { path } => cmd_info(&path),
@@ -697,6 +708,7 @@ fn cmd_exec(
lr_trace_out: Option<&str>,
audit_pc_probe_hex: Option<&str>,
audit_mem_read_hex: Option<&str>,
audit_r3_dump_bytes: Option<u32>,
) -> Result<()> {
cmd_exec_inner(
path,
@@ -726,6 +738,7 @@ fn cmd_exec(
lr_trace_out,
audit_pc_probe_hex,
audit_mem_read_hex,
audit_r3_dump_bytes,
None,
None,
false,
@@ -774,6 +787,7 @@ fn cmd_check(
None, // lr_trace_out — same
None, // audit_pc_probe_hex — diagnostic, never wanted on goldens
None, // audit_mem_read_hex — same
None, // audit_r3_dump_bytes — same
out,
expect,
stable_digest,
@@ -808,6 +822,7 @@ fn cmd_exec_inner(
lr_trace_out: Option<&str>,
audit_pc_probe_hex: Option<&str>,
audit_mem_read_hex: Option<&str>,
audit_r3_dump_bytes: Option<u32>,
digest_out: Option<&str>,
digest_expect: Option<&str>,
stable_digest: bool,
@@ -1263,6 +1278,29 @@ fn cmd_exec_inner(
}
}
// AUDIT-052 — `--audit-r3-dump-bytes=80`. When set, every
// `--audit-pc-probe-hex` fire emits a paired `AUDIT-R3-DUMP` line
// with N bytes from `r3` (4-byte aligned, capped at 256). Sized for
// the 80-byte stack-local struct at `sub_82452DC0`'s `r31+96` —
// probe `sub_8245B000` entry where `r3 == parent's r31+96`.
let audit_r3_dump_combined: Option<u32> = match (
audit_r3_dump_bytes, std::env::var("XENIA_AUDIT_R3_DUMP_BYTES").ok(),
) {
(Some(n), _) => Some(n),
(None, Some(s)) if !s.is_empty() => Some(
s.parse::<u32>().map_err(|e| anyhow::anyhow!("--audit-r3-dump-bytes {s:?}: {e}"))?,
),
_ => None,
};
if let Some(n) = audit_r3_dump_combined {
if n > 0 {
kernel.audit_r3_dump_bytes = Some(n);
if !quiet {
tracing::info!("audit-r3-dump armed: {} bytes", n);
}
}
}
// 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