From 926c2d6ba7975522d63a8dbb0d448f5b9d1514d4 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Sun, 11 Oct 2026 06:07:42 +0800 Subject: [PATCH] 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. --- scripts/lib/real-ghidra-entry-aliases.mjs | 30 ++++++++++++----------- tests/conformance/ghidra/entry-aliases.c | 21 +++++++++++++--- 2 files changed, 33 insertions(+), 18 deletions(-) diff --git a/scripts/lib/real-ghidra-entry-aliases.mjs b/scripts/lib/real-ghidra-entry-aliases.mjs index dc68ab1250..d628c3172c 100644 --- a/scripts/lib/real-ghidra-entry-aliases.mjs +++ b/scripts/lib/real-ghidra-entry-aliases.mjs @@ -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, }); diff --git a/tests/conformance/ghidra/entry-aliases.c b/tests/conformance/ghidra/entry-aliases.c index af152827a5..554dfc77b9 100644 --- a/tests/conformance/ghidra/entry-aliases.c +++ b/tests/conformance/ghidra/entry-aliases.c @@ -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"