M9.5 + M11.5 + VMX + SJIS/UTF-8: close the post-M5.5 deferred set
Closes the four remaining deferred follow-up items in one bundle. All four are smaller-scope and additive; lockstep determinism unaffected (analyzer-only changes). ## M9.5 — __CxxFrameHandler scope-table parsing - New `xenia_analysis::eh_scope` module. Magic-scans .rdata for the three documented MSVC FuncInfo signatures (0x19930520/21/22) on 4-byte alignment. Each match is parsed as the documented struct (BE u32 fields), with sanity caps on max_state / n_try_blocks / pointer validity. - Walks pUnwindMap (UnwindMapEntry, 8 bytes) and pTryBlockMap (TryBlockMapEntry, 20 bytes) into one row each. - New tables eh_funcinfo, eh_unwind_map, eh_try_blocks. - Sylpheed yield: 2,588 FuncInfo (all version 0x19930522) / 10,019 unwind entries / 315 try-blocks. ## M11.5 — Static-init driver chain detection - New `xenia_analysis::static_init` module. Walks every function looking for the canonical _initterm loop: lwz cursor; mtctr; bcctrl; addi cursor, cursor, 4 bounded by a compare against another constant register. Extracts (array_start, array_end) and reads the array. - Reuses `function_pointer_arrays` table — drivers' arrays land with kind='static_init' (replacing M11's prologue-heuristic output where the structurally-grounded pattern fires). - Sylpheed yield: 0 drivers detected — the binary's static-init structure does not match the canonical CRT loop. Infrastructure ready; future M11.6 can relax. ## VMX vector-store xrefs (M6 follow-up) - Adds AltiVec/VMX X-form load/store XOs to the M6 opcode-31 dispatch: lvx/lvxl/lvebx/lvehx/lvewx (reads) and stvx/stvxl/stvebx/stvehx/stvewx (writes), all addr_mode= 'x_form_indexed'. Static resolution still requires both rA and rB constant. - Sylpheed yield: 110 newly-detected stvx writes. ## Shift_JIS + UTF-8 localised-string detection (M7 follow-up) - Extends `xenia_analysis::strings::analyze` with scan_shift_jis (JIS X 0208 lead/trail byte ranges + half-width katakana pass-through) and scan_utf8 (2- and 3-byte sequences). At least one multi-byte unit required so pure-ASCII strings aren't double-counted. - SJIS bytes rendered as \xHH escapes for diagnostic readability; full SJIS→UTF-8 decoding deferred. - Sylpheed yield: 790 Shift_JIS strings (Japanese debug + UI text) + 39 UTF-8. ## Tests - +2 EH (parses_minimal_funcinfo_v0, rejects_bogus_max_state) - +2 static_init (detects_canonical_initterm_loop, rejects_function_without_pattern) - +2 strings (detects_shift_jis_string, detects_utf8_multibyte_string) Tests 649→655 (+6 unit tests). DB schema golden + write_analysis_results signature updated for new EH parameter. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -307,7 +307,8 @@ impl DbWriter {
|
||||
/// `vtables` is the M3 result; pass an empty slice when the caller has
|
||||
/// not run the vtable scan (the tables are still created, just empty).
|
||||
/// `strings` is the M7 result; same convention. `funcptr_arrays` is the
|
||||
/// M8/M11 result. `typed_ind` is the M5.5 result.
|
||||
/// M8/M11 result. `typed_ind` is the M5.5 result. `eh_records` is the
|
||||
/// M9.5 result.
|
||||
#[tracing::instrument(skip_all, name = "db.write_analysis_results")]
|
||||
pub fn write_analysis_results(
|
||||
&mut self,
|
||||
@@ -320,6 +321,7 @@ impl DbWriter {
|
||||
strings: &[crate::strings::DetectedString],
|
||||
funcptr_arrays: &[crate::funcptr_arrays::FuncPtrArray],
|
||||
typed_ind: Option<&crate::ind_dispatch_typed::TypedIndirectResult>,
|
||||
eh_records: &[crate::eh_scope::EhFuncInfo],
|
||||
) -> anyhow::Result<()> {
|
||||
self.conn.execute_batch("
|
||||
CREATE TABLE functions (
|
||||
@@ -441,6 +443,40 @@ impl DbWriter {
|
||||
PRIMARY KEY (writer_pc, vtable_address, vptr_offset)
|
||||
);
|
||||
|
||||
-- M9.5 — MSVC __CxxFrameHandler scope-table records found by
|
||||
-- magic-number scan in .rdata.
|
||||
CREATE TABLE eh_funcinfo (
|
||||
address BIGINT PRIMARY KEY,
|
||||
magic BIGINT NOT NULL, -- 0x19930520/21/22
|
||||
max_state BIGINT NOT NULL,
|
||||
p_unwind_map BIGINT NOT NULL,
|
||||
n_try_blocks BIGINT NOT NULL,
|
||||
p_try_block_map BIGINT NOT NULL,
|
||||
n_ip_map_entries BIGINT NOT NULL,
|
||||
p_ip_to_state_map BIGINT NOT NULL,
|
||||
p_es_type_list BIGINT,
|
||||
eh_flags BIGINT
|
||||
);
|
||||
|
||||
CREATE TABLE eh_unwind_map (
|
||||
funcinfo_address BIGINT NOT NULL, -- FK to eh_funcinfo.address
|
||||
state_index BIGINT NOT NULL,
|
||||
to_state BIGINT NOT NULL,
|
||||
action_pc BIGINT NOT NULL,
|
||||
PRIMARY KEY (funcinfo_address, state_index)
|
||||
);
|
||||
|
||||
CREATE TABLE eh_try_blocks (
|
||||
funcinfo_address BIGINT NOT NULL, -- FK to eh_funcinfo.address
|
||||
try_index BIGINT NOT NULL,
|
||||
try_low BIGINT NOT NULL,
|
||||
try_high BIGINT NOT NULL,
|
||||
catch_high BIGINT NOT NULL,
|
||||
n_catches BIGINT NOT NULL,
|
||||
p_handler_array BIGINT NOT NULL,
|
||||
PRIMARY KEY (funcinfo_address, try_index)
|
||||
);
|
||||
|
||||
CREATE TABLE demangled_names (
|
||||
address BIGINT, -- VA the mangled name is associated with; NULL when from a non-address source (e.g. RTTI-only string)
|
||||
mangled VARCHAR NOT NULL, -- original mangled symbol (e.g. ?Foo@Bar@@QEAAXXZ)
|
||||
@@ -474,6 +510,7 @@ impl DbWriter {
|
||||
if let Some(t) = typed_ind {
|
||||
insert_typed_ind_dispatch(&self.conn, t)?;
|
||||
}
|
||||
insert_eh_records(&self.conn, eh_records)?;
|
||||
insert_xrefs_streaming(&self.conn, xrefs, pe, info.image_base, func_analysis, labels)?;
|
||||
|
||||
let indices = [
|
||||
@@ -524,7 +561,7 @@ impl DbWriter {
|
||||
xrefs: &XrefMap,
|
||||
) -> anyhow::Result<()> {
|
||||
self.ingest_instructions(pe, info, func_analysis, labels)?;
|
||||
self.write_analysis_results(pe, info, func_analysis, labels, xrefs, &[], &[], &[], None)?;
|
||||
self.write_analysis_results(pe, info, func_analysis, labels, xrefs, &[], &[], &[], None, &[])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -993,6 +1030,69 @@ fn insert_strings(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn insert_eh_records(
|
||||
conn: &Connection,
|
||||
records: &[crate::eh_scope::EhFuncInfo],
|
||||
) -> anyhow::Result<()> {
|
||||
if records.is_empty() { return Ok(()); }
|
||||
let mut stmt_fi = conn.prepare(
|
||||
"INSERT INTO eh_funcinfo
|
||||
(address, magic, max_state, p_unwind_map, n_try_blocks,
|
||||
p_try_block_map, n_ip_map_entries, p_ip_to_state_map,
|
||||
p_es_type_list, eh_flags)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT DO NOTHING"
|
||||
)?;
|
||||
let mut stmt_unwind = conn.prepare(
|
||||
"INSERT INTO eh_unwind_map
|
||||
(funcinfo_address, state_index, to_state, action_pc)
|
||||
VALUES (?, ?, ?, ?) ON CONFLICT DO NOTHING"
|
||||
)?;
|
||||
let mut stmt_try = conn.prepare(
|
||||
"INSERT INTO eh_try_blocks
|
||||
(funcinfo_address, try_index, try_low, try_high, catch_high,
|
||||
n_catches, p_handler_array)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT DO NOTHING"
|
||||
)?;
|
||||
let mut n_fi = 0u64;
|
||||
let mut n_unwind = 0u64;
|
||||
let mut n_try = 0u64;
|
||||
for r in records {
|
||||
stmt_fi.execute(params![
|
||||
r.address as i64, r.magic as i64, r.max_state as i64,
|
||||
r.p_unwind_map as i64, r.n_try_blocks as i64,
|
||||
r.p_try_block_map as i64, r.n_ip_map_entries as i64,
|
||||
r.p_ip_to_state_map as i64,
|
||||
r.p_es_type_list.map(|p| p as i64),
|
||||
r.eh_flags.map(|f| f as i64),
|
||||
])?;
|
||||
n_fi += 1;
|
||||
for (i, e) in r.unwind_map.iter().enumerate() {
|
||||
stmt_unwind.execute(params![
|
||||
r.address as i64, i as i64, e.to_state as i64, e.action_pc as i64,
|
||||
])?;
|
||||
n_unwind += 1;
|
||||
}
|
||||
for (i, t) in r.try_blocks.iter().enumerate() {
|
||||
stmt_try.execute(params![
|
||||
r.address as i64, i as i64,
|
||||
t.try_low as i64, t.try_high as i64, t.catch_high as i64,
|
||||
t.n_catches as i64, t.p_handler_array as i64,
|
||||
])?;
|
||||
n_try += 1;
|
||||
}
|
||||
}
|
||||
metrics::counter!("db.rows", "table" => "eh_funcinfo").increment(n_fi);
|
||||
metrics::counter!("db.rows", "table" => "eh_unwind_map").increment(n_unwind);
|
||||
metrics::counter!("db.rows", "table" => "eh_try_blocks").increment(n_try);
|
||||
tracing::info!(
|
||||
funcinfo = n_fi, unwind = n_unwind, try_blocks = n_try,
|
||||
"EH scope-table insert complete"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn insert_typed_ind_dispatch(
|
||||
conn: &Connection,
|
||||
t: &crate::ind_dispatch_typed::TypedIndirectResult,
|
||||
|
||||
Reference in New Issue
Block a user