feat(kernel): KRNBUG-AUDIT-007 — --branch-probe instrumentation; sub_824A9710 exit gate identified
Sister to --pc-probe / --ctor-probe but emits a single compact one-line BRANCH-PROBE record per fire (pc, tid, hw, cycle, r3, lr, cr0/cr6 flags) with no back-chain. Designed for tracing every conditional-branch fire inside a candidate-gate function so the last PC reached before the function epilogue identifies the exit branch. Runtime trace at audit-runs/audit-007/sub_824A9710-trace.log decisively identifies the priv-11 gate: - Exit branch: 0x824a9944 (post bl sub_824ABD88 first call) - Responsible kernel call: NtDeviceIoControlFile, FsCtlCode=0x74004 (registered as stub_success at exports.rs:90) - Mechanical chain: stub returns 0/SUCCESS without writing OUT, game reads [out_buf+8], finds zero, assigns hardcoded 0xC0000034 (STATUS_OBJECT_NAME_NOT_FOUND) at sub_824ABD88:0x824abea8-ac, exits via 0x824a9944's lt branch before priv-11 site at 0x824a99a0. 592→592 tests; lockstep instructions=100000010, swaps=2, draws=0 deterministic across reruns. Read-only diagnostic — no fix this session. Next session: KRNBUG-IO-003 (real NtDeviceIoControlFile per canary NullDevice::IoControl for FsCtlCodes 0x70000 + 0x74004). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -201,6 +201,18 @@ enum Commands {
|
||||
/// runs — typically when `--halt-on-deadlock` triggers.
|
||||
#[arg(long)]
|
||||
dump_addr: Option<String>,
|
||||
/// Diagnostic. Comma-separated list of guest PCs that emit a
|
||||
/// one-line `BRANCH-PROBE` record on each fire (pc, tid, hw,
|
||||
/// cycle, r3, lr, cr0/cr6 flags). Sister to `--pc-probe` but
|
||||
/// without the 8-frame back-chain, suited for tracing every
|
||||
/// conditional branch inside a candidate-gate function so the
|
||||
/// last PC reached before the function epilogue identifies the
|
||||
/// exit branch. Example:
|
||||
/// `--branch-probe=0x824a9710,0x824a9778,0x824a97dc,...`.
|
||||
/// Read-only; lockstep digest unaffected. Settable via
|
||||
/// `XENIA_BRANCH_PROBE`.
|
||||
#[arg(long)]
|
||||
branch_probe: Option<String>,
|
||||
},
|
||||
/// Browse XISO disc image contents
|
||||
Browse {
|
||||
@@ -358,6 +370,7 @@ fn main() -> Result<()> {
|
||||
xaudio_tick,
|
||||
ctor_probe,
|
||||
dump_addr,
|
||||
branch_probe,
|
||||
} => cmd_exec(
|
||||
&path,
|
||||
max_instructions,
|
||||
@@ -378,6 +391,7 @@ fn main() -> Result<()> {
|
||||
xaudio_tick,
|
||||
ctor_probe.as_deref(),
|
||||
dump_addr.as_deref(),
|
||||
branch_probe.as_deref(),
|
||||
),
|
||||
Commands::Browse { path } => cmd_browse(&path),
|
||||
Commands::Info { path } => cmd_info(&path),
|
||||
@@ -581,6 +595,7 @@ fn cmd_exec(
|
||||
xaudio_tick: bool,
|
||||
ctor_probe: Option<&str>,
|
||||
dump_addr: Option<&str>,
|
||||
branch_probe: Option<&str>,
|
||||
) -> Result<()> {
|
||||
cmd_exec_inner(
|
||||
path,
|
||||
@@ -602,6 +617,7 @@ fn cmd_exec(
|
||||
xaudio_tick,
|
||||
ctor_probe,
|
||||
dump_addr,
|
||||
branch_probe,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
@@ -642,6 +658,7 @@ fn cmd_check(
|
||||
xaudio_tick,
|
||||
None, // ctor_probe — diagnostic, never wanted on goldens
|
||||
None, // dump_addr — same
|
||||
None, // branch_probe — diagnostic, never wanted on goldens
|
||||
out,
|
||||
expect,
|
||||
stable_digest,
|
||||
@@ -668,6 +685,7 @@ fn cmd_exec_inner(
|
||||
xaudio_tick: bool,
|
||||
ctor_probe: Option<&str>,
|
||||
dump_addr: Option<&str>,
|
||||
branch_probe: Option<&str>,
|
||||
digest_out: Option<&str>,
|
||||
digest_expect: Option<&str>,
|
||||
stable_digest: bool,
|
||||
@@ -972,6 +990,33 @@ fn cmd_exec_inner(
|
||||
}
|
||||
}
|
||||
|
||||
let branch_probe_combined: Option<String> = match (
|
||||
branch_probe,
|
||||
std::env::var("XENIA_BRANCH_PROBE").ok(),
|
||||
) {
|
||||
(Some(s), _) => Some(s.to_string()),
|
||||
(None, Some(s)) if !s.is_empty() => Some(s),
|
||||
_ => None,
|
||||
};
|
||||
if let Some(list) = branch_probe_combined {
|
||||
for token in list.split(',').map(str::trim).filter(|s| !s.is_empty()) {
|
||||
let pc = parse_hex_u32(token).map_err(|e| {
|
||||
anyhow::anyhow!("invalid PC in --branch-probe: {token:?}: {e}")
|
||||
})?;
|
||||
kernel.branch_probe_pcs.insert(pc);
|
||||
}
|
||||
if !quiet && !kernel.branch_probe_pcs.is_empty() {
|
||||
let mut pcs: Vec<u32> = kernel.branch_probe_pcs.iter().copied().collect();
|
||||
pcs.sort_unstable();
|
||||
let strs: Vec<String> = pcs.iter().map(|p| format!("{p:#010x}")).collect();
|
||||
tracing::info!(
|
||||
"branch probes armed: {} ({})",
|
||||
kernel.branch_probe_pcs.len(),
|
||||
strs.join(", "),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -1952,6 +1997,7 @@ fn worker_prologue(
|
||||
// Empty set is the common case → single `is_empty()` test inside
|
||||
// the helper, no overhead on the hot path.
|
||||
kernel.fire_ctor_probe_if_match(hw_id, mem);
|
||||
kernel.fire_branch_probe_if_match(hw_id);
|
||||
|
||||
// 1) Halt-sentinel check (per HW thread).
|
||||
if pc == LR_HALT {
|
||||
|
||||
@@ -215,6 +215,15 @@ pub struct KernelState {
|
||||
/// extended syntax of `--pc-probe` / `--ctor-probe`. Read-only
|
||||
/// load — does not mutate guest state.
|
||||
pub pc_probe_consumers: HashMap<u32, (u32, u32)>,
|
||||
/// Diagnostic. Comma-separated set of guest PCs that, when reached,
|
||||
/// emit a single compact one-line `BRANCH-PROBE` record. The line
|
||||
/// includes (pc, tid, hw, cycle, r3, lr, cr0.{lt,gt,eq}, cr6.{lt,gt,eq})
|
||||
/// — designed for tracing every conditional-branch fire inside a
|
||||
/// candidate-gate function (sub_824A9710 etc.) so the LAST PC
|
||||
/// reached before function epilogue identifies the exit branch.
|
||||
/// Distinct from `ctor_probe_pcs` because that helper emits 8
|
||||
/// frames of back-chain per hit — too noisy for branch tracing.
|
||||
pub branch_probe_pcs: std::collections::HashSet<u32>,
|
||||
/// Diagnostic. Guest addresses to dump (64 bytes each, hex + u32
|
||||
/// lanes) at end-of-run. Populated from `--dump-addr=0x828F3D08,
|
||||
/// 0x828F4070`. Used to inspect static dispatcher / job-queue /
|
||||
@@ -277,6 +286,7 @@ impl KernelState {
|
||||
parallel_active: false,
|
||||
ctor_probe_pcs: std::collections::HashSet::new(),
|
||||
pc_probe_consumers: HashMap::new(),
|
||||
branch_probe_pcs: std::collections::HashSet::new(),
|
||||
dump_addrs: Vec::new(),
|
||||
};
|
||||
crate::exports::register_exports(&mut state);
|
||||
@@ -620,6 +630,39 @@ impl KernelState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Diagnostic. If the live PC for HW slot `hw_id` is in
|
||||
/// `self.branch_probe_pcs`, emit one compact `BRANCH-PROBE` line
|
||||
/// with (pc, tid, hw, cycle, r3, lr, cr0.{lt,gt,eq}, cr6.{lt,gt,eq}).
|
||||
/// No back-chain walk — designed for tracing every conditional
|
||||
/// branch fire inside a candidate-gate function. Read-only.
|
||||
/// Lockstep digest unaffected.
|
||||
pub fn fire_branch_probe_if_match(&self, hw_id: u8) {
|
||||
if self.branch_probe_pcs.is_empty() {
|
||||
return;
|
||||
}
|
||||
let ctx = self.scheduler.ctx(hw_id);
|
||||
let pc = ctx.pc;
|
||||
if !self.branch_probe_pcs.contains(&pc) {
|
||||
return;
|
||||
}
|
||||
let tid = self.scheduler.tid(hw_id).unwrap_or(0);
|
||||
let r3 = ctx.gpr[3] as u32;
|
||||
let lr = ctx.lr as u32;
|
||||
let cycle = ctx.cycle_count;
|
||||
let cr0 = &ctx.cr[0];
|
||||
let cr6 = &ctx.cr[6];
|
||||
println!(
|
||||
"BRANCH-PROBE pc={:#010x} tid={} hw={} cycle={} r3={:#010x} lr={:#010x} cr0={}{}{} cr6={}{}{}",
|
||||
pc, tid, hw_id, cycle, r3, lr,
|
||||
if cr0.lt { 'L' } else { '.' },
|
||||
if cr0.gt { 'G' } else { '.' },
|
||||
if cr0.eq { 'E' } else { '.' },
|
||||
if cr6.lt { 'L' } else { '.' },
|
||||
if cr6.gt { 'G' } else { '.' },
|
||||
if cr6.eq { 'E' } else { '.' },
|
||||
);
|
||||
}
|
||||
|
||||
/// Read a TLS slot for the currently running HW thread.
|
||||
pub fn tls_get(&self, index: u32) -> u64 {
|
||||
self.scheduler.tls_get(index)
|
||||
|
||||
Reference in New Issue
Block a user