mirror of
https://github.com/morluto/rea
synced 2026-10-26 06:16:17 -04:00
fix(ghidra): preserve decoded return flow evidence
This commit is contained in:
@@ -15,6 +15,7 @@ import { verifyGhidraTargetAdmission } from "./real-ghidra-target-admission.mjs"
|
||||
import { verifyGhidraLargeResults } from "./real-ghidra-large-results.mjs";
|
||||
import { verifyGhidraEntryAliases } from "./real-ghidra-entry-aliases.mjs";
|
||||
import { verifyGhidraNamespaceAnnotations } from "./real-ghidra-namespace-annotations.mjs";
|
||||
import { verifyGhidraInstructionFlow } from "./real-ghidra-instruction-flow.mjs";
|
||||
|
||||
/** Probe real Ghidra location, annotation and error contracts through public adapters. */
|
||||
export async function verifyGhidraBoundaries(
|
||||
@@ -157,6 +158,7 @@ export async function verifyGhidraBoundaries(
|
||||
/Annotations require a local function entry/u,
|
||||
);
|
||||
const names = await call("list_names");
|
||||
const instructionFlow = await verifyGhidraInstructionFlow(call, names, cli);
|
||||
const leaf = names.find((item) =>
|
||||
item.value.endsWith("rea_ghidra_inventory_leaf"),
|
||||
);
|
||||
@@ -766,6 +768,7 @@ export async function verifyGhidraBoundaries(
|
||||
missing_and_nonregular_source_rejected: true,
|
||||
imported_source_identity_retained: true,
|
||||
equivalent_instruction_address_spellings: true,
|
||||
instruction_flow: instructionFlow,
|
||||
qualified_annotation_name_roundtrip: true,
|
||||
imported_entry_alias_selection: true,
|
||||
oversized_result_retention_and_complete_export: true,
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
/** Verify decoded return evidence and distinct non-return flows on owned fixtures. */
|
||||
export async function verifyGhidraInstructionFlow(call, names, cli) {
|
||||
const leaf = names.find((item) =>
|
||||
item.value.endsWith("rea_ghidra_inventory_leaf"),
|
||||
);
|
||||
assert.ok(leaf, "Source fixture lacks its returning leaf");
|
||||
const leafInstructions = await inspectFunction(call, leaf.address);
|
||||
const returned = leafInstructions.find(
|
||||
(instruction) => instruction.flow.kind === "return",
|
||||
);
|
||||
assert.ok(returned, "Returning source leaf lacks decoded RETURN evidence");
|
||||
assertReturnEvidence(returned);
|
||||
assert.equal(
|
||||
(await call("resolve_native_call_targets", { address: returned.address }))
|
||||
.status,
|
||||
"not-call",
|
||||
);
|
||||
assert.deepEqual(
|
||||
await cli("inspect-native-instruction", returned.address),
|
||||
returned,
|
||||
);
|
||||
|
||||
const controls = [
|
||||
["rea_ghidra_flow_return", "RET", "c3"],
|
||||
["rea_ghidra_flow_cleanup_return", "RET", "c21000"],
|
||||
["rea_ghidra_flow_shared_tail", "JMP", null],
|
||||
["rea_ghidra_flow_trap", "UD2", "0f0b"],
|
||||
];
|
||||
let checked = 0;
|
||||
for (const [symbol, mnemonic, bytes] of controls) {
|
||||
const procedure = names.find((item) => item.value === symbol);
|
||||
if (procedure === undefined) continue;
|
||||
const before = await call("resolve_containing_procedure", {
|
||||
address: procedure.address,
|
||||
});
|
||||
const instructions = await inspectFunction(call, procedure.address);
|
||||
const instruction = instructions.find((item) => item.mnemonic === mnemonic);
|
||||
assert.ok(instruction, `Source fixture lacks ${symbol}'s ${mnemonic}`);
|
||||
if (bytes !== null) assert.equal(instruction.bytes, bytes);
|
||||
if (mnemonic === "RET") {
|
||||
assertReturnEvidence(instruction);
|
||||
assert.equal(instruction.flow.kind, "return");
|
||||
if (bytes === "c21000")
|
||||
assert.equal(instruction.operands[0]?.raw, "0x10");
|
||||
} else {
|
||||
assert.notEqual(instruction.flow.kind, "return");
|
||||
assert.ok(
|
||||
instruction.flow.classification_evidence.some(
|
||||
(item) => item.source === "ghidra-listing-flow-type",
|
||||
),
|
||||
);
|
||||
}
|
||||
assert.deepEqual(
|
||||
await call("resolve_containing_procedure", {
|
||||
address: procedure.address,
|
||||
}),
|
||||
before,
|
||||
);
|
||||
checked++;
|
||||
}
|
||||
const unavailable = await call("inspect_native_instruction", {
|
||||
address: "0x0",
|
||||
});
|
||||
assert.equal(unavailable.status, "outside-memory");
|
||||
assert.equal(unavailable.flow.kind, "unavailable");
|
||||
assert.deepEqual(unavailable.flow.classification_evidence, []);
|
||||
return {
|
||||
return_instruction: returned.address,
|
||||
decoded_pcode_return: true,
|
||||
return_cli_mcp_parity: true,
|
||||
explicit_x86_elf_controls: checked,
|
||||
explicit_control_scope:
|
||||
checked === 0
|
||||
? "Not present on this host-native fixture format/architecture"
|
||||
: "x86-64 ELF",
|
||||
generic_unknown_preserved: true,
|
||||
};
|
||||
}
|
||||
|
||||
async function inspectFunction(call, procedure) {
|
||||
const listing = await call("read_function_instructions", { procedure });
|
||||
const instructions = [];
|
||||
for (const line of listing.instructions) {
|
||||
const delimiter = line.indexOf(": ");
|
||||
assert.ok(delimiter > 0, `Malformed provider instruction listing: ${line}`);
|
||||
instructions.push(
|
||||
await call("inspect_native_instruction", {
|
||||
address: line.slice(0, delimiter),
|
||||
}),
|
||||
);
|
||||
}
|
||||
return instructions;
|
||||
}
|
||||
|
||||
function assertReturnEvidence(instruction) {
|
||||
assert.equal(instruction.status, "decoded");
|
||||
assert.ok(
|
||||
instruction.flow.classification_evidence.some(
|
||||
(item) =>
|
||||
item.source === "ghidra-instruction-pcode" && item.value === "RETURN",
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
!instruction.limitations.some((text) =>
|
||||
text.includes("not classified as a return"),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -92,8 +92,7 @@ export async function verifyDebugFunctionOperations(
|
||||
if (
|
||||
instruction.status !== "decoded" ||
|
||||
instruction.length < 1 ||
|
||||
instruction.bytes.length !== instruction.length * 2 ||
|
||||
instruction.operands.length === 0
|
||||
instruction.bytes.length !== instruction.length * 2
|
||||
)
|
||||
throw new Error("Structured native instruction facts drifted");
|
||||
const directSite = outgoing.references.find(
|
||||
|
||||
Reference in New Issue
Block a user