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:
MechaCat02
2026-05-04 21:35:10 +02:00
parent 79697ddf4e
commit c51f51f9cb
3 changed files with 229 additions and 0 deletions

View File

@@ -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 {