Apply caller-selected analyzer opt-outs before Ghidra analysis, reject unknown or ineffective names, and commit raw/header imports to the same analyzer profile. Use supported x86 raw compiler specs and cover the real analyzer/readback and raw workflows. Adopts the original implementation from #1800 with validated corrections. Refs #1783, #1836. Co-authored-by: akushonkamen <74654766+akushonkamen@users.noreply.github.com> Co-authored-by: N0zoM1z0 <161784452+N0zoM1z0@users.noreply.github.com> Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
144 lines
4.5 KiB
JavaScript
144 lines
4.5 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import assert from "node:assert/strict";
|
|
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { isAbsolute, join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import {
|
|
spawnOwnedProviderProcess,
|
|
ProviderProcessSupervisor,
|
|
} from "../../../dist/process/ProviderProcess.js";
|
|
import { cleanupOwnedProcessGroup } from "../../../dist/process/ProcessOwnership.js";
|
|
import { createVerifierRun } from "../../lib/verifier-run.mjs";
|
|
|
|
const installation = process.env.GHIDRA_INSTALL_DIR;
|
|
assert.ok(
|
|
installation && isAbsolute(installation),
|
|
"Set GHIDRA_INSTALL_DIR to a local Ghidra installation",
|
|
);
|
|
assert.notEqual(
|
|
process.platform,
|
|
"win32",
|
|
"This direct headless regression requires a POSIX host; Windows uses its controlled provider lane",
|
|
);
|
|
const workspace = await mkdtemp(join(tmpdir(), "rea-ghidra-seed-regression-"));
|
|
const run = createVerifierRun();
|
|
let supervisor;
|
|
try {
|
|
const target = join(workspace, "seed-functions.bin");
|
|
const seeds = join(workspace, "seeds.tsv");
|
|
// Falling through from the lower entry reaches the higher entry. Creating
|
|
// the higher function first is what keeps the two function bodies separate.
|
|
await writeFile(target, Buffer.from([0x90, 0x90, 0xc3]));
|
|
await writeFile(
|
|
seeds,
|
|
[
|
|
"0x0\tfunction\tseed_low",
|
|
"0x100000000\tfunction\tout_of_range",
|
|
"0x1\tfunction\tseed_high",
|
|
"0x1000\tlabel\tseed_global",
|
|
"0x1000\tcode",
|
|
"",
|
|
].join("\n"),
|
|
);
|
|
const output = await runHeadless(target, "seed-regression", [
|
|
"-postScript",
|
|
"ReaSeedRegressionProbe.java",
|
|
seeds,
|
|
]);
|
|
// Headless can exit zero after a script fails, so require the probe's marker.
|
|
assert.ok(output.includes("REA_SEED_REGRESSION_OK "), output);
|
|
|
|
const analyzer = "Decompiler Switch Analysis";
|
|
const disabled = await runHeadless(target, "analyzer-regression-valid", [
|
|
"-preScript",
|
|
"ReaGhidraAnalysisOptions.java",
|
|
analyzer,
|
|
"-postScript",
|
|
"ReaAnalyzerOptionsRegressionProbe.java",
|
|
analyzer,
|
|
]);
|
|
assert.ok(
|
|
disabled.includes(`REA_ANALYZER_OPTIONS_DISABLED_OK ${analyzer}`),
|
|
disabled,
|
|
);
|
|
|
|
const unknownAnalyzer = "REA Unknown Analyzer";
|
|
const rejected = await runHeadless(target, "analyzer-regression-unknown", [
|
|
"-preScript",
|
|
"ReaGhidraAnalysisOptions.java",
|
|
`${analyzer},${unknownAnalyzer}`,
|
|
"-postScript",
|
|
"ReaAnalyzerOptionsRegressionProbe.java",
|
|
analyzer,
|
|
]);
|
|
assert.ok(
|
|
rejected.includes(
|
|
`Unknown Ghidra analyzer for this program: ${unknownAnalyzer}`,
|
|
),
|
|
rejected,
|
|
);
|
|
assert.ok(
|
|
rejected.includes("Processing aborted as a result of pre-script."),
|
|
rejected,
|
|
);
|
|
assert.ok(!rejected.includes("REA_ANALYZER_OPTIONS_DISABLED_OK"), rejected);
|
|
} finally {
|
|
if (supervisor !== undefined) {
|
|
const stopped = await supervisor.stop();
|
|
assert.notEqual(stopped.status, "incomplete", JSON.stringify(stopped));
|
|
supervisor.dispose();
|
|
}
|
|
await rm(workspace, { recursive: true, force: true });
|
|
}
|
|
console.log(
|
|
"Real Ghidra pre-script regression passed: descending function order, out-of-range seed, BSS label, BSS code rejection, analyzer option readback, unknown-analyzer abort before post-scripts, and owned-process cleanup.",
|
|
);
|
|
|
|
async function runHeadless(target, project, scriptArguments) {
|
|
const launch = await spawnOwnedProviderProcess({
|
|
command: join(installation, "support", "analyzeHeadless"),
|
|
arguments: [
|
|
workspace,
|
|
project,
|
|
"-import",
|
|
target,
|
|
"-loader",
|
|
"BinaryLoader",
|
|
"-processor",
|
|
"x86:LE:32:default",
|
|
"-noanalysis",
|
|
"-readOnly",
|
|
"-deleteProject",
|
|
"-scriptPath",
|
|
[
|
|
fileURLToPath(new URL("../../../bridge/ghidra", import.meta.url)),
|
|
fileURLToPath(
|
|
new URL("../../../tests/conformance/ghidra", import.meta.url),
|
|
),
|
|
].join(";"),
|
|
...scriptArguments,
|
|
],
|
|
runId: run.run_id,
|
|
expectedCommand: null,
|
|
});
|
|
supervisor = new ProviderProcessSupervisor({
|
|
...launch,
|
|
ownsProcessLifetime: true,
|
|
cleanup: () => cleanupOwnedProcessGroup(launch.ownership),
|
|
});
|
|
assert.ok(
|
|
await supervisor.waitForExit(180_000),
|
|
"Ghidra seed regression exceeded its startup deadline",
|
|
);
|
|
const snapshot = supervisor.snapshot();
|
|
const output = `${snapshot.stdout.text}\n${snapshot.stderr.text}`;
|
|
assert.equal(snapshot.exitCode, 0, output);
|
|
const stopped = await supervisor.stop();
|
|
assert.notEqual(stopped.status, "incomplete", JSON.stringify(stopped));
|
|
supervisor.dispose();
|
|
supervisor = undefined;
|
|
return output;
|
|
}
|