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:
morluto authored and GitHub committed 2026-11-15 21:07:43 +08:00
1 parent fe05a4c56d
commit 14a5f871ae
19 files changed
+365 -15

No files matched your search

+3
View File
@@ -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.
+3
View File
@@ -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.
+3
View File
@@ -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.
+3
View File
@@ -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.
+2 -1
View File
@@ -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,
+2 -1
View File
@@ -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);
},
);
+3 -2
View File
@@ -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,
+2 -1
View File
@@ -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 },
);
+2 -1
View File
@@ -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,
+2 -1
View File
@@ -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));
+2 -1
View File
@@ -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,
+4 -3
View File
@@ -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)),
);