Compare commits
3 Commits
iterate-2B
...
audit-2BF/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9340ff4592 | ||
|
|
bcd018659b | ||
|
|
09e59e09b7 |
@@ -242,6 +242,44 @@ enum Commands {
|
|||||||
/// line). Stdout when omitted.
|
/// line). Stdout when omitted.
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
lr_trace_out: Option<String>,
|
lr_trace_out: Option<String>,
|
||||||
|
/// AUDIT-2BF — comma-separated list of guest PCs (hex, no `0x`
|
||||||
|
/// prefix required) to capture as one-line `AUDIT-PC-PROBE`
|
||||||
|
/// records on every fire. Designed for the silph init chain
|
||||||
|
/// virtual-dispatch site at `sub_82172BA0+0x1E8` (PC
|
||||||
|
/// `0x82172D88`, a `bctrl` after a 3-deep vtable-slot-6 load).
|
||||||
|
/// Each record carries (pc, tid, hw, cycle, lr, r3, r11) plus
|
||||||
|
/// four guest-memory dereferences off r3: `[r3+0]` (vtable),
|
||||||
|
/// `[[r3+0]+24]` (slot 6 method = bctrl target), `[r3+0x0C]`
|
||||||
|
/// (auxiliary handle), `[r3+0x30]` (embedded sub-object vtable).
|
||||||
|
/// Compares directly against canary's round-9 capture:
|
||||||
|
/// r3=0xBCCC52C0, [r3+0]=0x820A3644, slot6=sub_821B55D8,
|
||||||
|
/// [r3+0xC]=0xF80000D8, [r3+0x30]=0x820A1870. Read-only;
|
||||||
|
/// lockstep digest unaffected. Settable via
|
||||||
|
/// `XENIA_AUDIT_PC_PROBE`. Example:
|
||||||
|
/// `--audit-pc-probe-hex=82172D88,82172D80`.
|
||||||
|
#[arg(long)]
|
||||||
|
audit_pc_probe_hex: Option<String>,
|
||||||
|
/// AUDIT-2BF round 14 — guest VA (hex, optional `0x` prefix) to
|
||||||
|
/// dereference 3 deep on every `--audit-pc-probe-hex` fire.
|
||||||
|
/// Emits a paired `AUDIT-MEM-READ` line with the singleton value,
|
||||||
|
/// vtable, vtable[0] (= first virtual method, the bctrl target
|
||||||
|
/// at `0x822F1B4C`), and vtable[24] (= slot 6 = canary's silph
|
||||||
|
/// chain target `sub_821B55D8`). Compare ours vs canary to
|
||||||
|
/// determine whether the bctrl dispatches to the same function
|
||||||
|
/// or a different one. Read-only; lockstep digest unaffected.
|
||||||
|
/// Settable via `XENIA_AUDIT_MEM_READ`. Example:
|
||||||
|
/// `--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 XISO disc image contents
|
||||||
Browse {
|
Browse {
|
||||||
@@ -405,6 +443,9 @@ fn main() -> Result<()> {
|
|||||||
probe_db,
|
probe_db,
|
||||||
lr_trace,
|
lr_trace,
|
||||||
lr_trace_out,
|
lr_trace_out,
|
||||||
|
audit_pc_probe_hex,
|
||||||
|
audit_mem_read_hex,
|
||||||
|
audit_r3_dump_bytes,
|
||||||
} => cmd_exec(
|
} => cmd_exec(
|
||||||
&path,
|
&path,
|
||||||
max_instructions,
|
max_instructions,
|
||||||
@@ -431,6 +472,9 @@ fn main() -> Result<()> {
|
|||||||
probe_db.as_deref(),
|
probe_db.as_deref(),
|
||||||
lr_trace.as_deref(),
|
lr_trace.as_deref(),
|
||||||
lr_trace_out.as_deref(),
|
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::Browse { path } => cmd_browse(&path),
|
||||||
Commands::Info { path } => cmd_info(&path),
|
Commands::Info { path } => cmd_info(&path),
|
||||||
@@ -662,6 +706,9 @@ fn cmd_exec(
|
|||||||
probe_db: Option<&str>,
|
probe_db: Option<&str>,
|
||||||
lr_trace: Option<&str>,
|
lr_trace: Option<&str>,
|
||||||
lr_trace_out: Option<&str>,
|
lr_trace_out: Option<&str>,
|
||||||
|
audit_pc_probe_hex: Option<&str>,
|
||||||
|
audit_mem_read_hex: Option<&str>,
|
||||||
|
audit_r3_dump_bytes: Option<u32>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
cmd_exec_inner(
|
cmd_exec_inner(
|
||||||
path,
|
path,
|
||||||
@@ -689,6 +736,9 @@ fn cmd_exec(
|
|||||||
probe_db,
|
probe_db,
|
||||||
lr_trace,
|
lr_trace,
|
||||||
lr_trace_out,
|
lr_trace_out,
|
||||||
|
audit_pc_probe_hex,
|
||||||
|
audit_mem_read_hex,
|
||||||
|
audit_r3_dump_bytes,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
false,
|
false,
|
||||||
@@ -735,6 +785,9 @@ fn cmd_check(
|
|||||||
None, // probe_db — same
|
None, // probe_db — same
|
||||||
None, // lr_trace — same
|
None, // lr_trace — same
|
||||||
None, // lr_trace_out — same
|
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,
|
out,
|
||||||
expect,
|
expect,
|
||||||
stable_digest,
|
stable_digest,
|
||||||
@@ -767,6 +820,9 @@ fn cmd_exec_inner(
|
|||||||
probe_db: Option<&str>,
|
probe_db: Option<&str>,
|
||||||
lr_trace: Option<&str>,
|
lr_trace: Option<&str>,
|
||||||
lr_trace_out: Option<&str>,
|
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_out: Option<&str>,
|
||||||
digest_expect: Option<&str>,
|
digest_expect: Option<&str>,
|
||||||
stable_digest: bool,
|
stable_digest: bool,
|
||||||
@@ -1167,6 +1223,84 @@ fn cmd_exec_inner(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AUDIT-2BF — `--audit-pc-probe-hex=82172D88,...`. Bare-hex tokens
|
||||||
|
// (with or without `0x` prefix). Parses every comma-separated entry
|
||||||
|
// as a u32 PC and inserts into `kernel.audit_pc_probe_pcs`. Empty
|
||||||
|
// set is the hot-path no-op (single is_empty() check).
|
||||||
|
let audit_pc_probe_combined: Option<String> = match (
|
||||||
|
audit_pc_probe_hex, std::env::var("XENIA_AUDIT_PC_PROBE").ok(),
|
||||||
|
) {
|
||||||
|
(Some(s), _) => Some(s.to_string()),
|
||||||
|
(None, Some(s)) if !s.is_empty() => Some(s),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if let Some(list) = audit_pc_probe_combined {
|
||||||
|
for token in list.split(',').map(str::trim).filter(|s| !s.is_empty()) {
|
||||||
|
let hex = token.strip_prefix("0x").or_else(|| token.strip_prefix("0X")).unwrap_or(token);
|
||||||
|
let pc = u32::from_str_radix(hex, 16)
|
||||||
|
.map_err(|e| anyhow::anyhow!("--audit-pc-probe-hex {token:?}: {e}"))?;
|
||||||
|
kernel.audit_pc_probe_pcs.insert(pc);
|
||||||
|
}
|
||||||
|
if !quiet && !kernel.audit_pc_probe_pcs.is_empty() {
|
||||||
|
let mut pcs: Vec<u32> = kernel.audit_pc_probe_pcs.iter().copied().collect();
|
||||||
|
pcs.sort_unstable();
|
||||||
|
let strs: Vec<String> = pcs.iter().map(|p| format!("{p:#010x}")).collect();
|
||||||
|
tracing::info!(
|
||||||
|
"audit-pc-probe armed: {} ({})",
|
||||||
|
kernel.audit_pc_probe_pcs.len(),
|
||||||
|
strs.join(", "),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AUDIT-2BF round 14 — `--audit-mem-read-hex=828E1F08`. Single
|
||||||
|
// hex VA (optional `0x` prefix). Stored on `kernel.audit_mem_read_addr`.
|
||||||
|
// Paired with `audit_pc_probe_pcs`: on every probe fire, the kernel
|
||||||
|
// emits a second `AUDIT-MEM-READ` line dereferencing 3 deep so we can
|
||||||
|
// resolve vtable[0] / vtable[24] at the singleton.
|
||||||
|
let audit_mem_read_combined: Option<String> = match (
|
||||||
|
audit_mem_read_hex, std::env::var("XENIA_AUDIT_MEM_READ").ok(),
|
||||||
|
) {
|
||||||
|
(Some(s), _) => Some(s.to_string()),
|
||||||
|
(None, Some(s)) if !s.is_empty() => Some(s),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if let Some(tok) = audit_mem_read_combined {
|
||||||
|
let tok = tok.trim();
|
||||||
|
if !tok.is_empty() {
|
||||||
|
let hex = tok.strip_prefix("0x").or_else(|| tok.strip_prefix("0X")).unwrap_or(tok);
|
||||||
|
let addr = u32::from_str_radix(hex, 16)
|
||||||
|
.map_err(|e| anyhow::anyhow!("--audit-mem-read-hex {tok:?}: {e}"))?;
|
||||||
|
kernel.audit_mem_read_addr = Some(addr);
|
||||||
|
if !quiet {
|
||||||
|
tracing::info!("audit-mem-read armed: {:#010x}", addr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
// 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
|
||||||
@@ -2242,6 +2376,7 @@ fn worker_prologue(
|
|||||||
// the helper, no overhead on the hot path.
|
// the helper, no overhead on the hot path.
|
||||||
kernel.fire_ctor_probe_if_match(hw_id, mem);
|
kernel.fire_ctor_probe_if_match(hw_id, mem);
|
||||||
kernel.fire_branch_probe_if_match(hw_id);
|
kernel.fire_branch_probe_if_match(hw_id);
|
||||||
|
kernel.fire_audit_pc_probe_if_match(hw_id, mem);
|
||||||
kernel.fire_lr_trace_if_match(hw_id);
|
kernel.fire_lr_trace_if_match(hw_id);
|
||||||
|
|
||||||
if mem.has_mem_watch() {
|
if mem.has_mem_watch() {
|
||||||
|
|||||||
@@ -244,6 +244,41 @@ pub struct KernelState {
|
|||||||
/// Distinct from `ctor_probe_pcs` because that helper emits 8
|
/// Distinct from `ctor_probe_pcs` because that helper emits 8
|
||||||
/// frames of back-chain per hit — too noisy for branch tracing.
|
/// frames of back-chain per hit — too noisy for branch tracing.
|
||||||
pub branch_probe_pcs: std::collections::HashSet<u32>,
|
pub branch_probe_pcs: std::collections::HashSet<u32>,
|
||||||
|
/// AUDIT-2BF — diagnostic. PCs at which to emit a structured one-line
|
||||||
|
/// `AUDIT-PC-PROBE` record on every fire, designed for the silph init
|
||||||
|
/// chain virtual-dispatch site at `sub_82172BA0+0x1E8` (PC
|
||||||
|
/// `0x82172D88`, a `bctrl` after a 3-deep load of vtable slot 6). The
|
||||||
|
/// emitted line carries (pc, tid, hw, cycle, lr, r3, r11) plus four
|
||||||
|
/// guest-memory dereferences off `r3`: `[r3+0]` (vtable), `[[r3+0]+24]`
|
||||||
|
/// (slot 6 method pointer = the bctrl target), `[r3+0x0C]` (audit-059
|
||||||
|
/// round-9 canary-known auxiliary handle `0xF80000D8`), and `[r3+0x30]`
|
||||||
|
/// (canary-known embedded sub-object vtable `0x820A1870`). Distinct
|
||||||
|
/// from `branch_probe_pcs` because that helper only logs registers (no
|
||||||
|
/// memory) and from `lr_trace_pcs` because that emits JSON intended
|
||||||
|
/// for canary diffing, not the four hard-coded indirect dereferences
|
||||||
|
/// needed here. Read-only — no guest state mutation. Lockstep
|
||||||
|
/// digest unaffected. Settable via `--audit-pc-probe-hex` /
|
||||||
|
/// `XENIA_AUDIT_PC_PROBE`.
|
||||||
|
pub audit_pc_probe_pcs: std::collections::HashSet<u32>,
|
||||||
|
/// AUDIT-2BF round 14 — diagnostic. Optional guest VA. When set, each
|
||||||
|
/// `AUDIT-PC-PROBE` fire emits a paired `AUDIT-MEM-READ` line with
|
||||||
|
/// `addr`, `*addr` (singleton value), `**addr` (vtable), `***addr+0`
|
||||||
|
/// (vtable[0] = first virtual method), and `***addr+24` (vtable[6]
|
||||||
|
/// in 4-byte stride = slot 6 = silph chain bctrl target). Three-deep
|
||||||
|
/// dereference to resolve the vtable[0] target at the bctrl site
|
||||||
|
/// `0x822F1B4C` inside `sub_822F1AA8`. Read-only; lockstep digest
|
||||||
|
/// unaffected. Settable via `--audit-mem-read-hex` /
|
||||||
|
/// `XENIA_AUDIT_MEM_READ`.
|
||||||
|
pub audit_mem_read_addr: Option<u32>,
|
||||||
|
/// AUDIT-052 — diagnostic. When set, each `AUDIT-PC-PROBE` fire
|
||||||
|
/// additionally emits an `AUDIT-R3-DUMP` line with N bytes of guest
|
||||||
|
/// memory dumped from `r3` as `u32` lanes (4-byte aligned only).
|
||||||
|
/// Sized for audit-051's 80-byte stack-local struct at `r31+96`
|
||||||
|
/// inside `sub_82452DC0` (probe `sub_8245B000` entry where
|
||||||
|
/// `r3 == parent's r31+96`). Read-only; lockstep digest unaffected.
|
||||||
|
/// Settable via `--audit-r3-dump-bytes` /
|
||||||
|
/// `XENIA_AUDIT_R3_DUMP_BYTES`.
|
||||||
|
pub audit_r3_dump_bytes: Option<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
|
||||||
@@ -327,6 +362,9 @@ impl KernelState {
|
|||||||
ctor_probe_pcs: std::collections::HashSet::new(),
|
ctor_probe_pcs: std::collections::HashSet::new(),
|
||||||
pc_probe_consumers: HashMap::new(),
|
pc_probe_consumers: HashMap::new(),
|
||||||
branch_probe_pcs: std::collections::HashSet::new(),
|
branch_probe_pcs: std::collections::HashSet::new(),
|
||||||
|
audit_pc_probe_pcs: std::collections::HashSet::new(),
|
||||||
|
audit_mem_read_addr: None,
|
||||||
|
audit_r3_dump_bytes: 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(),
|
||||||
@@ -797,6 +835,91 @@ impl KernelState {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// AUDIT-2BF — diagnostic. If the live PC for HW slot `hw_id` is in
|
||||||
|
/// `self.audit_pc_probe_pcs`, emit a single one-line
|
||||||
|
/// `AUDIT-PC-PROBE` record with (pc, tid, hw, cycle, lr, r3, r11)
|
||||||
|
/// plus four guest-memory dereferences off r3: `[r3+0]` (vtable),
|
||||||
|
/// `[[r3+0]+24]` (slot 6 method = bctrl target), `[r3+0x0C]`
|
||||||
|
/// (auxiliary handle field), `[r3+0x30]` (embedded sub-object
|
||||||
|
/// vtable field). Tuned for the silph init chain virtual-dispatch
|
||||||
|
/// site at `sub_82172BA0+0x1E8` (PC `0x82172D88`).
|
||||||
|
///
|
||||||
|
/// Read-only. No guest-state mutation; lockstep digest unaffected.
|
||||||
|
/// Empty set is the common case → single `is_empty()` test on the
|
||||||
|
/// hot path.
|
||||||
|
pub fn fire_audit_pc_probe_if_match(&self, hw_id: u8, mem: &GuestMemory) {
|
||||||
|
if self.audit_pc_probe_pcs.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let ctx = self.scheduler.ctx(hw_id);
|
||||||
|
let pc = ctx.pc;
|
||||||
|
if !self.audit_pc_probe_pcs.contains(&pc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let tid = self.scheduler.tid(hw_id).unwrap_or(0);
|
||||||
|
let r3 = ctx.gpr[3] as u32;
|
||||||
|
let r11 = ctx.gpr[11] as u32;
|
||||||
|
let lr = ctx.lr as u32;
|
||||||
|
let cycle = ctx.cycle_count;
|
||||||
|
// Memory dereferences. Guest pointers may be unmapped/garbage;
|
||||||
|
// `read_u32` returns 0 for unmapped pages (heap.rs:510 returns
|
||||||
|
// a default), so an all-zero block in the output reliably
|
||||||
|
// indicates an invalid `r3`.
|
||||||
|
let vtable = mem.read_u32(r3);
|
||||||
|
let slot6_method = if vtable != 0 {
|
||||||
|
mem.read_u32(vtable.wrapping_add(24))
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
let aux_handle = mem.read_u32(r3.wrapping_add(0x0C));
|
||||||
|
let sub_vt = mem.read_u32(r3.wrapping_add(0x30));
|
||||||
|
println!(
|
||||||
|
"AUDIT-PC-PROBE pc={:#010x} tid={} hw={} cycle={} lr={:#010x} r3={:#010x} r11={:#010x} \
|
||||||
|
[r3+0]={:#010x} [[r3+0]+24]={:#010x} [r3+0x0C]={:#010x} [r3+0x30]={:#010x}",
|
||||||
|
pc, tid, hw_id, cycle, lr, r3, r11,
|
||||||
|
vtable, slot6_method, aux_handle, sub_vt,
|
||||||
|
);
|
||||||
|
// AUDIT-2BF round 14 — paired memory-read. When
|
||||||
|
// `audit_mem_read_addr` is set, dereference 3 deep: singleton
|
||||||
|
// pointer → vtable → vtable[0] / vtable[24]. Defensively
|
||||||
|
// null-checks each level. `read_u32` returns 0 for unmapped
|
||||||
|
// pages so all-zero output is the unmapped/uninitialized
|
||||||
|
// signature.
|
||||||
|
if let Some(addr) = self.audit_mem_read_addr {
|
||||||
|
let val = mem.read_u32(addr);
|
||||||
|
let vt = if val != 0 { mem.read_u32(val) } else { 0 };
|
||||||
|
let m0 = if vt != 0 { mem.read_u32(vt) } else { 0 };
|
||||||
|
let m6 = if vt != 0 { mem.read_u32(vt.wrapping_add(24)) } else { 0 };
|
||||||
|
println!(
|
||||||
|
"AUDIT-MEM-READ addr={:#010x} val={:#010x} vtable={:#010x} \
|
||||||
|
vtable[0]={:#010x} vtable[24]={:#010x} pc={:#010x} tid={} cycle={}",
|
||||||
|
addr, val, vt, m0, m6, pc, tid, cycle,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// AUDIT-052 — dump N bytes of guest memory from r3 as u32 lanes
|
||||||
|
// when `audit_r3_dump_bytes` is set. Sized for the 80-byte
|
||||||
|
// stack-local struct at sub_82452DC0's `r31+96` (probe is
|
||||||
|
// sub_8245B000 entry where r3 IS the struct ptr). Output
|
||||||
|
// format: `AUDIT-R3-DUMP pc=… r3=… +0x00=… +0x04=… …`.
|
||||||
|
if let Some(n) = self.audit_r3_dump_bytes {
|
||||||
|
let n = n.min(256) & !3u32; // cap 256B, 4-byte align
|
||||||
|
let mut out = String::with_capacity(64 + (n as usize) * 16);
|
||||||
|
use std::fmt::Write as _;
|
||||||
|
let _ = write!(
|
||||||
|
&mut out,
|
||||||
|
"AUDIT-R3-DUMP pc={:#010x} tid={} cycle={} r3={:#010x}",
|
||||||
|
pc, tid, cycle, r3,
|
||||||
|
);
|
||||||
|
let mut off: u32 = 0;
|
||||||
|
while off < n {
|
||||||
|
let v = mem.read_u32(r3.wrapping_add(off));
|
||||||
|
let _ = write!(&mut out, " +0x{:02x}={:#010x}", off, v);
|
||||||
|
off = off.wrapping_add(4);
|
||||||
|
}
|
||||||
|
println!("{}", out);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 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
|
||||||
/// `self.lr_trace_pcs`, emit one JSONL record. Format mirrors what
|
/// `self.lr_trace_pcs`, emit one JSONL record. Format mirrors what
|
||||||
/// xenia-canary's `--log_lr_on_pc` patch emits, plus the cycle
|
/// xenia-canary's `--log_lr_on_pc` patch emits, plus the cycle
|
||||||
|
|||||||
Reference in New Issue
Block a user