[GH-ISSUE #1597] [Bug] verify:ghidra entry-alias check fails on macOS arm64 #540

Closed
opened 2026-10-15 18:25:59 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @RishTheFish21 on GitHub (Oct 10, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/1597

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Native analysis or decompilation

Observed behavior

Any real-Ghidra lane on an Apple Silicon Mac fails in verifyGhidraEntryAliases before reaching its own fixtures, e.g. npm run verify:ghidra:aarch64-jump-table:

AssertionError: Expected "actual" to be strictly unequal to: '0x1000003c0'
    at verifyGhidraEntryAliases (scripts/lib/real-ghidra-entry-aliases.mjs:91:12)

The fixture's _rea_interior label resolves to the same address as the function entry.

Reproduction

On macOS arm64 with Ghidra 12.1.4 (natives built via buildNatives) and JDK 21, run npm run verify:ghidra:aarch64-jump-table on current main.

Expected behavior

The host-native lane passes on macOS arm64, which docs/testing.md lists as supported.

Impact and scope

Only the verification fixture and assertion are affected, not REA's runtime behavior. Name resolution is doing the right thing given Ghidra's function list. CI doesn't run real Ghidra on macOS, so this went unnoticed. A fix probably needs a different interior-label approach on Mach-O, or a platform-specific expectation in the verifier.

REA version

810107a2

Runtime and platform

macOS, Apple Silicon (arm64), Node 24.18.0, Ghidra 12.1.4, JDK 21 (Temurin)

Target and artifact context

Mach-O arm64 executable built from tests/conformance/ghidra/entry-aliases.c by the verifier (cc -O0 -g -fno-inline)

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence


Typed outcome or recovery expected

No response

Analysis or suspected cause

Source-derived, partly tested; I haven't confirmed the second point inside Ghidra itself.

  1. Apple clang at -O0 emits no prologue for rea_alias_target on arm64, so the inline-asm rea_interior label lands on the function's first instruction. Compiling the fixture for arm64-apple-macos puts both symbols at offset 0; on aarch64/x86-64 Linux and x86-64 macOS they're 4–5 bytes apart.
  2. Adding a nop before the label fixes that, but the next assertion then fails: procedure_address accepted {"procedure":"_rea_interior"}. My guess is the Mach-O linker records every symbol in __text in LC_FUNCTION_STARTS, so Ghidra creates a function at _rea_interior and REA accepts it by name, which would be correct behavior. Linking with ld64.lld lists the label as a function start whether it's global, local, or marked .alt_entry. I haven't checked Apple's linker directly.

So this probably needs a different interior-label approach on Mach-O, or a platform-specific expectation in the verifier, rather than a change to name resolution.

Additional context

Found while running the AArch64 jump-table lane for #487. CI doesn't run real Ghidra on macOS, which is probably why this hasn't come up.

Originally created by @RishTheFish21 on GitHub (Oct 10, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/1597 ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Native analysis or decompilation ### Observed behavior Any real-Ghidra lane on an Apple Silicon Mac fails in `verifyGhidraEntryAliases` before reaching its own fixtures, e.g. `npm run verify:ghidra:aarch64-jump-table`: ``` AssertionError: Expected "actual" to be strictly unequal to: '0x1000003c0' at verifyGhidraEntryAliases (scripts/lib/real-ghidra-entry-aliases.mjs:91:12) ``` The fixture's `_rea_interior` label resolves to the same address as the function entry. ### Reproduction On macOS arm64 with Ghidra 12.1.4 (natives built via `buildNatives`) and JDK 21, run `npm run verify:ghidra:aarch64-jump-table` on current `main`. ### Expected behavior The host-native lane passes on macOS arm64, which `docs/testing.md` lists as supported. ### Impact and scope Only the verification fixture and assertion are affected, not REA's runtime behavior. Name resolution is doing the right thing given Ghidra's function list. CI doesn't run real Ghidra on macOS, so this went unnoticed. A fix probably needs a different interior-label approach on Mach-O, or a platform-specific expectation in the verifier. ### REA version 810107a2 ### Runtime and platform macOS, Apple Silicon (arm64), Node 24.18.0, Ghidra 12.1.4, JDK 21 (Temurin) ### Target and artifact context Mach-O arm64 executable built from tests/conformance/ghidra/entry-aliases.c by the verifier (cc -O0 -g -fno-inline) ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence ```text ``` ### Typed outcome or recovery expected _No response_ ### Analysis or suspected cause Source-derived, partly tested; I haven't confirmed the second point inside Ghidra itself. 1. Apple clang at `-O0` emits no prologue for `rea_alias_target` on arm64, so the inline-asm `rea_interior` label lands on the function's first instruction. Compiling the fixture for `arm64-apple-macos` puts both symbols at offset 0; on aarch64/x86-64 Linux and x86-64 macOS they're 4–5 bytes apart. 2. Adding a `nop` before the label fixes that, but the next assertion then fails: `procedure_address accepted {"procedure":"_rea_interior"}`. My guess is the Mach-O linker records every symbol in `__text` in `LC_FUNCTION_STARTS`, so Ghidra creates a function at `_rea_interior` and REA accepts it by name, which would be correct behavior. Linking with `ld64.lld` lists the label as a function start whether it's global, local, or marked `.alt_entry`. I haven't checked Apple's linker directly. So this probably needs a different interior-label approach on Mach-O, or a platform-specific expectation in the verifier, rather than a change to name resolution. ### Additional context Found while running the AArch64 jump-table lane for #487. CI doesn't run real Ghidra on macOS, which is probably why this hasn't come up.
bsakaguchi 2026-10-15 18:25:59 +00:00
  • closed this issue
  • added the
    triage
    bug
    labels
Author
Owner

@N0zoM1z0 commented on GitHub (Oct 10, 2026):

Reproduced both representations with Clang/LD64 14.0.6 and real Ghidra 12.1.4 through REA stdio MCP on Linux:

  • Original ARM64 Mach-O: _rea_interior and the entry aliases have the same address.
  • With a leading NOP: the interior symbol moves four bytes, LD64 advertises it in LC_FUNCTION_STARTS, and Ghidra correctly imports a separate function there.
  • The same NOP fixture linked with -no_function_starts: the interior symbol remains imported, Ghidra keeps it inside the alias function, and procedure_address rejects its name as intended. Entry aliases still resolve, and the data-symbol negative control is rejected in every case.

All target bytes were preserved and both probe process lineages verified empty. This establishes a fixture correction direction (a guaranteed leading instruction plus omitting function-start metadata for this regression fixture); it is cross-target evidence on Linux, not a native macOS/Apple-linker pass. No production resolver change is indicated by these probes.

<!-- gh-comment-id:6101800876 --> @N0zoM1z0 commented on GitHub (Oct 10, 2026): Reproduced both representations with Clang/LD64 14.0.6 and real Ghidra 12.1.4 through REA stdio MCP on Linux: - Original ARM64 Mach-O: `_rea_interior` and the entry aliases have the same address. - With a leading NOP: the interior symbol moves four bytes, LD64 advertises it in `LC_FUNCTION_STARTS`, and Ghidra correctly imports a separate function there. - The same NOP fixture linked with `-no_function_starts`: the interior symbol remains imported, Ghidra keeps it inside the alias function, and `procedure_address` rejects its name as intended. Entry aliases still resolve, and the data-symbol negative control is rejected in every case. All target bytes were preserved and both probe process lineages verified empty. This establishes a fixture correction direction (a guaranteed leading instruction plus omitting function-start metadata for this regression fixture); it is cross-target evidence on Linux, not a native macOS/Apple-linker pass. No production resolver change is indicated by these probes.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#540
No description provided.