[GH-ISSUE #7381] [Feature] Ghidra provider: support universal (fat) Mach-O by selecting a slice instead of refusing #4388

Open
opened 2026-10-31 00:49:25 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @5mghost on GitHub (Oct 27, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/7381

Summary

The Ghidra provider refuses any universal (fat) Mach-O with architecture_unsupported, even though Ghidra can import a specific slice when -processor is passed. Thin Mach-O and ELF targets work fine. In practice, most macOS system binaries and many shipped apps are universal, so users have to lipo -thin every target by hand before rea decompile works.

Environment

  • rea-agents 6.8.0 (latest on npm), CLI
  • Ghidra 12.1.4 (PUBLIC), Temurin JDK 21.0.13
  • macOS 27.0.1, Apple Silicon (arm64)
  • Node.js v24.22.1

Steps to reproduce

cp /bin/echo /tmp/echo        # universal: x86_64 + arm64e
rea decompile /tmp/echo main --provider ghidra --format json

Expected

rea picks a slice (e.g. the host slice, or one chosen by the caller) and decompiles it, or offers a documented way to choose the slice for a universal input.

Actual

{"error":"Analysis failed","code":"capability_unavailable","category":"unsupported_provider",
 "details":{"operation":"open_binary","selection_reason":"target_unsupported","requested_provider_id":"ghidra",
  "rejections":[{"provider_id":"ghidra","code":"architecture_unsupported",
   "reason":"Ghidra v1 cannot enforce the selected architecture when importing a universal Mach-O; analyze a thinned Mach-O slice instead.",
   "diagnostics":{"host_platform":"darwin","target_kind":"executable","target_format":"mach-o-universal",
    "architecture":"arm64","available_architectures":["x86_64","arm64"]}}]}}

Passing --architecture arm64 doesn't help either:
architecture and entry_point apply only with format=raw-binary.

Comparison (what does work)

  • Thin Mach-O works: lipo -thin arm64e /tmp/echo -output /tmp/echo-arm64e, then rea decompile /tmp/echo-arm64e ... --provider ghidra succeeds. A minimal clang-built thin arm64 Mach-O also decompiles correctly (_compute).
  • ELF works: the same test function built as an aarch64 Linux ELF decompiles correctly.
  • Ghidra itself can select the slice:
    • analyzeHeadless <proj> p -import /tmp/echo imports the first slice (x86:LE:64:default:gcc). So the concern in the refusal message is valid when nothing is specified.
    • analyzeHeadless <proj> p -import /tmp/echo -processor AARCH64:LE:64:AppleSilicon imports the arm64 slice (Using Language/Compiler: AARCH64:LE:64:AppleSilicon:default).

Where the restriction lives

src/ghidra/GhidraProvider.ts, inspectTargetSupport() (around L285–292 on main; dist/ghidra/GhidraProvider.js ~L163 in 6.8.0):

if (isMachOFormat(target.format) && target.availableArchitectures.length > 1)
  return { status: "unsupported", code: "architecture_unsupported",
           reason: "Ghidra v1 cannot enforce the selected architecture when importing a universal Mach-O; ..." };

Suggestion

One of these would remove the manual lipo step:

  1. For universal Mach-O, pass -processor (and -cspec if needed) for the selected slice to analyzeHeadless. The launcher already builds -processor args for other import modes. Then confirm after import that the loaded program's language matches the selected architecture, and fail closed if it doesn't.
  2. Or thin the selected slice into the ephemeral session root (rea already copies the target to target-<sha>.bin) and import that thin slice.
  3. At minimum, accept a slice selector (e.g. --architecture) for Mach-O inputs, and mention the lipo -thin workaround in the refusal remediation.

Small related note: Mach-O symbols carry a leading underscore (_compute). rea decompile <macho> compute returns Unknown Ghidra procedure name or address: compute. A hint suggesting _compute in that error would help.

Originally created by @5mghost on GitHub (Oct 27, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/7381 ## Summary The Ghidra provider refuses any universal (fat) Mach-O with `architecture_unsupported`, even though Ghidra can import a specific slice when `-processor` is passed. Thin Mach-O and ELF targets work fine. In practice, most macOS system binaries and many shipped apps are universal, so users have to `lipo -thin` every target by hand before `rea decompile` works. ## Environment - rea-agents 6.8.0 (latest on npm), CLI - Ghidra 12.1.4 (PUBLIC), Temurin JDK 21.0.13 - macOS 27.0.1, Apple Silicon (arm64) - Node.js v24.22.1 ## Steps to reproduce ```sh cp /bin/echo /tmp/echo # universal: x86_64 + arm64e rea decompile /tmp/echo main --provider ghidra --format json ``` ## Expected rea picks a slice (e.g. the host slice, or one chosen by the caller) and decompiles it, or offers a documented way to choose the slice for a universal input. ## Actual ```json {"error":"Analysis failed","code":"capability_unavailable","category":"unsupported_provider", "details":{"operation":"open_binary","selection_reason":"target_unsupported","requested_provider_id":"ghidra", "rejections":[{"provider_id":"ghidra","code":"architecture_unsupported", "reason":"Ghidra v1 cannot enforce the selected architecture when importing a universal Mach-O; analyze a thinned Mach-O slice instead.", "diagnostics":{"host_platform":"darwin","target_kind":"executable","target_format":"mach-o-universal", "architecture":"arm64","available_architectures":["x86_64","arm64"]}}]}} ``` Passing `--architecture arm64` doesn't help either: `architecture and entry_point apply only with format=raw-binary`. ## Comparison (what does work) - **Thin Mach-O works:** `lipo -thin arm64e /tmp/echo -output /tmp/echo-arm64e`, then `rea decompile /tmp/echo-arm64e ... --provider ghidra` succeeds. A minimal clang-built thin arm64 Mach-O also decompiles correctly (`_compute`). - **ELF works:** the same test function built as an aarch64 Linux ELF decompiles correctly. - **Ghidra itself can select the slice:** - `analyzeHeadless <proj> p -import /tmp/echo` imports the first slice (`x86:LE:64:default:gcc`). So the concern in the refusal message is valid when nothing is specified. - `analyzeHeadless <proj> p -import /tmp/echo -processor AARCH64:LE:64:AppleSilicon` imports the arm64 slice (`Using Language/Compiler: AARCH64:LE:64:AppleSilicon:default`). ## Where the restriction lives `src/ghidra/GhidraProvider.ts`, `inspectTargetSupport()` (around L285–292 on `main`; `dist/ghidra/GhidraProvider.js` ~L163 in 6.8.0): ```ts if (isMachOFormat(target.format) && target.availableArchitectures.length > 1) return { status: "unsupported", code: "architecture_unsupported", reason: "Ghidra v1 cannot enforce the selected architecture when importing a universal Mach-O; ..." }; ``` ## Suggestion One of these would remove the manual `lipo` step: 1. For universal Mach-O, pass `-processor` (and `-cspec` if needed) for the selected slice to `analyzeHeadless`. The launcher already builds `-processor` args for other import modes. Then confirm after import that the loaded program's language matches the selected architecture, and fail closed if it doesn't. 2. Or thin the selected slice into the ephemeral session root (rea already copies the target to `target-<sha>.bin`) and import that thin slice. 3. At minimum, accept a slice selector (e.g. `--architecture`) for Mach-O inputs, and mention the `lipo -thin` workaround in the refusal remediation. Small related note: Mach-O symbols carry a leading underscore (`_compute`). `rea decompile <macho> compute` returns `Unknown Ghidra procedure name or address: compute`. A hint suggesting `_compute` in that error would help.
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#4388
No description provided.