test(ghidra): preserve metadata in interior-label fixture

Place the imported control label inside a valid multi-byte NOP instead of suppressing Mach-O function-start metadata. Encode the supported ARM64 and x86-64 instructions explicitly and retain the load dependency so the control remains past the function entry. Assert that Ghidra imports a label and no procedure at the control address before checking name rejection and address containment.

Two real arm64 Ghidra 12.1.4 imports confirmed both fixture strategies produce labels; only default linking retains LC_FUNCTION_STARTS. The full native-value CLI/MCP verifier passed with 349 successful and 52 expected rejected boundary calls, including imported aliases, complete exports, and startup cancellation cleanup. Host ARM64 and Apple x86-64 fixture compilation passed; x86 provider execution remains unverified.
This commit is contained in:
morluto committed 2026-10-11 06:10:10 +08:00
1 parent c7345512ea
commit 926c2d6ba7
2 files changed
+33 -18

No files matched your search

+16 -14
View File
@@ -22,20 +22,10 @@ export async function verifyGhidraEntryAliases({
);
const path = join(workspace, "entry-aliases");
try {
// 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 },
);
await exec(compiler, ["-O0", "-g", "-fno-inline", source, "-o", path], {
env,
timeout: 30000,
});
const bytes = await readFile(path);
await call("close_binary");
const imported = await call("open_binary", { path, provider_id: "ghidra" });
@@ -101,6 +91,18 @@ export async function verifyGhidraEntryAliases({
);
assert.ok(interior);
assert.notEqual(interior.address, alias.address);
assert.equal(
interior.symbol.type,
"label",
"Fixture interior control must remain an imported label",
);
assert.equal(
(await call("list_procedures")).some(
({ address }) => address === interior.address,
),
false,
"Fixture interior control must not be a Ghidra procedure entry",
);
const failure = await reject("procedure_address", {
procedure: interior.value,
});
+17 -4
View File
@@ -9,10 +9,23 @@ volatile int rea_xrefs_data = 17;
__attribute__((used)) volatile int rea_xrefs_unreferenced = 41;
__attribute__((noinline, used)) int rea_alias_target(void) {
// 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;
int value = rea_xrefs_data;
// Preserve normal function-start metadata. Imported symbols at instruction
// boundaries can become procedure entries, so keep this label inside a NOP.
#if defined(__aarch64__)
__asm__ volatile(".globl " REA_SYMBOL("rea_interior") "\n"
".byte 0x1f\n"
REA_SYMBOL("rea_interior") ":\n"
".byte 0x20,0x03,0xd5\n" : : "r"(value));
#elif defined(__x86_64__)
__asm__ volatile(".byte 0x0f\n"
".globl " REA_SYMBOL("rea_interior") "\n"
REA_SYMBOL("rea_interior") ":\n"
".byte 0x1f,0x00\n" : : "r"(value));
#else
#error "The Ghidra entry-alias fixture supports only x86_64 and arm64"
#endif
return value;
}
__asm__(".globl " REA_SYMBOL("rea_entry_alias") "\n"