M3: vtable scan + MSVC RTTI walk + 3 new tables

Adds detection of statically-allocated MSVC vtables in .rdata/.data:
- New `xenia_analysis::vtables` walks read-only sections looking for runs of
  ≥3 contiguous big-endian u32 values where each value lands on a known
  function start (from M1's corrected functions table). 2-slot runs are
  rejected to keep false-positive rate down.
- For each candidate the MSVC RTTI walk vtable[-1] → CompleteObjectLocator
  → TypeDescriptor → mangled name is attempted; on success the demangled
  class name is recorded along with a best-effort RTTIClassHierarchyDescriptor
  walk to fill base_classes_json. On failure (RTTI stripped — common for
  shipped game binaries) the class is named ANON_Class_<fnv1a-hash> keyed
  by sorted method-PC list, so identical vtables collapse to one entry.
- DB: new tables `vtables`, `methods`, `classes` with indices on
  function_address and rtti_present. `write_analysis_results` takes a
  `&[Vtable]` slice; `write_disasm` (back-compat) passes empty.
- cmd_dis wires the scan after xref analysis using
  `func_analysis.functions.keys()` as the function-start oracle.

Validation on Sylpheed (RTTI stripped, as expected): 722 vtables / 499
unique classes / 5571 methods. Sanity invariant: every methods.function_address
joins to functions.address (0 broken refs). Largest vtable: 131 slots.

Tests 617→621 (+4 vtable unit tests covering 3-slot detect, 2-slot reject,
synth name stability, and synth name divergence).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-05-08 20:17:45 +02:00
parent bd5753311e
commit 1d6c51fbf8
6 changed files with 620 additions and 8 deletions

View File

@@ -4051,6 +4051,21 @@ fn cmd_dis(
"xref analysis complete"
);
// Vtable + RTTI scan (M3). Uses M1's corrected function-start set as the
// pointer-validity oracle; runs over .rdata + .data.
let function_starts: std::collections::BTreeSet<u32> =
func_analysis.functions.keys().copied().collect();
let vtables = xenia_analysis::vtables::analyze(
&pe_image, base, &sections, &function_starts,
);
let rtti_count = vtables.iter().filter(|v| v.rtti_present).count();
info!(
vtables = vtables.len(),
rtti = rtti_count,
anon = vtables.len() - rtti_count,
"vtable scan complete",
);
// Build DisasmInfo
let disasm_info = xenia_analysis::formatter::DisasmInfo {
image_base: base,
@@ -4074,6 +4089,7 @@ fn cmd_dis(
&func_analysis,
&xref_result.labels,
&xref_result.xrefs,
&vtables,
)?;
if matches!(analyze, AnalyzeMode::Sql | AnalyzeMode::Both) {
w.create_sql_views()?;