fix(test): preserve Ghidra interior-label controls on Mach-O (#1640)

* fix(test): preserve Ghidra interior-label controls on Mach-O

* test: preserve selector semantics and accept signaled fixture exits

* test(javascript): exercise deep returns through the public CLI
This commit is contained in:
Ysopwnable 2026-10-11 05:45:00 +08:00 committed by GitHub
parent 69fe5b6010
commit ac9677bd6c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 34 additions and 11 deletions

View File

@ -21,15 +21,25 @@ export async function verifyGhidraEntryAliases({
new URL("../../tests/conformance/ghidra/entry-aliases.c", import.meta.url),
);
const path = join(workspace, "entry-aliases");
const prefix = process.platform === "darwin" ? "_" : "";
try {
await exec(compiler, ["-O0", "-g", "-fno-inline", source, "-o", path], {
env,
timeout: 30000,
});
// Mach-O function-start metadata would promote the interior control label.
await exec(
compiler,
[
"-O0",
"-g",
"-fno-inline",
...(process.platform === "darwin" ? ["-Wl,-no_function_starts"] : []),
source,
"-o",
path,
],
{ env, timeout: 30000 },
);
const bytes = await readFile(path);
await call("close_binary");
await call("open_binary", { path, provider_id: "ghidra" });
const imported = await call("open_binary", { path, provider_id: "ghidra" });
const prefix = imported.format === "mach-o" ? "_" : "";
const names = await call("list_names");
const alias = names.find(
({ value }) => value === `${prefix}rea_entry_alias`,
@ -46,8 +56,10 @@ export async function verifyGhidraEntryAliases({
);
assert.ok(canonical, "Fixture alias is not at a function entry");
const xrefsSelectors = [];
// CLI xrefs keeps bare hexadecimal selectors as addresses. A Mach-O
// primary label such as "dead" is tested below as a procedure name instead.
for (const value of [
canonical.value,
`${prefix}rea_alias_target`,
alias.value,
`${prefix}rea_xrefs_data`,
`${prefix}rea_xrefs_unreferenced`,
@ -153,7 +165,11 @@ export async function verifyGhidraEntryAliases({
{ env, timeout: 240000, maxBuffer: 16 * 1024 * 1024 },
);
const evidence = JSON.parse(stdout);
assert.deepEqual(evidence.normalized_result, expected);
assert.deepEqual(
evidence.normalized_result,
expected,
`CLI xrefs mismatch for ${JSON.stringify(selector)}`,
);
assert.equal(evidence.parameters.address, selector.at(-1));
}
}

View File

@ -9,6 +9,12 @@ import {
} from "../../../../src/hopper/HopperClient.js";
import { HopperFixtureLauncher } from "./hopperClient.fixture.js";
const expectStoppedLauncher = (launcher: HopperFixtureLauncher): void => {
const child = launcher.processes[0];
if (child === undefined) throw new Error("Diagnostic fixture did not launch");
expect(child.exitCode !== null || child.signalCode !== null).toBe(true);
};
it("bounds long-lived launcher output while keeping the bridge usable", async () => {
let observedStderrBytes = 0;
const diagnosticLauncher = new HopperFixtureLauncher(undefined, "noisy");
@ -35,7 +41,7 @@ it("bounds long-lived launcher output while keeping the bridge usable", async ()
await client.close();
const directory = diagnosticLauncher.directories[0] ?? "";
await expect(access(directory)).rejects.toMatchObject({ code: "ENOENT" });
expect(diagnosticLauncher.processes[0]?.exitCode).not.toBeNull();
expectStoppedLauncher(diagnosticLauncher);
});
it("reports observed and retained output when capture cuts a token", async () => {
@ -84,5 +90,5 @@ it("reports observed and retained output when capture cuts a token", async () =>
await client.close();
const directory = diagnosticLauncher.directories[0] ?? "";
await expect(access(directory)).rejects.toMatchObject({ code: "ENOENT" });
expect(diagnosticLauncher.processes[0]?.exitCode).not.toBeNull();
expectStoppedLauncher(diagnosticLauncher);
});

View File

@ -9,7 +9,8 @@ volatile int rea_xrefs_data = 17;
__attribute__((used)) volatile int rea_xrefs_unreferenced = 41;
__attribute__((noinline, used)) int rea_alias_target(void) {
__asm__ volatile(".globl " REA_SYMBOL("rea_interior") "\n"
// ARM64 leaf functions may have no prologue; keep the control past entry.
__asm__ volatile("nop\n.globl " REA_SYMBOL("rea_interior") "\n"
REA_SYMBOL("rea_interior") ":");
return rea_xrefs_data;
}