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:
parent
69fe5b6010
commit
ac9677bd6c
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue