M1: parse .pdata RUNTIME_FUNCTION; cross-validate function boundaries
Adds an authoritative function-boundary source from the linker: - New `xenia_xex::pdata` parses .pdata 8-byte entries (BeginAddress + packed prolog/length/flags). Bit layout per Microsoft PE32 PowerPC spec: prolog in bits 0..7, function_length in bits 8..29, flags in 30..31. - `func::analyze_with_pdata` unions pdata BeginAddresses into the candidate set, attaches `pdata_validated`/`pdata_length` to each `FuncInfo`, and trims any function whose `end` overlaps the next start (catches mis-merge where one row spanned two prologues — the audit-031 sub_824D23B0/sub_824D29F0 case). - DB: extends `functions` with `pdata_validated BOOLEAN`, `pdata_length BIGINT`; new table `pdata_entries`; index on pdata_validated. - New `crates/xenia-analysis/SCHEMA.md` documents M1 layer + forward work. Validation on Sylpheed: 25481 functions (was 12156) / 23073 pdata_validated / 0 orphans / 0 mis-merges. Audit-031 mis-merge resolved: sub_824D29F0 now has its own row with `pdata_length=280` (70 dwords); sub_824D23B0 now correctly ends at 0x824D2878 (`pdata_length=1224` matches prologue walk). Tests 605→610. New 5-test pdata unit suite covers bit layout + sentinel + out-of-range filtering + real-world layout round-trip. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -314,13 +314,23 @@ impl DbWriter {
|
||||
) -> anyhow::Result<()> {
|
||||
self.conn.execute_batch("
|
||||
CREATE TABLE functions (
|
||||
address BIGINT PRIMARY KEY, -- absolute VA of entry point
|
||||
name VARCHAR NOT NULL, -- symbol name, or sub_XXXXXXXX if unresolved
|
||||
end_address BIGINT NOT NULL, -- VA of last instruction + 4 (exclusive end)
|
||||
frame_size BIGINT NOT NULL, -- stack frame size in bytes (from prologue)
|
||||
saved_gprs BIGINT NOT NULL, -- bitmask of GPRs saved in prologue (bit N = rN)
|
||||
is_leaf BOOLEAN NOT NULL, -- true if the function has no outgoing calls
|
||||
is_saverestore BOOLEAN NOT NULL -- true if __savegprlr_* / __restgprlr_* stub
|
||||
address BIGINT PRIMARY KEY, -- absolute VA of entry point
|
||||
name VARCHAR NOT NULL, -- symbol name, or sub_XXXXXXXX if unresolved
|
||||
end_address BIGINT NOT NULL, -- VA of last instruction + 4 (exclusive end)
|
||||
frame_size BIGINT NOT NULL, -- stack frame size in bytes (from prologue)
|
||||
saved_gprs BIGINT NOT NULL, -- bitmask of GPRs saved in prologue (bit N = rN)
|
||||
is_leaf BOOLEAN NOT NULL, -- true if the function has no outgoing calls
|
||||
is_saverestore BOOLEAN NOT NULL, -- true if __savegprlr_* / __restgprlr_* stub
|
||||
pdata_validated BOOLEAN NOT NULL, -- true if .pdata RUNTIME_FUNCTION exists at this VA
|
||||
pdata_length BIGINT -- length in bytes per .pdata; NULL if no pdata entry
|
||||
);
|
||||
|
||||
CREATE TABLE pdata_entries (
|
||||
begin_address BIGINT PRIMARY KEY, -- absolute VA of function start (RUNTIME_FUNCTION.BeginAddress)
|
||||
end_address BIGINT NOT NULL, -- begin_address + function_length (exclusive)
|
||||
function_length BIGINT NOT NULL, -- function size in bytes
|
||||
prolog_length BIGINT NOT NULL, -- prolog size in bytes
|
||||
flags BIGINT NOT NULL -- raw 2-bit flags (bit 1=32-bit-code, bit 0=exception)
|
||||
);
|
||||
|
||||
CREATE TABLE labels (
|
||||
@@ -341,11 +351,13 @@ impl DbWriter {
|
||||
")?;
|
||||
|
||||
insert_functions(&self.conn, func_analysis, labels)?;
|
||||
insert_pdata_entries(&self.conn, &func_analysis.pdata_entries)?;
|
||||
insert_labels(&self.conn, labels)?;
|
||||
insert_xrefs_streaming(&self.conn, xrefs, pe, info.image_base, func_analysis, labels)?;
|
||||
|
||||
let indices = [
|
||||
("idx_functions_name", "CREATE INDEX idx_functions_name ON functions(name)"),
|
||||
("idx_functions_pdata_validated", "CREATE INDEX idx_functions_pdata_validated ON functions(pdata_validated)"),
|
||||
("idx_labels_kind", "CREATE INDEX idx_labels_kind ON labels(kind)"),
|
||||
("idx_labels_name", "CREATE INDEX idx_labels_name ON labels(name)"),
|
||||
("idx_xrefs_target", "CREATE INDEX idx_xrefs_target ON xrefs(target)"),
|
||||
@@ -680,8 +692,10 @@ fn insert_functions(
|
||||
labels: &HashMap<u32, String>,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut stmt = conn.prepare(
|
||||
"INSERT INTO functions (address, name, end_address, frame_size, saved_gprs, is_leaf, is_saverestore)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)"
|
||||
"INSERT INTO functions
|
||||
(address, name, end_address, frame_size, saved_gprs, is_leaf, is_saverestore,
|
||||
pdata_validated, pdata_length)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
||||
)?;
|
||||
for (&addr, fi) in &func_analysis.functions {
|
||||
let name = labels.get(&addr)
|
||||
@@ -695,6 +709,33 @@ fn insert_functions(
|
||||
fi.saved_gprs as i64,
|
||||
fi.is_leaf,
|
||||
fi.is_saverestore,
|
||||
fi.pdata_validated,
|
||||
fi.pdata_length.map(|n| n as i64),
|
||||
])?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn insert_pdata_entries(
|
||||
conn: &Connection,
|
||||
entries: &[xenia_xex::pdata::PdataEntry],
|
||||
) -> anyhow::Result<()> {
|
||||
if entries.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let mut stmt = conn.prepare(
|
||||
"INSERT INTO pdata_entries
|
||||
(begin_address, end_address, function_length, prolog_length, flags)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
ON CONFLICT DO NOTHING"
|
||||
)?;
|
||||
for e in entries {
|
||||
stmt.execute(params![
|
||||
e.begin_address as i64,
|
||||
e.end_address() as i64,
|
||||
e.function_length as i64,
|
||||
e.prolog_length as i64,
|
||||
e.flags as i64,
|
||||
])?;
|
||||
}
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user