mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 19:23:24 +00:00
fix(verify): refuse the cross-architecture Ghidra lanes before allocating when their toolchain is missing (#11117)
* fix(verify): refuse the cross-architecture Ghidra lanes before allocating when their toolchain is missing * fix(verify): probe the fixture toolchain through the owned command runner and check the tools' targets
This commit is contained in:
1 parent
fe05a4c56d
commit
14a5f871ae
19 files changed
+365
-15
No files matched your search
@@ -34,6 +34,7 @@ import {
|
||||
} from "./lib/verifier-run.mjs";
|
||||
import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs";
|
||||
import { VerifierFixture } from "./lib/verifier-fixture.mjs";
|
||||
import { requireLaneToolchain } from "../tests/conformance/ghidra/fixture-toolchain.mjs";
|
||||
|
||||
// Optional cross-target lane. No tool acquisition and no target execution.
|
||||
if (process.argv.length !== 2)
|
||||
@@ -59,6 +60,8 @@ if (installation.status !== "available")
|
||||
const execute = promisify(execFile);
|
||||
const entrypoint = fileURLToPath(new URL("./rea.mjs", import.meta.url));
|
||||
const run = createVerifierRun();
|
||||
// Refuse a host without the fixture toolchain before allocating anything.
|
||||
await requireLaneToolchain("dol");
|
||||
const workspaceFixture = await VerifierFixture.open({
|
||||
prefix: "rea-dol-proof-",
|
||||
// Ghidra children use the workspace as TMPDIR; a plain root admits them on every host.
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
import { requireMcpOperationResult } from "./lib/mcp-verifier-results.mjs";
|
||||
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
||||
import { VerifierFixture } from "./lib/verifier-fixture.mjs";
|
||||
import { requireLaneToolchain } from "../tests/conformance/ghidra/fixture-toolchain.mjs";
|
||||
|
||||
// Optional cross-target lane for ET_REL objects. No tool acquisition, no
|
||||
// linking and no target execution.
|
||||
@@ -48,6 +49,8 @@ if (installation.status !== "available")
|
||||
const execute = promisify(execFile);
|
||||
const entrypoint = fileURLToPath(new URL("./rea.mjs", import.meta.url));
|
||||
const run = createVerifierRun();
|
||||
// Refuse a host without the fixture toolchain before allocating anything.
|
||||
await requireLaneToolchain("mips-relocatable");
|
||||
const workspaceFixture = await VerifierFixture.open({
|
||||
prefix: "rea-mips-rel-proof-",
|
||||
// Ghidra children use the workspace as TMPDIR; a plain root admits them on every host.
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
} from "./lib/mcp-verifier-results.mjs";
|
||||
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
||||
import { VerifierFixture } from "./lib/verifier-fixture.mjs";
|
||||
import { requireLaneToolchain } from "../tests/conformance/ghidra/fixture-toolchain.mjs";
|
||||
|
||||
if (
|
||||
process.env.GHIDRA_INSTALL_DIR === undefined ||
|
||||
@@ -26,6 +27,8 @@ if (
|
||||
);
|
||||
const execute = promisify(execFile);
|
||||
const run = createVerifierRun();
|
||||
// Refuse a host without the fixture toolchain before allocating anything.
|
||||
await requireLaneToolchain("mips-shared");
|
||||
const workspaceFixture = await VerifierFixture.open({
|
||||
prefix: "rea-mips-shared-proof-",
|
||||
// Ghidra children use the workspace as TMPDIR; a plain root admits them on every host.
|
||||
|
||||
@@ -46,6 +46,7 @@ import {
|
||||
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
||||
import { runOwnedCommand } from "../dist/process/OwnedCommand.js";
|
||||
import { VerifierFixture } from "./lib/verifier-fixture.mjs";
|
||||
import { requireLaneToolchain } from "../tests/conformance/ghidra/fixture-toolchain.mjs";
|
||||
|
||||
// Optional cross-target lane. No tool acquisition and no target execution.
|
||||
const mode = process.argv[2];
|
||||
@@ -76,6 +77,8 @@ if (installation.status !== "available")
|
||||
const execute = promisify(execFile);
|
||||
const entrypoint = fileURLToPath(new URL("./rea.mjs", import.meta.url));
|
||||
const run = createVerifierRun();
|
||||
// Refuse a host without the fixture toolchain before allocating anything.
|
||||
await requireLaneToolchain(psp ? "psp" : "mips");
|
||||
const workspaceFixture = await VerifierFixture.open({
|
||||
prefix: "rea-mips-proof-",
|
||||
// Ghidra children use the workspace as TMPDIR; a plain root admits them on every host.
|
||||
|
||||
@@ -23,6 +23,7 @@ import { buildPowerPcFixture } from "../tests/conformance/ghidra/ppc-fixture.mjs
|
||||
import { requireMcpOperationResult } from "./lib/mcp-verifier-results.mjs";
|
||||
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
||||
import { VerifierFixture } from "./lib/verifier-fixture.mjs";
|
||||
import { requireLaneToolchain } from "../tests/conformance/ghidra/fixture-toolchain.mjs";
|
||||
|
||||
// Optional cross-target lane. No tool acquisition and no target execution.
|
||||
if (process.argv.length !== 2)
|
||||
@@ -47,6 +48,8 @@ if (installation.status !== "available")
|
||||
const execute = promisify(execFile);
|
||||
const entrypoint = fileURLToPath(new URL("./rea.mjs", import.meta.url));
|
||||
const run = createVerifierRun();
|
||||
// Refuse a host without the fixture toolchain before allocating anything.
|
||||
await requireLaneToolchain("ppc");
|
||||
const workspaceFixture = await VerifierFixture.open({
|
||||
prefix: "rea-ppc-proof-",
|
||||
// Ghidra children use the workspace as TMPDIR; a plain root admits them on every host.
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
} from "./lib/mcp-verifier-results.mjs";
|
||||
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
||||
import { VerifierFixture } from "./lib/verifier-fixture.mjs";
|
||||
import { requireLaneToolchain } from "../tests/conformance/ghidra/fixture-toolchain.mjs";
|
||||
|
||||
if (process.argv.length !== 2)
|
||||
throw new Error("Usage: node scripts/verify-real-ghidra-riscv.mjs");
|
||||
@@ -60,6 +61,8 @@ assert.equal(installation.providerVersion, "12.1.4");
|
||||
const execute = promisify(execFile);
|
||||
const entrypoint = fileURLToPath(new URL("./rea.mjs", import.meta.url));
|
||||
const run = createVerifierRun();
|
||||
// Refuse a host without the fixture toolchain before allocating anything.
|
||||
await requireLaneToolchain("riscv");
|
||||
const workspaceFixture = await VerifierFixture.open({
|
||||
prefix: "rea-riscv-proof-",
|
||||
// Ghidra children use the workspace as TMPDIR; a plain root admits them on every host.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { execFile } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
import { FIXTURE_TOOLS, fixtureCommand } from "./fixture-toolchain.mjs";
|
||||
|
||||
const execute = promisify(execFile);
|
||||
// Stock language is the default; the Gekko override is checked only when installed.
|
||||
@@ -104,7 +105,7 @@ export function parseReadelfDolSource(text) {
|
||||
|
||||
/** Independent GNU readelf reader for the ELF the DOL was converted from. */
|
||||
export async function inspectReadelfDolSource(elfPath) {
|
||||
const command = process.env.REA_PPC_READELF ?? "readelf";
|
||||
const command = fixtureCommand(FIXTURE_TOOLS.ppcReadelf);
|
||||
const options = {
|
||||
env: { ...process.env, LC_ALL: "C" },
|
||||
timeout: 10000,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { readFile, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
import { FIXTURE_TOOLS, fixtureCommand } from "./fixture-toolchain.mjs";
|
||||
|
||||
const execute = promisify(execFile);
|
||||
|
||||
@@ -103,7 +104,7 @@ export function convertElfToDol(elf, { bss }) {
|
||||
* The fixture is never executed.
|
||||
*/
|
||||
export async function buildGameCubeDolFixture(directory) {
|
||||
const compiler = process.env.REA_PPC_CLANG ?? "clang";
|
||||
const compiler = fixtureCommand(FIXTURE_TOOLS.ppcClang);
|
||||
const target = "powerpc-unknown-linux-gnu";
|
||||
const here = (path) => fileURLToPath(new URL(path, import.meta.url));
|
||||
const sources = [
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/** A host command a fixture runs, and the variable that selects another one. */
|
||||
export interface FixtureTool {
|
||||
readonly variable?: string;
|
||||
readonly fallback: string;
|
||||
}
|
||||
|
||||
export declare const FIXTURE_TOOLS: Readonly<Record<string, FixtureTool>>;
|
||||
|
||||
/** The command a fixture runs for `tool`. */
|
||||
export declare function fixtureCommand(tool: FixtureTool): string;
|
||||
|
||||
/**
|
||||
* Refuse a lane before it allocates a workspace when this host lacks a
|
||||
* command its fixtures run, naming each missing capability.
|
||||
*/
|
||||
export declare function requireLaneToolchain(lane: string): Promise<void>;
|
||||
@@ -0,0 +1,217 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { access } from "node:fs/promises";
|
||||
import { devNull } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { importVerifierSource } from "../../../scripts/lib/verifier-source-loader.mjs";
|
||||
|
||||
/**
|
||||
* The host commands the cross-architecture Ghidra fixtures build and inspect
|
||||
* with. Each names the environment variable that selects it and the command
|
||||
* used when that variable is unset; fixtures and checks resolve commands only
|
||||
* through `fixtureCommand`, so the prerequisite check probes what they run.
|
||||
*/
|
||||
export const FIXTURE_TOOLS = Object.freeze({
|
||||
mipsClang: { variable: "REA_MIPS_CLANG", fallback: "clang" },
|
||||
mipsAssembler: { variable: "REA_MIPS_AS", fallback: "mips-linux-gnu-as" },
|
||||
mipsLinker: { variable: "REA_MIPS_LD", fallback: "mips-linux-gnu-ld" },
|
||||
mipsReadelf: { variable: "REA_MIPS_READELF", fallback: "readelf" },
|
||||
ppcClang: { variable: "REA_PPC_CLANG", fallback: "clang" },
|
||||
ppcReadelf: { variable: "REA_PPC_READELF", fallback: "readelf" },
|
||||
riscvClang: { variable: "REA_RISCV_CLANG", fallback: "clang" },
|
||||
riscvLinker: { variable: "REA_RISCV_LLD", fallback: "ld.lld" },
|
||||
riscvReadelf: { variable: "REA_RISCV_READELF", fallback: "llvm-readelf" },
|
||||
riscvObjdump: { variable: "REA_RISCV_OBJDUMP", fallback: "llvm-objdump" },
|
||||
// The PSPSDK build runs these by name; no variable selects them.
|
||||
make: { fallback: "make" },
|
||||
pspConfig: { fallback: "psp-config" },
|
||||
pspCompiler: { fallback: "psp-gcc" },
|
||||
pspReadelf: { fallback: "psp-readelf" },
|
||||
pspObjdump: { fallback: "psp-objdump" },
|
||||
});
|
||||
|
||||
/** The command a fixture runs for `tool`. */
|
||||
export const fixtureCommand = (tool) =>
|
||||
(tool.variable === undefined ? undefined : process.env[tool.variable]) ??
|
||||
tool.fallback;
|
||||
|
||||
const firstLine = (text) =>
|
||||
String(text ?? "")
|
||||
.split("\n")
|
||||
.find((line) => line.trim() !== "")
|
||||
?.trim();
|
||||
|
||||
/**
|
||||
* A command that must answer `arguments` (default `--version`), with output
|
||||
* matching `banner` when the fixture reads a particular tool's output.
|
||||
*/
|
||||
const runs = (tool, banner, args = ["--version"]) => ({
|
||||
tool,
|
||||
need: banner === undefined ? "run" : `report ${String(banner)}`,
|
||||
arguments: args,
|
||||
accepts: (stdout) =>
|
||||
banner === undefined || banner.test(stdout)
|
||||
? undefined
|
||||
: `${args.join(" ")} reported ${JSON.stringify(firstLine(stdout) ?? "")}`,
|
||||
});
|
||||
|
||||
/**
|
||||
* A Clang that generates code for `target`; with `lld` it also links it with
|
||||
* ld.lld, which prints its version and exits without writing an output.
|
||||
*/
|
||||
const targets = (tool, target, { lld = false } = {}) => [
|
||||
{
|
||||
tool,
|
||||
need: lld
|
||||
? `generate and link ${target} code with -fuse-ld=lld`
|
||||
: `generate ${target} code`,
|
||||
arguments: [
|
||||
`--target=${target}`,
|
||||
...(lld ? ["-fuse-ld=lld", "-nostdlib", "-Wl,--version"] : ["-c"]),
|
||||
"-x",
|
||||
"c",
|
||||
devNull,
|
||||
"-o",
|
||||
devNull,
|
||||
],
|
||||
accepts: () => undefined,
|
||||
},
|
||||
];
|
||||
|
||||
/** The PSPSDK root whose build rules the fixture Makefile includes. */
|
||||
const pspsdk = {
|
||||
tool: FIXTURE_TOOLS.pspConfig,
|
||||
need: "report a PSPSDK path with lib/build.mak",
|
||||
arguments: ["--pspsdk-path"],
|
||||
accepts: async (stdout) => {
|
||||
const path = firstLine(stdout);
|
||||
if (path === undefined) return "--pspsdk-path reported nothing";
|
||||
try {
|
||||
await access(join(path, "lib", "build.mak"));
|
||||
return undefined;
|
||||
} catch {
|
||||
return `--pspsdk-path reported ${JSON.stringify(path)}, which has no lib/build.mak`;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const gnuReadelf = /^GNU readelf\b/u;
|
||||
// The RISC-V fixture parses LLVM's attribute layout and disassembles one
|
||||
// symbol with --disassemble-symbols, which GNU binutils do not provide.
|
||||
const llvm = /\bLLVM\b/u;
|
||||
const tools = FIXTURE_TOOLS;
|
||||
|
||||
/** What each lane's fixtures and independent readers run. */
|
||||
export const LANE_TOOLCHAINS = Object.freeze({
|
||||
mips: [
|
||||
...targets(tools.mipsClang, "mipsel-linux-gnu", { lld: true }),
|
||||
...targets(tools.mipsClang, "mips-linux-gnu", { lld: true }),
|
||||
runs(tools.mipsReadelf, gnuReadelf),
|
||||
],
|
||||
psp: [
|
||||
runs(tools.make),
|
||||
pspsdk,
|
||||
runs(tools.pspCompiler),
|
||||
runs(tools.pspReadelf),
|
||||
runs(tools.pspObjdump),
|
||||
],
|
||||
"mips-relocatable": [
|
||||
...targets(tools.mipsClang, "mipsel-linux-gnu"),
|
||||
...targets(tools.mipsClang, "mips-linux-gnu"),
|
||||
runs(tools.mipsReadelf, gnuReadelf),
|
||||
],
|
||||
"mips-shared": [
|
||||
// GNU tools name their configured target; a host-native assembler or
|
||||
// linker answers --version but cannot build MIPS16 code.
|
||||
runs(tools.mipsAssembler, /GNU assembler[\s\S]*target of .mips/u),
|
||||
runs(tools.mipsLinker, /GNU ld[\s\S]*\belf32\w*mip/u, ["-V"]),
|
||||
runs(tools.mipsReadelf),
|
||||
],
|
||||
ppc: [
|
||||
...targets(tools.ppcClang, "powerpc-unknown-linux-gnu", { lld: true }),
|
||||
runs(tools.ppcReadelf, gnuReadelf),
|
||||
],
|
||||
dol: [
|
||||
...targets(tools.ppcClang, "powerpc-unknown-linux-gnu", { lld: true }),
|
||||
runs(tools.ppcReadelf, gnuReadelf),
|
||||
],
|
||||
riscv: [
|
||||
...targets(tools.riscvClang, "riscv32-unknown-elf"),
|
||||
...targets(tools.riscvClang, "riscv64-unknown-elf"),
|
||||
runs(tools.riscvLinker),
|
||||
runs(tools.riscvReadelf, llvm),
|
||||
runs(tools.riscvObjdump, llvm),
|
||||
],
|
||||
});
|
||||
|
||||
let ownedCommand;
|
||||
/** Probes run through the shared owned-command runner, which owns their process group. */
|
||||
const runOwnedCommand = async () => {
|
||||
ownedCommand ??= importVerifierSource(
|
||||
new URL("../../../src/process/OwnedCommand.ts", import.meta.url).href,
|
||||
import.meta.url,
|
||||
);
|
||||
return (await ownedCommand).runOwnedCommand;
|
||||
};
|
||||
|
||||
/** Why `requirement` is unmet on this host, or undefined when it is met. */
|
||||
const unmet = async (requirement) => {
|
||||
const run = await runOwnedCommand();
|
||||
let stdout;
|
||||
try {
|
||||
const snapshot = await run(
|
||||
{
|
||||
command: fixtureCommand(requirement.tool),
|
||||
arguments: requirement.arguments,
|
||||
runId: randomUUID(),
|
||||
expectedCommand: null,
|
||||
env: { ...process.env, LC_ALL: "C" },
|
||||
},
|
||||
{ timeoutMs: 10_000, diagnosticBytes: 1024 * 1024 },
|
||||
);
|
||||
stdout = snapshot.stdout.text;
|
||||
} catch (cause) {
|
||||
// A probe whose process group could not be confirmed stopped is not a
|
||||
// missing tool; that failure stays the caller's to report.
|
||||
if (cause?.cleanupFailure != null) throw cause;
|
||||
if (cause?.code === "ENOENT" || cause?.processError?.code === "ENOENT")
|
||||
return "command not found";
|
||||
return (
|
||||
firstLine(cause?.snapshot?.stderr?.text) ??
|
||||
firstLine(cause?.snapshot?.stdout?.text) ??
|
||||
firstLine(cause?.message) ??
|
||||
"failed"
|
||||
);
|
||||
}
|
||||
return requirement.accepts(stdout);
|
||||
};
|
||||
|
||||
/**
|
||||
* Refuse a lane, before it allocates a workspace, when this host lacks a
|
||||
* command its fixtures run. The refusal names each missing capability, the
|
||||
* command probed and the variable that selects another one.
|
||||
*/
|
||||
export async function requireLaneToolchain(lane) {
|
||||
const requirements = LANE_TOOLCHAINS[lane];
|
||||
if (requirements === undefined)
|
||||
throw new Error(`No fixture toolchain is declared for lane ${lane}`);
|
||||
const missing = [];
|
||||
for (const requirement of requirements) {
|
||||
const reason = await unmet(requirement);
|
||||
if (reason === undefined) continue;
|
||||
const command = fixtureCommand(requirement.tool);
|
||||
const selected =
|
||||
requirement.tool.variable === undefined
|
||||
? command
|
||||
: `${requirement.tool.variable}=${command}`;
|
||||
missing.push(`- ${selected} must ${requirement.need}: ${reason}`);
|
||||
}
|
||||
if (missing.length > 0)
|
||||
throw new Error(
|
||||
[
|
||||
`Missing prerequisite: the ${lane} lane's fixture toolchain (no workspace was created).`,
|
||||
...missing,
|
||||
"Install the missing commands, or set each variable to a command that provides it.",
|
||||
].join("\n"),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import { chmod, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { afterEach, expect, it, vi } from "vitest";
|
||||
|
||||
import { requireLaneToolchain } from "./fixture-toolchain.mjs";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
/** A stand-in command that prints `banner` for any arguments. */
|
||||
const command = async (directory: string, name: string, banner: string) => {
|
||||
const path = join(directory, name);
|
||||
await writeFile(
|
||||
path,
|
||||
`#!${process.execPath}\nprocess.stdout.write(${JSON.stringify(`${banner}\n`)});\n`,
|
||||
);
|
||||
await chmod(path, 0o700);
|
||||
return path;
|
||||
};
|
||||
|
||||
/** The refusal's lines that name `selected` (a `VARIABLE=command` pair). */
|
||||
const refusalFor = async (lane: string, selected: string) => {
|
||||
const refusal = await requireLaneToolchain(lane).catch(
|
||||
(cause: unknown) => cause,
|
||||
);
|
||||
expect(refusal).toBeInstanceOf(Error);
|
||||
expect((refusal as Error).message).toMatch(/^Missing prerequisite\b/u);
|
||||
return (refusal as Error).message
|
||||
.split("\n")
|
||||
.filter((line) => line.includes(selected));
|
||||
};
|
||||
|
||||
const mipsBinutils = async (directory: string, target: string) => {
|
||||
vi.stubEnv(
|
||||
"REA_MIPS_AS",
|
||||
await command(
|
||||
directory,
|
||||
"as",
|
||||
`GNU assembler (GNU Binutils) 2.44\nThis assembler was configured for a target of \`${target}'.`,
|
||||
),
|
||||
);
|
||||
vi.stubEnv(
|
||||
"REA_MIPS_LD",
|
||||
await command(
|
||||
directory,
|
||||
"ld",
|
||||
`GNU ld (GNU Binutils) 2.44\n Supported emulations:\n ${target.startsWith("mips") ? "elf32btsmip" : "elf_x86_64"}`,
|
||||
),
|
||||
);
|
||||
vi.stubEnv("REA_MIPS_READELF", await command(directory, "readelf", "x"));
|
||||
};
|
||||
|
||||
it.skipIf(process.platform === "win32")(
|
||||
"names each missing fixture command, its variable and the observed reason",
|
||||
async () => {
|
||||
const directory = await createTestTempDirectory("rea-fixture-toolchain-");
|
||||
const clang = join(directory, "missing-clang");
|
||||
const readelf = await command(directory, "readelf", "LLVM readelf 19");
|
||||
vi.stubEnv("REA_PPC_CLANG", clang);
|
||||
vi.stubEnv("REA_PPC_READELF", readelf);
|
||||
|
||||
const [compiler] = await refusalFor("ppc", `REA_PPC_CLANG=${clang}`);
|
||||
expect(compiler).toContain("powerpc-unknown-linux-gnu");
|
||||
expect(compiler).toContain("command not found");
|
||||
const [reader] = await refusalFor("ppc", `REA_PPC_READELF=${readelf}`);
|
||||
expect(reader).toContain("LLVM readelf 19");
|
||||
},
|
||||
);
|
||||
|
||||
it.skipIf(process.platform === "win32")(
|
||||
"admits MIPS binutils configured for MIPS and refuses host-native ones",
|
||||
async () => {
|
||||
const directory = await createTestTempDirectory("rea-fixture-toolchain-");
|
||||
await mipsBinutils(directory, "mips-linux-gnu");
|
||||
await expect(requireLaneToolchain("mips-shared")).resolves.toBeUndefined();
|
||||
|
||||
const native = await createTestTempDirectory("rea-fixture-toolchain-");
|
||||
await mipsBinutils(native, "x86_64-linux-gnu");
|
||||
expect(
|
||||
await refusalFor("mips-shared", `REA_MIPS_AS=${join(native, "as")}`),
|
||||
).toHaveLength(1);
|
||||
expect(
|
||||
await refusalFor("mips-shared", `REA_MIPS_LD=${join(native, "ld")}`),
|
||||
).toHaveLength(1);
|
||||
},
|
||||
);
|
||||
@@ -1,6 +1,7 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { execFile } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
import { FIXTURE_TOOLS, fixtureCommand } from "./fixture-toolchain.mjs";
|
||||
|
||||
const execute = promisify(execFile);
|
||||
const language = (byteOrder) => {
|
||||
@@ -194,7 +195,7 @@ export function parseMipsReadelf(
|
||||
|
||||
/** Independent reader is a bounded test prerequisite, never an REA provider. */
|
||||
export async function inspectMipsReadelf(path, byteOrder, floatAbi = "hard") {
|
||||
const command = process.env.REA_MIPS_READELF ?? "readelf";
|
||||
const command = fixtureCommand(FIXTURE_TOOLS.mipsReadelf);
|
||||
const options = {
|
||||
env: { ...process.env, LC_ALL: "C" },
|
||||
timeout: 10000,
|
||||
@@ -226,7 +227,7 @@ export async function inspectMipsReadelf(path, byteOrder, floatAbi = "hard") {
|
||||
|
||||
/** Independent GNU readelf interpretation for the recordless legacy fixture. */
|
||||
export async function inspectLegacyMipsReadelf(path, byteOrder) {
|
||||
const command = process.env.REA_MIPS_READELF ?? "readelf";
|
||||
const command = fixtureCommand(FIXTURE_TOOLS.mipsReadelf);
|
||||
const options = {
|
||||
env: { ...process.env, LC_ALL: "C" },
|
||||
timeout: 10000,
|
||||
|
||||
@@ -4,6 +4,7 @@ import { readFile, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
import { FIXTURE_TOOLS, fixtureCommand } from "./fixture-toolchain.mjs";
|
||||
|
||||
const execute = promisify(execFile);
|
||||
|
||||
@@ -196,7 +197,7 @@ async function compileMipsFixture(
|
||||
args,
|
||||
options = {},
|
||||
) {
|
||||
const compiler = process.env.REA_MIPS_CLANG ?? "clang";
|
||||
const compiler = fixtureCommand(FIXTURE_TOOLS.mipsClang);
|
||||
const version = await execute(compiler, ["--version"], { timeout: 10000 });
|
||||
await execute(compiler, args, { timeout: 60000 });
|
||||
const bytes = await readFile(path);
|
||||
|
||||
@@ -2,6 +2,7 @@ import assert from "node:assert/strict";
|
||||
import { execFile } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
import { parseMipsReadelf } from "./mips-checks.mjs";
|
||||
import { FIXTURE_TOOLS, fixtureCommand } from "./fixture-toolchain.mjs";
|
||||
|
||||
const execute = promisify(execFile);
|
||||
const RELOCATION_TYPES = new Map([
|
||||
@@ -90,7 +91,7 @@ export function parseMipsRelocatableReadelf(text) {
|
||||
|
||||
/** Independent reader is a bounded test prerequisite, never an REA provider. */
|
||||
export async function inspectMipsRelocatableReadelf(path, byteOrder, floatAbi) {
|
||||
const command = process.env.REA_MIPS_READELF ?? "readelf";
|
||||
const command = fixtureCommand(FIXTURE_TOOLS.mipsReadelf);
|
||||
const options = {
|
||||
env: { ...process.env, LC_ALL: "C" },
|
||||
timeout: 10000,
|
||||
|
||||
@@ -5,6 +5,7 @@ import { readFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
import { FIXTURE_TOOLS, fixtureCommand } from "./fixture-toolchain.mjs";
|
||||
|
||||
const execute = promisify(execFile);
|
||||
const source = fileURLToPath(new URL("./mips16-shared.S", import.meta.url));
|
||||
@@ -24,8 +25,8 @@ export async function buildMips16SharedFixture(directory, byteOrder, variant) {
|
||||
"section-relocations",
|
||||
].includes(variant),
|
||||
);
|
||||
const assembler = process.env.REA_MIPS_AS ?? "mips-linux-gnu-as";
|
||||
const linker = process.env.REA_MIPS_LD ?? "mips-linux-gnu-ld";
|
||||
const assembler = fixtureCommand(FIXTURE_TOOLS.mipsAssembler);
|
||||
const linker = fixtureCommand(FIXTURE_TOOLS.mipsLinker);
|
||||
const endian = byteOrder === "little" ? "-EL" : "-EB";
|
||||
const prefix = join(directory, `${byteOrder}-${variant}`);
|
||||
const object = `${prefix}.o`;
|
||||
@@ -97,7 +98,7 @@ export async function buildMips16SharedFixture(directory, byteOrder, variant) {
|
||||
return u32(mappings[0] + 4) + address - u32(mappings[0] + 8);
|
||||
};
|
||||
const readelf = await execute(
|
||||
process.env.REA_MIPS_READELF ?? "readelf",
|
||||
fixtureCommand(FIXTURE_TOOLS.mipsReadelf),
|
||||
["-W", "-h", "-l", "-s", "-r", path],
|
||||
{ timeout: 10000 },
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { execFile } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
import { FIXTURE_TOOLS, fixtureCommand } from "./fixture-toolchain.mjs";
|
||||
|
||||
const execute = promisify(execFile);
|
||||
// The lane binds this language; Ghidra's ELF loader alone selects e500.
|
||||
@@ -52,7 +53,7 @@ export function parsePowerPcReadelf(text) {
|
||||
|
||||
/** Independent reader is a bounded test prerequisite, never an REA provider. */
|
||||
export async function inspectPowerPcReadelf(path) {
|
||||
const command = process.env.REA_PPC_READELF ?? "readelf";
|
||||
const command = fixtureCommand(FIXTURE_TOOLS.ppcReadelf);
|
||||
const options = {
|
||||
env: { ...process.env, LC_ALL: "C" },
|
||||
timeout: 10000,
|
||||
|
||||
@@ -4,12 +4,13 @@ import { readFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
import { FIXTURE_TOOLS, fixtureCommand } from "./fixture-toolchain.mjs";
|
||||
|
||||
const execute = promisify(execFile);
|
||||
|
||||
/** Build a source-owned freestanding ELF; never execute its target code. */
|
||||
export async function buildPowerPcFixture(directory) {
|
||||
const compiler = process.env.REA_PPC_CLANG ?? "clang";
|
||||
const compiler = fixtureCommand(FIXTURE_TOOLS.ppcClang);
|
||||
const target = "powerpc-unknown-linux-gnu";
|
||||
const source = fileURLToPath(new URL("../c/ppc.c", import.meta.url));
|
||||
const probe = fileURLToPath(new URL("./ppc-probe.S", import.meta.url));
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { execFile } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
import { FIXTURE_TOOLS, fixtureCommand } from "./fixture-toolchain.mjs";
|
||||
|
||||
const execute = promisify(execFile);
|
||||
|
||||
@@ -328,7 +329,7 @@ export function parseRiscvCallSites(listing) {
|
||||
|
||||
/** Disassemble the typed fixture's caller with an independent LLVM tool. */
|
||||
export async function inspectRiscvCallSites(path) {
|
||||
const command = process.env.REA_RISCV_OBJDUMP ?? "llvm-objdump";
|
||||
const command = fixtureCommand(FIXTURE_TOOLS.riscvObjdump);
|
||||
const options = {
|
||||
env: { ...process.env, LC_ALL: "C" },
|
||||
timeout: 10000,
|
||||
|
||||
@@ -5,6 +5,7 @@ import { readFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
import { FIXTURE_TOOLS, fixtureCommand } from "./fixture-toolchain.mjs";
|
||||
|
||||
const execute = promisify(execFile);
|
||||
|
||||
@@ -96,9 +97,9 @@ export async function buildRiscvFixture(directory, bits, floatAbi = "double") {
|
||||
const build = BUILDS[floatAbi];
|
||||
assert.ok(build !== undefined, `No ${floatAbi}-float RISC-V fixture`);
|
||||
assert.ok(floatAbi === "double" || bits === 32);
|
||||
const compiler = process.env.REA_RISCV_CLANG ?? "clang";
|
||||
const linker = process.env.REA_RISCV_LLD ?? "ld.lld";
|
||||
const reader = process.env.REA_RISCV_READELF ?? "llvm-readelf";
|
||||
const compiler = fixtureCommand(FIXTURE_TOOLS.riscvClang);
|
||||
const linker = fixtureCommand(FIXTURE_TOOLS.riscvLinker);
|
||||
const reader = fixtureCommand(FIXTURE_TOOLS.riscvReadelf);
|
||||
const sources = build.sources.map((path) =>
|
||||
fileURLToPath(new URL(path, import.meta.url)),
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user