286 lines
11 KiB
Rust
286 lines
11 KiB
Rust
//! Additive SQL views over the Phase-3 ingest tables.
|
|
//!
|
|
//! These views are created when `--analyze=sql` or `--analyze=both` is set.
|
|
//! They are *not* a replacement for the Rust passes ([`crate::xref`],
|
|
//! [`crate::func`]) — those still own data-ref resolution and prologue
|
|
//! pattern matching. The views cover the cleanly-relational parts:
|
|
//!
|
|
//! - branch xrefs (self-join on `instructions.target_hex`)
|
|
//! - call graph + reachability (recursive CTE over `xrefs`)
|
|
//! - convenience joins (function-first-instruction, imports-called)
|
|
//!
|
|
//! All views are read-only and stable across re-creation: dropping and
|
|
//! recreating the database via [`crate::db::DbWriter::open_fresh`] re-runs
|
|
//! these definitions.
|
|
//!
|
|
//! ## Cross-check semantics
|
|
//!
|
|
//! `v_branch_xrefs` is intended to produce *exactly* the same `(source,
|
|
//! target, kind)` tuples as the Rust `xref.rs` first pass — given the same
|
|
//! input image. [`crate::db::DbWriter::cross_check_branch_xrefs`] queries
|
|
//! the symmetric difference and returns the row counts; both should be
|
|
//! zero. A non-zero count means the formatter's `mnemonic` column or the
|
|
//! kind-classification CASE drifted out of agreement with `xref.rs`, and
|
|
//! is worth a one-line warning at log time.
|
|
|
|
|
|
/// Every XDBF string side-by-side across the languages the title ships, so a
|
|
/// piece of UI text can be looked up once and read in all locales.
|
|
const V_XDBF_TEXT: &str = "
|
|
CREATE OR REPLACE VIEW v_xdbf_text AS
|
|
SELECT
|
|
s.string_id,
|
|
MAX(CASE WHEN s.language = 1 THEN s.value END) AS english,
|
|
MAX(CASE WHEN s.language = 2 THEN s.value END) AS japanese,
|
|
MAX(CASE WHEN s.language = 3 THEN s.value END) AS german,
|
|
MAX(CASE WHEN s.language = 4 THEN s.value END) AS french,
|
|
MAX(CASE WHEN s.language = 5 THEN s.value END) AS spanish,
|
|
MAX(CASE WHEN s.language = 6 THEN s.value END) AS italian
|
|
FROM xdbf_strings s
|
|
GROUP BY s.string_id;
|
|
";
|
|
|
|
/// Achievements joined to their three strings in every shipped language.
|
|
const V_XDBF_ACHIEVEMENTS: &str = "
|
|
CREATE OR REPLACE VIEW v_xdbf_achievements AS
|
|
SELECT
|
|
a.id,
|
|
a.gamerscore,
|
|
s.language,
|
|
s.language_name,
|
|
n.value AS name,
|
|
u.value AS unlocked_desc,
|
|
l.value AS locked_desc,
|
|
a.image_id
|
|
FROM xdbf_achievements a
|
|
JOIN (SELECT DISTINCT language, language_name FROM xdbf_strings) s ON TRUE
|
|
LEFT JOIN xdbf_strings n ON n.language = s.language AND n.string_id = a.label_id
|
|
LEFT JOIN xdbf_strings u ON u.language = s.language AND u.string_id = a.description_id
|
|
LEFT JOIN xdbf_strings l ON l.language = s.language AND l.string_id = a.unachieved_id;
|
|
";
|
|
|
|
/// `(view_name, CREATE VIEW … SQL)` pairs in the order they must run.
|
|
/// Later views may depend on earlier ones (e.g. `v_call_graph` reads
|
|
/// `xrefs`, which is the Rust-pass table; `v_branch_xrefs` is independent).
|
|
pub const ALL_VIEWS: &[(&str, &str)] = &[
|
|
("v_branch_xrefs", V_BRANCH_XREFS),
|
|
("v_call_graph", V_CALL_GRAPH),
|
|
("v_reachability_from_entry", V_REACHABILITY_FROM_ENTRY),
|
|
("v_indirect_reachability_from_entry", V_INDIRECT_REACHABILITY_FROM_ENTRY),
|
|
("v_function_first_instruction", V_FUNCTION_FIRST_INSTRUCTION),
|
|
("v_imports_called", V_IMPORTS_CALLED),
|
|
("v_xdbf_text", V_XDBF_TEXT),
|
|
("v_xdbf_achievements", V_XDBF_ACHIEVEMENTS),
|
|
("v_switch_cases", V_SWITCH_CASES),
|
|
("v_class_hierarchy", V_CLASS_HIERARCHY),
|
|
("v_class_methods", V_CLASS_METHODS),
|
|
("v_function_strings", V_FUNCTION_STRINGS),
|
|
];
|
|
|
|
/// Branch cross-references derived purely from `instructions.target_hex`.
|
|
///
|
|
/// Mirrors the kind classification in [`crate::xref::collect_branch_target`]
|
|
/// and the short tags returned by [`crate::xref::XrefKind::tag`] (which are
|
|
/// what `xrefs.kind` actually stores):
|
|
/// - I-form (`b`/`bl`/`ba`/`bla`): `bl`/`bla` → `"call"`, `b`/`ba` → `"j"`
|
|
/// - B-form (`bc`/`bcl`/`bca`/`bcla`): always → `"br"`
|
|
///
|
|
/// Indirect branches (`bclr`/`bcctr`) leave `target_hex` NULL and are
|
|
/// excluded from this view by design.
|
|
const V_BRANCH_XREFS: &str = "
|
|
CREATE OR REPLACE VIEW v_branch_xrefs AS
|
|
SELECT
|
|
address AS source,
|
|
target_hex AS target,
|
|
CASE
|
|
WHEN mnemonic IN ('bl', 'bla') THEN 'call'
|
|
WHEN mnemonic IN ('b', 'ba') THEN 'j'
|
|
WHEN mnemonic IN ('bc', 'bcl', 'bca', 'bcla') THEN 'br'
|
|
ELSE 'br'
|
|
END AS kind,
|
|
mnemonic AS instruction,
|
|
function AS source_func
|
|
FROM instructions
|
|
WHERE target_hex IS NOT NULL;
|
|
";
|
|
|
|
/// Call-graph edges resolved against function names.
|
|
///
|
|
/// Reads from `xrefs` (the Rust-pass table) — this is the canonical source
|
|
/// for *all* edge kinds, including indirect/data; SQL can't reconstruct the
|
|
/// data-ref edges cleanly because they require register tracking. For pure
|
|
/// branch edges, `v_branch_xrefs` produces equivalent rows directly from
|
|
/// `instructions`.
|
|
const V_CALL_GRAPH: &str = "
|
|
CREATE OR REPLACE VIEW v_call_graph AS
|
|
SELECT
|
|
x.source AS caller_addr,
|
|
cf.name AS caller_name,
|
|
x.target AS callee_addr,
|
|
tf.name AS callee_name,
|
|
x.kind AS edge_kind
|
|
FROM xrefs x
|
|
LEFT JOIN functions cf ON cf.address = x.source_func
|
|
LEFT JOIN functions tf ON tf.address = x.target
|
|
WHERE x.kind = 'call';
|
|
";
|
|
|
|
/// Transitive function-level reachability from the entry point over
|
|
/// call/jump/branch edges. Useful for finding dead code
|
|
/// (`SELECT address FROM functions
|
|
/// WHERE address NOT IN (SELECT addr FROM v_reachability_from_entry)`)
|
|
/// and for scoping analysis to the live subset.
|
|
///
|
|
/// Seeds from the function containing the `entry_point` label and walks
|
|
/// the recursive closure: a reachable function's instructions branch into
|
|
/// the functions enclosing the branch targets, which are then reachable
|
|
/// in turn. `UNION` (not `UNION ALL`) deduplicates to handle call-graph
|
|
/// cycles (recursive functions, mutually-recursive pairs).
|
|
const V_REACHABILITY_FROM_ENTRY: &str = "
|
|
CREATE OR REPLACE VIEW v_reachability_from_entry AS
|
|
WITH RECURSIVE reach(fn) AS (
|
|
SELECT i.function FROM instructions i
|
|
JOIN labels l ON l.address = i.address
|
|
WHERE l.name = 'entry_point' AND i.function IS NOT NULL
|
|
UNION
|
|
SELECT tgt.function FROM xrefs x
|
|
JOIN instructions src ON src.address = x.source
|
|
JOIN instructions tgt ON tgt.address = x.target
|
|
JOIN reach r ON src.function = r.fn
|
|
WHERE x.kind IN ('call', 'j', 'br', 'jt')
|
|
AND tgt.function IS NOT NULL
|
|
)
|
|
SELECT fn AS addr FROM reach;
|
|
";
|
|
|
|
/// Reachability extended over `kind='ind_call'` edges from M5. Strict
|
|
/// superset of `v_reachability_from_entry` — every fn there is also here,
|
|
/// plus any function reached only via a vtable bcctrl whose vtable+slot
|
|
/// the M5 dataflow could resolve. Sample 5 newly-reachable PCs in canary
|
|
/// before trusting widely; the analysis intentionally leaves out alias-
|
|
/// dependent indirect calls (vtable loaded from a `this` field).
|
|
const V_INDIRECT_REACHABILITY_FROM_ENTRY: &str = "
|
|
CREATE OR REPLACE VIEW v_indirect_reachability_from_entry AS
|
|
WITH RECURSIVE reach(fn) AS (
|
|
SELECT i.function FROM instructions i
|
|
JOIN labels l ON l.address = i.address
|
|
WHERE l.name = 'entry_point' AND i.function IS NOT NULL
|
|
UNION
|
|
SELECT tgt.function FROM xrefs x
|
|
JOIN instructions src ON src.address = x.source
|
|
JOIN instructions tgt ON tgt.address = x.target
|
|
JOIN reach r ON src.function = r.fn
|
|
WHERE x.kind IN ('call', 'ind_call', 'j', 'br', 'jt')
|
|
AND tgt.function IS NOT NULL
|
|
)
|
|
SELECT fn AS addr FROM reach;
|
|
";
|
|
|
|
/// Convenience join: each function's first decoded instruction. Useful for
|
|
/// quickly inspecting prologue patterns without computing offsets manually.
|
|
const V_FUNCTION_FIRST_INSTRUCTION: &str = "
|
|
CREATE OR REPLACE VIEW v_function_first_instruction AS
|
|
SELECT
|
|
f.address AS function_addr,
|
|
f.name AS function_name,
|
|
i.raw AS first_raw,
|
|
i.disasm AS first_disasm,
|
|
i.ext_disasm AS first_ext_disasm
|
|
FROM functions f
|
|
JOIN instructions i ON i.address = f.address;
|
|
";
|
|
|
|
/// Per-function summary of which kernel/library imports it calls. Joins
|
|
/// xrefs (call edges) against the labels table to surface import names.
|
|
const V_IMPORTS_CALLED: &str = "
|
|
CREATE OR REPLACE VIEW v_imports_called AS
|
|
SELECT
|
|
x.source_func AS function_addr,
|
|
f.name AS function_name,
|
|
x.target AS import_addr,
|
|
l.name AS import_name
|
|
FROM xrefs x
|
|
JOIN labels l ON l.address = x.target
|
|
LEFT JOIN functions f ON f.address = x.source_func
|
|
WHERE x.kind = 'call'
|
|
AND l.kind = 'import';
|
|
";
|
|
|
|
/// Every recovered `switch` case, joined to the dispatching function and to
|
|
/// the label on the case body. One row per case *value* — several rows can
|
|
/// share a `target_address` when case values fall through to one body.
|
|
const V_SWITCH_CASES: &str = "
|
|
CREATE OR REPLACE VIEW v_switch_cases AS
|
|
SELECT
|
|
jt.bctr_pc AS dispatch_pc,
|
|
jt.function AS function_addr,
|
|
f.name AS function_name,
|
|
jt.kind AS table_kind,
|
|
jt.table_address AS table_address,
|
|
e.case_index AS case_index,
|
|
e.target_address AS target_address,
|
|
l.name AS target_label
|
|
FROM jump_tables jt
|
|
JOIN jump_table_entries e ON e.bctr_pc = jt.bctr_pc
|
|
LEFT JOIN functions f ON f.address = jt.function
|
|
LEFT JOIN labels l ON l.address = e.target_address;
|
|
";
|
|
|
|
/// The C++ inheritance graph as recovered from RTTI. Index 0 of a base-class
|
|
/// array is the class itself and is excluded, so every row is a genuine
|
|
/// `derived -> base` edge carrying the displacement triple needed to find the
|
|
/// base subobject inside an instance.
|
|
const V_CLASS_HIERARCHY: &str = "
|
|
CREATE OR REPLACE VIEW v_class_hierarchy AS
|
|
SELECT DISTINCT
|
|
dtd.demangled_name AS derived_class,
|
|
b.name AS base_class,
|
|
b.base_index AS base_index,
|
|
b.mdisp AS mdisp,
|
|
b.pdisp AS pdisp,
|
|
b.vdisp AS vdisp,
|
|
c.vtable_address AS derived_vtable
|
|
FROM rtti_base_classes b
|
|
JOIN rtti_locators c ON c.class_hierarchy = b.class_hierarchy
|
|
JOIN rtti_type_descriptors dtd ON dtd.address = c.type_descriptor
|
|
WHERE b.base_index > 0;
|
|
";
|
|
|
|
/// Virtual methods per class, resolved through the RTTI-named vtable. The
|
|
/// authoritative counterpart to querying `methods` by an `ANON_Class_*` name.
|
|
const V_CLASS_METHODS: &str = "
|
|
CREATE OR REPLACE VIEW v_class_methods AS
|
|
SELECT
|
|
td.demangled_name AS class_name,
|
|
c.subobject_offset AS subobject_offset,
|
|
v.address AS vtable_address,
|
|
m.slot AS slot,
|
|
m.function_address AS method_addr,
|
|
f.name AS method_name,
|
|
f.has_eh AS method_has_eh
|
|
FROM rtti_locators c
|
|
JOIN rtti_type_descriptors td ON td.address = c.type_descriptor
|
|
JOIN vtables v ON v.address = c.vtable_address
|
|
JOIN methods m ON m.vtable_address = v.address
|
|
LEFT JOIN functions f ON f.address = m.function_address;
|
|
";
|
|
|
|
/// Which function references which string literal. The single most useful
|
|
/// orientation query in a stripped binary: it is how you find the code behind
|
|
/// a message you can see on screen.
|
|
const V_FUNCTION_STRINGS: &str = "
|
|
CREATE OR REPLACE VIEW v_function_strings AS
|
|
SELECT
|
|
x.source_func AS function_addr,
|
|
f.name AS function_name,
|
|
x.source AS reference_pc,
|
|
x.kind AS reference_kind,
|
|
s.address AS string_addr,
|
|
s.encoding AS encoding,
|
|
s.content AS content
|
|
FROM xrefs x
|
|
JOIN strings s ON s.address = x.target
|
|
LEFT JOIN functions f ON f.address = x.source_func
|
|
WHERE x.kind IN ('ref', 'read');
|
|
";
|