Merge audit-helper/p0-dump-section: --dump-section=BASE:LEN:PATH end-of-run snapshot

This commit is contained in:
MechaCat02
2026-05-08 15:05:07 +02:00
2 changed files with 70 additions and 0 deletions

View File

@@ -221,6 +221,9 @@ enum Commands {
/// `XENIA_MEM_WATCH`. Example: `--mem-watch=0x828F40B4`. /// `XENIA_MEM_WATCH`. Example: `--mem-watch=0x828F40B4`.
#[arg(long)] #[arg(long)]
mem_watch: Option<String>, mem_watch: Option<String>,
/// `--dump-section=BASE:LEN:PATH` end-of-run guest memory snapshot.
#[arg(long)]
dump_section: Option<String>,
}, },
/// Browse XISO disc image contents /// Browse XISO disc image contents
Browse { Browse {
@@ -380,6 +383,7 @@ fn main() -> Result<()> {
dump_addr, dump_addr,
branch_probe, branch_probe,
mem_watch, mem_watch,
dump_section,
} => cmd_exec( } => cmd_exec(
&path, &path,
max_instructions, max_instructions,
@@ -402,6 +406,7 @@ fn main() -> Result<()> {
dump_addr.as_deref(), dump_addr.as_deref(),
branch_probe.as_deref(), branch_probe.as_deref(),
mem_watch.as_deref(), mem_watch.as_deref(),
dump_section.as_deref(),
), ),
Commands::Browse { path } => cmd_browse(&path), Commands::Browse { path } => cmd_browse(&path),
Commands::Info { path } => cmd_info(&path), Commands::Info { path } => cmd_info(&path),
@@ -607,6 +612,7 @@ fn cmd_exec(
dump_addr: Option<&str>, dump_addr: Option<&str>,
branch_probe: Option<&str>, branch_probe: Option<&str>,
mem_watch: Option<&str>, mem_watch: Option<&str>,
dump_section: Option<&str>,
) -> Result<()> { ) -> Result<()> {
cmd_exec_inner( cmd_exec_inner(
path, path,
@@ -630,6 +636,7 @@ fn cmd_exec(
dump_addr, dump_addr,
branch_probe, branch_probe,
mem_watch, mem_watch,
dump_section,
None, None,
None, None,
false, false,
@@ -672,6 +679,7 @@ fn cmd_check(
None, // dump_addr — same None, // dump_addr — same
None, // branch_probe — diagnostic, never wanted on goldens None, // branch_probe — diagnostic, never wanted on goldens
None, // mem_watch — same None, // mem_watch — same
None, // dump_section — same
out, out,
expect, expect,
stable_digest, stable_digest,
@@ -700,6 +708,7 @@ fn cmd_exec_inner(
dump_addr: Option<&str>, dump_addr: Option<&str>,
branch_probe: Option<&str>, branch_probe: Option<&str>,
mem_watch: Option<&str>, mem_watch: Option<&str>,
dump_section: Option<&str>,
digest_out: Option<&str>, digest_out: Option<&str>,
digest_expect: Option<&str>, digest_expect: Option<&str>,
stable_digest: bool, stable_digest: bool,
@@ -1107,6 +1116,37 @@ fn cmd_exec_inner(
} }
} }
let dump_section_combined: Option<String> = match (dump_section, std::env::var("XENIA_DUMP_SECTION").ok()) {
(Some(s), _) => Some(s.to_string()),
(None, Some(s)) if !s.is_empty() => Some(s),
_ => None,
};
if let Some(spec) = dump_section_combined {
let parts: Vec<&str> = spec.splitn(3, ':').collect();
if parts.len() != 3 {
return Err(anyhow::anyhow!(
"--dump-section expects BASE:LEN:PATH, got {spec:?}"
));
}
let parse_u32 = |t: &str| -> Result<u32> {
if let Some(h) = t.strip_prefix("0x").or_else(|| t.strip_prefix("0X")) {
u32::from_str_radix(h, 16).map_err(|e| anyhow::anyhow!("bad hex {t:?}: {e}"))
} else {
t.parse::<u32>().map_err(|e| anyhow::anyhow!("bad dec {t:?}: {e}"))
}
};
let base = parse_u32(parts[0].trim())?;
let len = parse_u32(parts[1].trim())?;
let path = std::path::PathBuf::from(parts[2].trim());
if !quiet {
tracing::info!(
"dump-section armed: base={:#010x} len={:#x} path={}",
base, len, path.display()
);
}
kernel.dump_section = Some((base, len, path));
}
// Install the GPU register aperture MMIO region on the guest memory so // Install the GPU register aperture MMIO region on the guest memory so
// any `0x7FC8xxxx` access routes to our atomic mailbox. Matches canary's // any `0x7FC8xxxx` access routes to our atomic mailbox. Matches canary's
// `graphics_system.cc:141-144`. The callbacks capture Arc clones of the // `graphics_system.cc:141-144`. The callbacks capture Arc clones of the
@@ -3219,6 +3259,33 @@ fn dump_thread_diagnostic(
mem: &xenia_memory::GuestMemory, mem: &xenia_memory::GuestMemory,
quiet: bool, quiet: bool,
) { ) {
if let Some((base, len, ref path)) = kernel.dump_section {
let mut buf = vec![0u8; len as usize];
const CHUNK: u32 = 4096;
let mut off: u32 = 0;
let mut committed_pages = 0u32;
while off < len {
let take = CHUNK.min(len - off);
let addr = base.wrapping_add(off);
if mem.is_mapped(addr) {
let s = off as usize;
let e = s + take as usize;
mem.read_bulk(addr, &mut buf[s..e]);
committed_pages += 1;
}
off += take;
}
match std::fs::write(path, &buf) {
Ok(()) => eprintln!(
"dump-section: wrote {} bytes from {:#010x} ({} committed pages) to {}",
buf.len(), base, committed_pages, path.display(),
),
Err(e) => eprintln!(
"dump-section: failed to write {}: {e}",
path.display(),
),
}
}
if quiet { if quiet {
return; return;
} }

View File

@@ -237,6 +237,8 @@ pub struct KernelState {
/// dump is performed by `dump_thread_diagnostic`, never during /// dump is performed by `dump_thread_diagnostic`, never during
/// the hot interpreter loop, so lockstep determinism is unaffected. /// the hot interpreter loop, so lockstep determinism is unaffected.
pub dump_addrs: Vec<u32>, pub dump_addrs: Vec<u32>,
/// `--dump-section=BASE:LEN:PATH` end-of-run snapshot, page-gated by `is_mapped`.
pub dump_section: Option<(u32, u32, std::path::PathBuf)>,
} }
impl KernelState { impl KernelState {
@@ -296,6 +298,7 @@ impl KernelState {
pc_probe_consumers: HashMap::new(), pc_probe_consumers: HashMap::new(),
branch_probe_pcs: std::collections::HashSet::new(), branch_probe_pcs: std::collections::HashSet::new(),
dump_addrs: Vec::new(), dump_addrs: Vec::new(),
dump_section: None,
}; };
crate::exports::register_exports(&mut state); crate::exports::register_exports(&mut state);
crate::xam::register_exports(&mut state); crate::xam::register_exports(&mut state);