M5+M7: indirect-dispatch reachability + .rdata string detection
Two MEDIUM milestones bundled (both opportunistic per plan; both small).
## M5 — indirect-dispatch reachability
- `xenia_analysis::indirect`: per-basic-block register tracker over each
detected function. Recognises the canonical static-vtable pattern
`lis+addi → lwz off(rA) → mtctr → bcctrl` where rA holds a known M3
vtable address. Emits one `Xref { kind: IndirectCall }` per resolvable
bcctrl site.
- PowerPC ABI awareness: `bl`-style calls clobber volatile r0..r12 + ctr
but preserve non-volatile r13..r31, so a vtable pointer parked in r30/r31
before a call survives.
- Label-based basic-block boundaries kill register state — bounds
false-positive risk for jump-IN paths.
- New `XrefKind::IndirectCall` variant (DB tag `'ind_call'`).
- New SQL view `v_indirect_reachability_from_entry` — strict superset of
`v_reachability_from_entry`, taking `ind_call` edges in the BFS.
Sylpheed yield: 0 edges detected. The binary's 1,001 static lis+addi
references into vtables are nearly all constructor-side vptr writes, not
dispatches; real method dispatch goes through `this->vptr` which requires
alias analysis we explicitly don't do. Documented in SCHEMA.md as the
expected limitation. Three unit tests cover the synthetic-correctness path.
## M7 — string / constant-pool detection
- `xenia_analysis::strings`: scans `.rdata` for runs of ≥ 6 printable
ASCII bytes (NUL-terminated) and ≥ 6 UTF-16LE code units (basic-plane
printable ASCII, NUL u16 terminator).
- New `strings(address PK, encoding, length, content)` table + encoding index.
- Implicit cross-ref via existing `xrefs.kind='ref'` rows whose target
matches a strings.address.
Sylpheed yield: 6,311 ASCII strings (including embedded HLSL shader source
and AS_CB_SURFACE_SWIZZLE_* assertion strings). 9,132 lis+addi sites
cross-reference detected strings — names source PCs near each string in
one query. Four unit tests cover encoding detection, NUL termination, and
short-run rejection.
Tests 626→633 (+3 indirect, +4 strings).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4081,7 +4081,7 @@ fn cmd_dis(
|
||||
);
|
||||
|
||||
// Cross-reference analysis
|
||||
let xref_result = xenia_analysis::xref::analyze_xrefs(
|
||||
let mut xref_result = xenia_analysis::xref::analyze_xrefs(
|
||||
&pe_image, base, entry, §ions, &func_analysis, &import_map,
|
||||
);
|
||||
let total_xrefs: usize = xref_result.xrefs.values().map(|v| v.len()).sum();
|
||||
@@ -4106,6 +4106,28 @@ fn cmd_dis(
|
||||
"vtable scan complete",
|
||||
);
|
||||
|
||||
// Indirect-dispatch reachability (M5). Walks each function looking for
|
||||
// the canonical lis+addi → lwz off(vtable) → mtctr → bcctrl pattern and
|
||||
// emits one xref edge per resolvable site. Inserted into xrefs as
|
||||
// kind='ind_call'.
|
||||
let indirect_edges = xenia_analysis::indirect::analyze(
|
||||
&pe_image, base, &func_analysis, &vtables, &xref_result.labels,
|
||||
);
|
||||
info!(indirect_edges = indirect_edges.len(), "indirect-dispatch scan complete");
|
||||
for edge in &indirect_edges {
|
||||
xref_result.xrefs
|
||||
.entry(edge.target)
|
||||
.or_default()
|
||||
.push(xenia_analysis::xref::Xref {
|
||||
source: edge.source,
|
||||
kind: xenia_analysis::xref::XrefKind::IndirectCall,
|
||||
});
|
||||
}
|
||||
|
||||
// String / constant-pool detection (M7).
|
||||
let strings = xenia_analysis::strings::analyze(&pe_image, base, §ions);
|
||||
info!(strings = strings.len(), "string scan complete");
|
||||
|
||||
// Build DisasmInfo
|
||||
let disasm_info = xenia_analysis::formatter::DisasmInfo {
|
||||
image_base: base,
|
||||
@@ -4130,6 +4152,7 @@ fn cmd_dis(
|
||||
&xref_result.labels,
|
||||
&xref_result.xrefs,
|
||||
&vtables,
|
||||
&strings,
|
||||
)?;
|
||||
if matches!(analyze, AnalyzeMode::Sql | AnalyzeMode::Both) {
|
||||
w.create_sql_views()?;
|
||||
|
||||
Reference in New Issue
Block a user