mirror of
https://github.com/morluto/rea.git
synced 2026-10-12 02:19:24 +00:00
* fix(ghidra): admit the 12.1 line and the install's JDK range The provider treated the verified Ghidra 12.1.4 build and JDK 21 as the only legal installation, so a working 12.1.x package or a newer JDK was rejected before analysis started. Admission now follows the 12.1 release line and application.java.min/max. A compatible build other than 12.1.4 keeps its observed version and records that the bridge was verified against 12.1.4. Fixes #821 * fix(ghidra): stop pinning the NativeAOT build to one version string The build script still rejected every Ghidra release except the literal 12.1.4 and every javac except major 21. It now accepts the 12.1 line and javac 21 or newer, compiles with --release 21, and records the observed Ghidra version. The JADX engine version uses the audited release constant. * fix(ghidra): report unverified builds on analysis results Session open uses health only as a startup gate and discards that execution, so the unverified-build limitation never reached CLI or MCP results. Attach it to every Ghidra execution for that install. --------- Co-authored-by: Cursor Agent <[email protected]>
179 lines
5.5 KiB
JavaScript
179 lines
5.5 KiB
JavaScript
#!/usr/bin/env node
|
|
import { spawnSync } from "node:child_process";
|
|
import { createHash } from "node:crypto";
|
|
import {
|
|
mkdir,
|
|
mkdtemp,
|
|
readdir,
|
|
readFile,
|
|
rm,
|
|
writeFile,
|
|
} from "node:fs/promises";
|
|
import { dirname, join, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
const source = resolve(
|
|
process.env.REA_NATIVEAOT_SOURCE ??
|
|
join(root, "third_party/ghidra-nativeaot"),
|
|
);
|
|
const install = process.env.GHIDRA_INSTALL_DIR;
|
|
if (!install)
|
|
throw new Error(
|
|
"NativeAOT build lane requires existing GHIDRA_INSTALL_DIR (Ghidra 12.1.x).",
|
|
);
|
|
const properties = await readFile(
|
|
join(install, "Ghidra/application.properties"),
|
|
"utf8",
|
|
);
|
|
const ghidraVersion = propertyValue(properties, "application.version");
|
|
if (!ghidraReleaseLineAccepted(ghidraVersion))
|
|
throw new Error(
|
|
`NativeAOT build lane accepts Ghidra 12.1.x (verified with 12.1.4). Observed application.version=${ghidraVersion ?? "missing"}.`,
|
|
);
|
|
const output = resolve(
|
|
process.env.REA_NATIVEAOT_BUILD_ROOT ??
|
|
join(root, "_reference/nativeaot-integration/extension"),
|
|
);
|
|
const java = process.env.JAVA_HOME ? join(process.env.JAVA_HOME, "bin") : null;
|
|
const run = (command, args) => {
|
|
const result = spawnSync(command, args, {
|
|
cwd: root,
|
|
encoding: "utf8",
|
|
timeout: 120_000,
|
|
maxBuffer: 4 * 1024 * 1024,
|
|
});
|
|
if (result.error || result.status !== 0)
|
|
throw new Error(
|
|
`NativeAOT build prerequisite/action ${command} failed: ${result.error?.message ?? ""}\n${result.stdout ?? ""}${result.stderr ?? ""}`,
|
|
);
|
|
return result.stdout.trim();
|
|
};
|
|
const javac = java ? join(java, "javac") : "javac";
|
|
const javacBanner = capture(javac, ["-version"]);
|
|
const javacMajor = Number(/^javac (\d+)/mu.exec(javacBanner)?.[1]);
|
|
if (!Number.isInteger(javacMajor) || javacMajor < 21)
|
|
throw new Error(
|
|
`NativeAOT build lane requires javac 21 or newer and compiles with --release 21. Observed: ${javacBanner || "no version"}. REA does not install or upgrade Java.`,
|
|
);
|
|
const revision = run("git", ["-C", source, "rev-parse", "HEAD"]);
|
|
if (
|
|
revision !== "effeb734fc570c32650f88b159608979dc7b423e" ||
|
|
run("git", ["-C", source, "status", "--porcelain"])
|
|
)
|
|
throw new Error(
|
|
"NativeAOT build requires the clean pinned upstream source; initialize third_party/ghidra-nativeaot.",
|
|
);
|
|
const files = async (path, extension) => {
|
|
const result = [];
|
|
for (const entry of await readdir(path, { withFileTypes: true })) {
|
|
const child = join(path, entry.name);
|
|
if (entry.isDirectory()) result.push(...(await files(child, extension)));
|
|
else if (entry.isFile() && child.endsWith(extension)) result.push(child);
|
|
}
|
|
return result.sort();
|
|
};
|
|
await mkdir(output, { recursive: true });
|
|
const classes = await mkdtemp(join(output, "classes-"));
|
|
try {
|
|
const sources = [
|
|
...(await files(join(source, "src/main/java"), ".java")),
|
|
...(await files(join(root, "bridge/ghidra/extensions/nativeaot"), ".java")),
|
|
];
|
|
const jars = await files(join(install, "Ghidra"), ".jar");
|
|
const args = [
|
|
"-proc:none",
|
|
"-cp",
|
|
jars.join(process.platform === "win32" ? ";" : ":"),
|
|
"-d",
|
|
classes,
|
|
...sources,
|
|
];
|
|
await writeFile(
|
|
join(output, "javac.args"),
|
|
args
|
|
.map((s) => `"${s.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`)
|
|
.join("\n"),
|
|
);
|
|
run(javac, [
|
|
"-J-Xmx512m",
|
|
"-J-XX:ActiveProcessorCount=1",
|
|
"--release",
|
|
"21",
|
|
`@${join(output, "javac.args")}`,
|
|
]);
|
|
await writeFile(
|
|
join(classes, "LICENSE-ghidra-nativeaot.md"),
|
|
await readFile(join(source, "LICENSE.md")),
|
|
);
|
|
const jar = join(output, "rea-ghidra-nativeaot.jar");
|
|
run(java ? join(java, "jar") : "jar", [
|
|
"--create",
|
|
"--file",
|
|
jar,
|
|
"--date=2025-01-01T00:00:00Z",
|
|
"-C",
|
|
classes,
|
|
".",
|
|
]);
|
|
const sha256 = createHash("sha256")
|
|
.update(await readFile(jar))
|
|
.digest("hex");
|
|
await writeFile(
|
|
join(output, "build.json"),
|
|
JSON.stringify(
|
|
{
|
|
jar,
|
|
sha256,
|
|
upstream_revision: revision,
|
|
ghidra_version: ghidraVersion,
|
|
integration_api: 1,
|
|
},
|
|
null,
|
|
2,
|
|
) + "\n",
|
|
);
|
|
console.log(
|
|
JSON.stringify({
|
|
jar,
|
|
sha256,
|
|
upstream_revision: revision,
|
|
ghidra_version: ghidraVersion,
|
|
integration_api: 1,
|
|
}),
|
|
);
|
|
} finally {
|
|
await rm(classes, { recursive: true, force: true });
|
|
}
|
|
|
|
/** Read one application.properties value. Empty and missing values stay unknown. */
|
|
function propertyValue(content, name) {
|
|
for (const line of content.split(/\r?\n/u)) {
|
|
const separator = line.indexOf("=");
|
|
if (separator < 0 || line.slice(0, separator).trim() !== name) continue;
|
|
const value = line.slice(separator + 1).trim();
|
|
return value.length === 0 ? null : value;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/** Same 12.1 line as GhidraInstallationPolicy, including a distro suffix. */
|
|
function ghidraReleaseLineAccepted(raw) {
|
|
const match = /^(\d+)\.(\d+)(?:\.(\d+))?([-+].*)?$/u.exec(raw ?? "");
|
|
return match !== null && Number(match[1]) === 12 && Number(match[2]) === 1;
|
|
}
|
|
|
|
function capture(command, args) {
|
|
const result = spawnSync(command, args, {
|
|
cwd: root,
|
|
encoding: "utf8",
|
|
timeout: 120_000,
|
|
maxBuffer: 4 * 1024 * 1024,
|
|
});
|
|
if (result.error || result.status !== 0)
|
|
throw new Error(
|
|
`NativeAOT build prerequisite/action ${command} failed: ${result.error?.message ?? ""}\n${result.stdout ?? ""}${result.stderr ?? ""}`,
|
|
);
|
|
return `${result.stdout ?? ""}\n${result.stderr ?? ""}`.trim();
|
|
}
|