M8+M9+M10+M11+M12: LOW-tier milestones — funcptr-arrays, EH flag, TLS, lr-trace
Five LOW-priority milestones bundled. Total ~700 LOC across 11 files. ## M9 — has_eh derived from pdata.flags exception bit - New `functions.has_eh BOOLEAN NOT NULL` column. Derived from M1's already-parsed `pdata.flags` (bit 31 of the packed word — the exception-handler-present flag, distinct from bit 30 which is the always-1 32-bit-code flag). Index idx_functions_has_eh. - Sylpheed: 2,975 of 23,073 pdata-validated functions have EH (12.9%). ## M10 — .tls section / IMAGE_TLS_DIRECTORY32 parser - New `xenia_xex::tls::parse_tls` parses the directory + zero-terminated callback array. Returns None when the binary has no .tls section. - New `tls_info` (singleton row) + `tls_callbacks(slot, address)` tables. - New `DbWriter::write_tls()` no-ops on None. - Sylpheed has no .tls section → 0 rows; infra ready for binaries with __declspec(thread). ## M8 + M11 — function_pointer_arrays (dispatch tables + static initialisers) - New `xenia_analysis::funcptr_arrays::analyze` widens M3's vtable scan: detects runs of ≥2 function pointers in .rdata and classifies each as `vtable` (M3 re-emit), `dispatch_table` (M8), or `static_init` (M11) via a constructor-prologue heuristic (mfspr + small stwu). - New tables `function_pointer_arrays(address PK, length, kind)` and `function_pointer_array_entries(array_address, slot, function_address)`. - Sylpheed: 722 vtables + 388 dispatch_tables = 1,110 arrays / 6,347 slots. 0 static_init detected (Sylpheed's ctors don't all match the conservative heuristic; M11.5 future work can chain via the entry- point's static-init driver). ## M12 — --lr-trace runtime canary-diff harness - New CLI `exec --lr-trace=PC[,PC,...]` and `--lr-trace-out=PATH` flags. Symbolic resolution (Class::method, Class::*) via M4 lookup. Env vars XENIA_LR_TRACE / XENIA_LR_TRACE_OUT also work. - New `KernelState::lr_trace_pcs` + `lr_trace_writer` + helper `fire_lr_trace_if_match(hw_id)` invoked from the per-instr probe slot. - JSONL output: pc/tid/hw/cycle/r3/r4/r5/r6/lr — superset of what xenia-canary's --log_lr_on_pc patch emits, with a cycle counter for cross-run reproducibility. Diff-friendly via `jq`. - Lockstep digest unaffected: smoke test on entry-point PC fires once with cycle=0/lr=BCBCBCBC/all-GPR-zero (correct initial state). Tests 636→640 (+2 TLS tests, +2 funcptr_arrays tests). Schema golden updated for new tables + has_eh column. Lockstep determinism preserved (instructions=2000005 ×2 reruns identical). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -230,6 +230,17 @@ pub struct KernelState {
|
||||
/// 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>,
|
||||
/// M12 — diagnostic. PCs at which to emit a structured JSONL record
|
||||
/// per fire, designed for diffing against xenia-canary's
|
||||
/// `--log_lr_on_pc` patch output. Each line carries
|
||||
/// `(pc, tid, hw, cycle, r3, r4, r5, r6, lr)` — a superset of what
|
||||
/// canary logs. Settable via `--lr-trace` / `XENIA_LR_TRACE`. Stdout
|
||||
/// by default; redirect with `--lr-trace-out=PATH`. Read-only;
|
||||
/// lockstep digest unaffected.
|
||||
pub lr_trace_pcs: std::collections::HashSet<u32>,
|
||||
/// M12 — optional file writer for `--lr-trace` output. `None` means
|
||||
/// stdout.
|
||||
pub lr_trace_writer: Option<std::sync::Mutex<std::fs::File>>,
|
||||
/// 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 /
|
||||
@@ -297,6 +308,8 @@ impl KernelState {
|
||||
ctor_probe_pcs: std::collections::HashSet::new(),
|
||||
pc_probe_consumers: HashMap::new(),
|
||||
branch_probe_pcs: std::collections::HashSet::new(),
|
||||
lr_trace_pcs: std::collections::HashSet::new(),
|
||||
lr_trace_writer: None,
|
||||
dump_addrs: Vec::new(),
|
||||
dump_section: None,
|
||||
};
|
||||
@@ -674,6 +687,46 @@ impl KernelState {
|
||||
);
|
||||
}
|
||||
|
||||
/// M12 — diagnostic. If the live PC for HW slot `hw_id` is in
|
||||
/// `self.lr_trace_pcs`, emit one JSONL record. Format mirrors what
|
||||
/// xenia-canary's `--log_lr_on_pc` patch emits, plus the cycle
|
||||
/// counter. Read-only; lockstep digest unaffected.
|
||||
pub fn fire_lr_trace_if_match(&self, hw_id: u8) {
|
||||
if self.lr_trace_pcs.is_empty() {
|
||||
return;
|
||||
}
|
||||
let ctx = self.scheduler.ctx(hw_id);
|
||||
let pc = ctx.pc;
|
||||
if !self.lr_trace_pcs.contains(&pc) {
|
||||
return;
|
||||
}
|
||||
let tid = self.scheduler.tid(hw_id).unwrap_or(0);
|
||||
let r3 = ctx.gpr[3] as u32;
|
||||
let r4 = ctx.gpr[4] as u32;
|
||||
let r5 = ctx.gpr[5] as u32;
|
||||
let r6 = ctx.gpr[6] as u32;
|
||||
let lr = ctx.lr as u32;
|
||||
let cycle = ctx.cycle_count;
|
||||
let line = format!(
|
||||
"{{\"pc\":\"{:#010x}\",\"tid\":{},\"hw\":{},\"cycle\":{},\
|
||||
\"r3\":\"{:#010x}\",\"r4\":\"{:#010x}\",\"r5\":\"{:#010x}\",\
|
||||
\"r6\":\"{:#010x}\",\"lr\":\"{:#010x}\"}}\n",
|
||||
pc, tid, hw_id, cycle, r3, r4, r5, r6, lr,
|
||||
);
|
||||
match &self.lr_trace_writer {
|
||||
Some(mu) => {
|
||||
if let Ok(mut f) = mu.lock() {
|
||||
use std::io::Write;
|
||||
let _ = f.write_all(line.as_bytes());
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// Stdout path; small alloc, fine for diagnostic use.
|
||||
print!("{line}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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