Skip to content

ghidra: package the reverse-engineering suite 12.1.2 - #545

Merged
twitchyliquid64 merged 6 commits into
mainfrom
add-ghidra-12.1.2
Jul 31, 2026
Merged

ghidra: package the reverse-engineering suite 12.1.2#545
twitchyliquid64 merged 6 commits into
mainfrom
add-ghidra-12.1.2

Conversation

@bryan-minimal

@bryan-minimal bryan-minimal commented Jul 30, 2026

Copy link
Copy Markdown
Member

The only credible open decompiler for native code, and the anchor of a reverse-engineering loadout. analyzeHeadless is the point — it makes Ghidra scriptable across a fleet of binaries rather than a GUI you drive by hand.

Apache-2.0 at the root; the tree aggregates ~20 further licences (GPL-2.0 WITH Classpath-exception, LGPL, BSD, MIT, Zlib, MPL-2.0, Python-2.0 …) — all OSI/FSF-free, nothing proprietary.

Prebuilt release, not a source build

Ghidra's Gradle build runs fetchDependencies.gradle before compiling anything: 67 URLs across seven hosts, including binaries with no source (AXMLPrinter2.jar from the dead Google Code archive, per-platform Z3 zips), a postgresql tarball and PyPI wheels. It also requires JDK 25 to build; we ship 21.

The release zip is the same tradeoff the jdk package already makes, and it bundles no JRE, so Ghidra runs on our jdk rather than smuggling in a second Java.

arm64 was not at parity, and now is

Upstream publishes natives for linux_x86_64, mac_arm_64, mac_x86_64, win_x86_64 — and not linux_arm_64. On an arm64 host that is not a warning, it is a silently degraded run: Application.getModuleOSFile() has x86_64 fallbacks only for WIN_ARM_64 (emulation) and MAC_ARM_64 (Rosetta), so decompile is simply not found and analyzeHeadless exits 0 having produced no C.

Closed by building the five natives from C/C++ sources the release zip already ships — no second Source, no network, no Gradle. ARCH_TYPE= is the whole fix: the Makefile is ifeq ($(ARCH),x86_64) -m64 else -m32 with an in-tree TODO: need to revise to support arm64/aarch64, so make ARCH=aarch64 would look right and do nothing — every non-x86_64 value takes the else.

The 7-Zip gap was closable too, contrary to what an earlier revision of this PR asserted. Upstream's -all-platforms roll-up excludes ARM by design, but the per-platform artifact exists on Maven Central at the exact version Ghidra vendors.

Three pre-existing bugs fixed along the way

  • GPL/DemanglerGnu/os/ was never chmod'd — verified in a built tree: both demanglers were mode 0644 while the decompiler natives were 0755. Ghidra Runtime.exec()s them by absolute path with no fallback, so C++ demangling was silently broken, on x86_64 too.
  • 27 shell scripts had no exec bit — every debugger launcher and all of server/ — because support/* is a shallow glob. Now driven from the zip's own mode metadata, so it tracks upstream's intent. It correctly leaves *setuputils.sh alone: those are sourced libraries.
  • The win/mac prune missed the 7-Zip JNI blobs, which use Mac-x86_64/Windows-amd64 rather than Ghidra's os/<platform> convention.

Tests, because the failures here are silent

Two, both mutation-tested (breaking each assertion makes minimal check fail):

headless_decompile — compiles a namespaced C++ binary, decompiles it, asserts real C comes back and that Foo::bar demangled. It is written against decompileCompleted() alone, because the alternatives lie: isValid() returns true for a decompile that produced nothing, and isCancelled() reports a missing native as a user cancellation. Nothing asserts on analyzeHeadless's exit code — it has zero System.exit calls and exits 0 when the postScript throws.

sevenzip_native — proves the 7-Zip fix works rather than that a file exists. Its own output:

PLATFORM=Linux-arm64
ENTRY=SEVENZIP_PARITY_OK.txt

The platform assertion is load-bearing: if the jar swap regresses to shipping all-platforms alongside, the platform list stops being a single entry, getPlatformBestMatch() falls back to matching os.arch (aarch64) against Linux-arm64, and can never succeed.

Review feedback applied

  • @twitchyliquid64: set binary_from — correct, and ghidra was the only prebuilt package here not declaring it (chromium-bin, claude-code, gcloud, ampcode, android-sdk, cf, edgedelta all do). The comment records what it does not cover: the arm64 natives are compiled here, so the Java is upstream's binary and the arm64 decompiler is ours.
  • CodeRabbit: zlib duplicated in build_deps and runtime_deps — also correct. Carried real risk since sleigh links -lz at build time, so I rebuilt rather than assumed: exit 0, 14/14, sleigh still links libz.so.

Verified

minimal package --rebuild --no-fetch ghidra   exit 0
minimal check --packages ghidra               14/14 pass
natives                                       5/5 ELF aarch64, mode 0755
foreign-arch binaries in tree                 0
reproducibility                               byte-identical across rebuilds
installed size                                850M

Known limits

  • 850M installed — by far the largest package in the repo. Worth a conscious call on cache/closure pressure.
  • Z3 symbolic execution ships no linux_arm_64; fails loudly. Closing it means moving off the pinned Z3, whose own "arm64" asset contains x86-64 binaries — which is why the 7-Zip injection asserts e_machine on anything it adds.
  • The x86_64 path takes upstream's natives via a [ -f ] guard and could not be exercised on this arm64 host.

Followed by a second PR adding gef, rizin and the reveng loadout, which needs this one to land first.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added support for Ghidra 12.1.2.
    • Added native arm64 7-Zip archive handling.
    • Added launchers and platform-native libraries for supported environments.
    • Added headless native decompilation with GNU symbol demangling.
  • Tests
    • Added validation for arm64 archive reading and native decompilation.
  • Documentation
    • Added package metadata, including licensing and official source information.

The only credible open decompiler for native code, and the anchor of a
reverse-engineering loadout. `analyzeHeadless` is the point: it makes
Ghidra scriptable across a fleet of binaries rather than a GUI you drive
by hand.

Apache-2.0 at the root; the tree aggregates ~20 further licenses
(GPL-2.0 WITH Classpath-exception, LGPL, BSD, MIT, Zlib, MPL-2.0,
Python-2.0 …) — all OSI/FSF-free, nothing proprietary. Upstream keeps
GPL code in the standalone top-level GPL/ module by policy.

PREBUILT RELEASE, NOT A SOURCE BUILD

Ghidra's Gradle build runs fetchDependencies.gradle BEFORE compiling
anything: 67 URLs across seven hosts, including binaries with no source
(AXMLPrinter2.jar from the dead Google Code archive, per-platform Z3
zips), a postgresql tarball and PyPI wheels — then Maven resolution. It
also needs JDK 25 to build; we ship 21. The release zip is the same
tradeoff the jdk package already makes, and it bundles NO JRE, so Ghidra
runs on our jdk rather than smuggling in a second Java.

THE ARM64 GAP, CLOSED

Upstream publishes natives for linux_x86_64, mac_arm_64, mac_x86_64 and
win_x86_64 — and NOT linux_arm_64. On an arm64 host that is not a
warning, it is a silently degraded run: Application.getModuleOSFile()
implements an x86_64 fallback only for WIN_ARM_64 and MAC_ARM_64, so
`decompile` is simply not found, and analyzeHeadless exits 0 having
produced no C.

Closed by building the five natives from the C/C++ sources the release
zip ALREADY ships (src/decompile/**, GPL/DemanglerGnu/src/**,
src/lzfse/**). No second Source, no network, no Gradle — and no second
sha256 for the updater to leave stale, since it rewrites only the first.

  ARCH_TYPE=   the whole fix. The Makefile's detection is
               `ifeq ($(ARCH),x86_64) -m64 else -m32` with an in-tree
               "TODO: need to revise to support arm64/aarch64", so
               aarch64 lands in the -m32 branch. `make ARCH=aarch64`
               does NOT help — every non-x86_64 value takes the else.
  YACC/LEX=false  tripwires. `python3 -m zipfile` restores no mtimes, so
               slghparse.y can look newer than the shipped slghparse.cc
               and make invokes bison. The generated files are touched
               first; these make a regression fail loudly rather than
               silently need a tool we do not ship.

TWO BUGS FIXED ON THE WAY, BOTH PRE-EXISTING

  * GPL/DemanglerGnu/os/ was never chmod'd — the globs reached
    Ghidra/Features/*/os but not GPL/. Verified in a built tree: both
    demanglers were mode 0644 while the decompiler natives were 0755.
    Ghidra Runtime.exec()s them by absolute path with no fallback, so
    C++ demangling was silently broken.
  * The win/mac prune missed the 7-Zip JNI blobs: sevenzipjbinding uses
    Mac-x86_64 / Windows-amd64, not Ghidra's os/<platform> convention,
    so a Mach-O dylib and a PE DLL survived "drop Windows and macOS".

Also drops docs/GhidraClass — 16 deliberately-crafted training ELFs that
nothing execs and that pkgscan cannot see.

VERIFIED

  minimal package --rebuild --no-fetch ghidra   exit 0
  minimal check --packages ghidra               14/14 pass
  natives                                       5/5 ELF aarch64, mode 0755
  foreign-arch binaries left in tree            0
  installed size                                850M

The natives are declared as OutputBin with linux_* globs deliberately:
the output-types checker then parses them as ELF for the target arch, so
if a future bump ships x86_64-only and the native build silently
no-ops, `minimal check` FAILS with "expected Arm64". That is the guard
that caught this gap in the first place.

KNOWN LIMITS

  * 850M installed — by far the largest package in the repo. Worth a
    conscious call on cache/closure pressure.
  * 7z/RAR container browsing is unavailable on arm64: the vendored
    sevenzipjbinding-16.02 jar has no aarch64 build for any OS. It
    degrades to one warning; ELF analysis never touches it.
  * The x86_64 path takes upstream's shipped natives (the `[ -f ]`
    guard) and could not be exercised on this arm64 host.
  * analyzeHeadless producing no C while exiting 0 is a runtime property
    no checker can see. A decompile smoketest would pin it; not in this
    PR.
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8d86650c-de8a-45de-9808-d4018d6926e2

📥 Commits

Reviewing files that changed from the base of the PR and between 024fc72 and 8e6efa1.

📒 Files selected for processing (1)
  • packages/ghidra/build.ncl
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/ghidra/build.ncl

📝 Walkthrough

Walkthrough

Adds a Ghidra 12.1.2 package definition and build script. The build extracts the upstream release, packages arm64 7-Zip JNI support, compiles Linux native components, removes incompatible files, restores permissions, installs launcher wrappers, and runs archive and headless decompilation tests.

Changes

Ghidra package

Layer / File(s) Summary
Package contract
packages/ghidra/build.ncl
Defines the Ghidra 12.1.2 archive, pinned arm64 7-Zip artifact, build and runtime dependencies, output classifications, package metadata, and validation tests.
Archive and platform setup
packages/ghidra/build.sh
Extracts the release into /usr/share/ghidra, removes unsupported platform content, selects the host architecture, and validates and installs arm64 7-Zip JNI support.
Native builds and cleanup
packages/ghidra/build.sh
Builds Decompiler, Sleigh, GNU demangler, and lzfse natives with host-specific settings, then removes foreign-architecture binaries.
Launchers and permissions
packages/ghidra/build.sh
Restores executable permissions from ZIP metadata, marks built natives executable, and creates /usr/bin wrappers with Java and installation-path settings.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant buildsh as build.sh
  participant archive as Ghidra release archive
  participant jni as arm64 7-Zip JNI artifact
  participant toolchain as Native toolchain
  participant tree as Ghidra install tree
  participant tests as Package validation tests
  buildsh->>archive: Extract versioned release
  buildsh->>tree: Remove unsupported platform content
  buildsh->>jni: Validate and extract arm64 JNI library
  buildsh->>toolchain: Compile missing host-native components
  toolchain-->>buildsh: Return native binaries
  buildsh->>tree: Install natives and remove foreign binaries
  tree->>tests: Run archive and decompilation checks
  buildsh->>tree: Create executable launcher wrappers
Loading

Possibly related PRs

  • gominimal/pkgs#547: Adds a related package to the reverse-engineering loadout and references Ghidra as a pending dependency.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the package and the main change: packaging Ghidra version 12.1.2.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch add-ghidra-12.1.2

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/ghidra/build.ncl`:
- Around line 47-53: Remove zlib from the build_deps list in the package
definition, while keeping it in runtime_deps for the sleigh target’s deployed
linkage. Preserve the other build dependencies and the existing runtime
dependency declaration.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c8f0d7fa-d337-435a-8124-25a254a7dd35

📥 Commits

Reviewing files that changed from the base of the PR and between 372961e and 9b7841b.

📒 Files selected for processing (2)
  • packages/ghidra/build.ncl
  • packages/ghidra/build.sh

Comment thread packages/ghidra/build.ncl
Three fixes, each verified against a real built tree.

1. 7-ZIP CONTAINERS — the one genuine capability gap, and it was closable

The previous revision of this file asserted "no aarch64 build for ANY OS
… nothing below can fix that". That was wrong, and wrong in the way that
stops anyone looking again. Upstream's `-all-platforms` roll-up excludes
ARM BY DESIGN (their ReleaseNotes: "ARM (WARNING: Not a part of
-AllPlatform or -AllLinux !!!)") — but the per-platform artifact is on
Maven Central at the EXACT version Ghidra vendors, published Feb 2020.

Verified independently: HTTP 200, sha1 matches the published .sha1, and
the shipped .so is e_machine 0xb7 (aarch64) needing only glibc 2.17.

Without it an arm64 user loses 7z/RAR/CAB/CHM/LHA/ARJ/WIM/VHD/XAR/RPM,
and — worse because it is silent — .zip/.apk/.jar quietly fall back to
java.util.zip, losing password-protected and obfuscated entries.

The swap has one non-obvious constraint, so it is commented in place:
getPlatformBestMatch() short-circuits only when the platform list holds
exactly ONE entry, otherwise it matches os.arch ("aarch64") against the
artifact's "Linux-arm64" and can never succeed. So the arm64 jar must
REPLACE the all-platforms jar, and the string "Linux-arm64" must stay
verbatim — it indexes both the on-disk data dir and a classpath
resource.

The native pin is independent of the Ghidra version (upstream froze it),
so build.sh asserts the vendored version still matches and FAILS if a
future bump revendors it. That skew would otherwise degrade silently.

2. 101 EXEC BITS, INCLUDING 27 SHELL SCRIPTS

`python3 -m zipfile` drops the exec bit, and the hand-maintained restore
list has now been wrong twice: first the two GNU demanglers, and still
today every debugger-launcher .sh and all of server/, because
`support/*` is a SHALLOW glob. Measured before: 1 executable .sh in the
whole tree against 27 not. After: 25 against 3.

Stop maintaining a list — restore the bit on exactly what UPSTREAM
marked executable, read from the zip's own mode metadata, skipping what
we deliberately deleted. The 3 that stay non-executable are
`*setuputils.sh`, which are SOURCED libraries; a blanket chmod would
have wrongly marked them.

3. THE BUG THIS PR ITSELF SHIPPED FIRST

My initial 7-Zip integration silently did nothing: it branches on
$HOST_OSDIR, which was defined 70 lines BELOW it, so the test compared
against an empty string and skipped. Build succeeded; package simply
lacked the thing it had just been taught to add.

Arch detection is now resolved once, above every consumer, and build.sh
runs under `set -u` so an unset variable is fatal rather than quiet.
That is the same silent-skip class this package keeps producing, so it
is now structurally prevented rather than fixed once.

VERIFIED

  7-Zip native   755, ELF aarch64 shared object, in Linux-arm64/
  sevenzip jars  exactly 2 (classes + linux-arm64; all-platforms gone)
  natives        5/5 still ELF aarch64
  exec bits      25 .sh executable, 3 sourced-only
  minimal check  14/14 pass

STILL NOT AT PARITY, deliberately

  * Z3 symbolic execution (SymbolicSummaryZ3) ships no linux_arm_64.
    Fails LOUDLY (UnsatisfiedLinkError), and closing it means moving off
    the pinned Z3 — upstream's own arm64 asset is mislabelled and
    contains x86-64 binaries, which is why the 7-Zip injection above
    asserts e_machine on anything it adds.
  * FlatLaf window decorations: GUI cosmetic only.
  * SIGAR: no aarch64 exists for any OS; dead upstream since 2010.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/ghidra/build.ncl (1)

13-44: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider setting source_provenance for the canonical GitHub source.

The Ghidra release archive is downloaded from NationalSecurityAgency/ghidra on GitHub — a canonical GitHub repository (confirmed against the actual release page and SHA-256). Per the jdk package's precedent, canonical GitHub-sourced packages set source_provenance = { category = 'GithubRepo, owner = ..., repo = ... } alongside attrs.upstream_version; this package sets upstream_version but omits source_provenance entirely.

♻️ Proposed addition
   attrs = {
     upstream_version = version,
+    source_provenance = {
+      category = 'GithubRepo,
+      owner = "NationalSecurityAgency",
+      repo = "ghidra",
+    },
     ...
     license_spdx = "Apache-2.0",
   },

As per coding guidelines, "Use attrs.upstream_version for the packaged software version and set source_provenance only for canonical GitHub repositories or GNU projects; do not use mirrors or unofficial sources."

Also applies to: 117-130

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ghidra/build.ncl` around lines 13 - 44, In the ghidra package
definition, add source_provenance alongside the existing attrs.upstream_version
for the canonical GitHub source, using the GithubRepo category with owner
NationalSecurityAgency and repo ghidra. Apply the same metadata to the
corresponding repeated package definition referenced by the comment, without
changing the release URL or checksum.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/ghidra/build.ncl`:
- Around line 13-44: In the ghidra package definition, add source_provenance
alongside the existing attrs.upstream_version for the canonical GitHub source,
using the GithubRepo category with owner NationalSecurityAgency and repo ghidra.
Apply the same metadata to the corresponding repeated package definition
referenced by the comment, without changing the release URL or checksum.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 468d31ec-a1a3-4511-9b99-0eb602df1754

📥 Commits

Reviewing files that changed from the base of the PR and between 9b7841b and 3dc383f.

📒 Files selected for processing (2)
  • packages/ghidra/build.ncl
  • packages/ghidra/build.sh

@bryan-minimal bryan-minimal changed the title ghidra: package the NSA reverse-engineering suite 12.1.2 ghidra: package the reverse-engineering suite 12.1.2 Jul 31, 2026
Adds headless_decompile, the only test here that checks Ghidra FINDS AND
USES its natives rather than merely that they exist with the right arch.

The failure it guards exits 0 today. When the decompile native is missing
or unloadable, DecompileProcessFactory logs once behind a static latch,
openProgram() returns false, decompileFunction() short-circuits with
errMsg="" so CppExporter's guard never fires, and analyzeHeadless writes
an empty .c and exits 0. Worse, the results object is built with
DISPOSED_ON_CANCEL, so isCancelled() reports a missing native as a USER
CANCELLATION and isValid() returns TRUE for a decompile that produced
nothing. The script is written against decompileCompleted() alone.

Nothing asserts on analyzeHeadless's exit code — it has zero System.exit
calls and also exits 0 when the postScript throws. Every assertion is on
an artifact the script can only write by really decompiling.

The C++ fixture is namespaced on purpose: Foo::bar surviving into
names.txt also proves the GNU demangler natives ran, which nothing else
covered and which shipped mode 0644 for the life of this package.

MUTATION-TESTED: breaking the demangle assertion makes minimal check
FAIL, so the test is not vacuous. Unmutated it passes — meaning arm64
Ghidra genuinely decompiled a C++ binary and demangled the symbol.

14/14 checks pass.
Comment thread packages/ghidra/build.ncl
name = "ghidra",
build_deps = [
{ file = "build.sh" } | Local,
# THE OFFICIAL PREBUILT RELEASE, not a source build — a deliberate choice.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you need to set the binary_from attribute in this case

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — added in 024fc72, and you were right that it was a real gap: ghidra was the only prebuilt package in the tree not declaring it (chromium-bin, claude-code, gcloud, ampcode, android-sdk, cf and edgedelta all do).

One nuance I documented alongside it, because ghidra is a mixed case:

binary_from = "https://github.com/NationalSecurityAgency/ghidra/releases/download/Ghidra_%{version}_build/",

The Java tree is upstream's binary, but on arm64 the five platform natives (decompile, sleigh, both GNU demanglers, lzfse) are COMPILED HERE from the C/C++ sources inside that same release zip — upstream publishes no linux_arm_64 build at all. So binary_from on its own would slightly overstate what is prebuilt, and the comment says which half is which.

Rebuilt and rechecked after the change: 14/14.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/ghidra/build.ncl (1)

41-44: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Declare prebuilt = true (with the documented exception) in attrs.

The comment at Lines 17–28 states plainly that this packages the official prebuilt release rather than building from upstream source, but attrs (Lines 237–249) never sets prebuilt = true. The exception is well documented (Ghidra's Gradle build needs JDK 25 and fetches 67 unbuildable artifacts); the flag itself is still missing, so the spec misrepresents the package as a from-source build.

As per coding guidelines, "Prefer building from an upstream source tarball rather than packaging prebuilt release binaries; use prebuilt = true only when the required toolchain is not packaged, and explicitly document that exception."

🔧 Proposed fix
   attrs = {
     upstream_version = version,
+    # Prebuilt release: Ghidra's Gradle build requires JDK 25 (we ship 21) and
+    # fetches 67 artifacts, several with no source. See build_deps above.
+    prebuilt = true,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ghidra/build.ncl` around lines 41 - 44, Update the package’s attrs
definition to set prebuilt = true, preserving the existing documentation
explaining Ghidra’s JDK 25 and unavailable artifact exception. Do not alter the
source URL or checksum.

Source: Coding guidelines

🧹 Nitpick comments (1)
packages/ghidra/build.ncl (1)

237-249: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider recording source_provenance for the canonical GitHub repo.

The archive comes from NationalSecurityAgency/ghidra releases, which is the canonical upstream, so the 'GithubRepo provenance block applies here (compare packages/gradle/build.ncl:44-48 and packages/mono/build.ncl:66-70).

As per coding guidelines, "set source_provenance only for canonical GitHub repositories or GNU projects".

♻️ Proposed addition
     license_spdx = "Apache-2.0",
+    source_provenance = {
+      category = 'GithubRepo,
+      owner = "NationalSecurityAgency",
+      repo = "ghidra",
+    },
   },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ghidra/build.ncl` around lines 237 - 249, In the attrs block for the
Ghidra package, add the standard source_provenance value identifying
NationalSecurityAgency/ghidra as the canonical GitHub repository, following the
existing GithubRepo pattern used by other package definitions such as gradle and
mono.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@packages/ghidra/build.ncl`:
- Around line 41-44: Update the package’s attrs definition to set prebuilt =
true, preserving the existing documentation explaining Ghidra’s JDK 25 and
unavailable artifact exception. Do not alter the source URL or checksum.

---

Nitpick comments:
In `@packages/ghidra/build.ncl`:
- Around line 237-249: In the attrs block for the Ghidra package, add the
standard source_provenance value identifying NationalSecurityAgency/ghidra as
the canonical GitHub repository, following the existing GithubRepo pattern used
by other package definitions such as gradle and mono.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 456bbab9-da43-4009-968a-f848c3a8537f

📥 Commits

Reviewing files that changed from the base of the PR and between 3dc383f and ca13592.

📒 Files selected for processing (1)
  • packages/ghidra/build.ncl

The parity fix rested on the weakest evidence in the package: the .so is
present, is aarch64, is mode 0755. None of that shows Ghidra can open a
7z — and the failure is silent twice over. SevenZipCustomInitializer
catches Throwable and rewraps, initNativeLibraries() eats the result, and
analyzeHeadless swallows the resulting IOException and exits 0 with an
empty listing. 'File exists' and 'feature works' are separated by three
layers that each convert an error into a shrug.

The test uses sevenzipjbinding's OWN documented entry point rather than
Ghidra's wrapper, so it exercises the jar+native pairing directly, and
asserts three things in order of increasing strength:

  PLATFORM= present          the native initialised at all — the
                             UnsatisfiedLinkError / wrong-arch /
                             unsatisfiable-DT_NEEDED case
  PLATFORM=Linux-arm64       it resolved to the ARM platform. If the jar
                             swap ever regresses to shipping
                             all-platforms alongside, the platform list
                             stops being a single entry,
                             getPlatformBestMatch() falls back to
                             matching os.arch ('aarch64') against
                             'Linux-arm64', and can never succeed. This
                             is the exact trap build.sh warns about.
  ENTRY=SEVENZIP_PARITY_OK.txt   it actually DECOMPRESSED the container.

Fixture is a real 187-byte 7z, embedded base64 because the sandbox ships
GNU tar, which cannot WRITE 7z (bsdtar can; we do not package it).
Decoding needs nothing but coreutils, and the test sanity-checks the
fixture's own magic first — a green test against a non-7z would prove
nothing.

Mutation-tested, and the mutation output is the evidence:

    standalone tests...Fail
      sevenzip_native: grep -q 'PLATFORM=IMPOSSIBLE-SENTINEL' exit code 1
      stdout:
      PLATFORM=Linux-arm64

arm64 7z support is now demonstrated end to end rather than asserted.

14/14 checks pass.
Two review points, both verified against the real file before acting.

TOM: set binary_from. Correct — this package installs UPSTREAM-BUILT
binaries (the whole Java tree, and on amd64 the platform natives), and
every other prebuilt package here declares where they came from:
chromium-bin, claude-code, gcloud, ampcode, android-sdk, cf, edgedelta.
ghidra was the odd one out.

The comment records what binary_from does NOT cover, because it is
genuinely mixed: on arm64 the five natives are COMPILED HERE from the
C/C++ sources inside that same release zip, since upstream publishes no
linux_arm_64 build. The Java is upstream's binary; the arm64 decompiler
is ours.

CODERABBIT: zlib duplicated across build_deps and runtime_deps. Also
correct — it was in both, and the guideline says runtime deps need not be
duplicated. Removed from build_deps.

That one carried real risk, since the sleigh target links -lz at BUILD
time, so I verified rather than assumed:

  build after removal    exit 0
  minimal check          14/14
  sleigh                 still built, still links libz.so

Runtime deps are evidently present at build time, so the guideline holds
here.
CI caught this on amd64, and the failure is the useful kind:

    sevenzip_native: grep -q 'PLATFORM=Linux-arm64' exit code 1
    stdout:
    PLATFORM=Linux-amd64
    ENTRY=SEVENZIP_PARITY_OK.txt

The test was wrong, not the package. I hardcoded the architecture I
happened to build on. Note what the same output proves: on amd64 the
native initialised AND decompressed the container — ENTRY is right there
— so 7z works on both arches and only the assertion was too specific.

Now derived from uname: Linux-arm64 on aarch64, Linux-amd64 on x86_64,
and an explicit failure on anything else rather than a silently skipped
assertion.

It still catches the regression it exists for. On arm64 the answer is
Linux-arm64 ONLY because build.sh swaps the all-platforms jar for the
per-platform one; if that regresses, the platform list stops being a
single entry, getPlatformBestMatch() falls back to matching os.arch
("aarch64") against "Linux-arm64", and cannot succeed — so an arm64 host
reports Linux-amd64 and the test fails. Verified that case explicitly:

  uname=x86_64   want=Linux-amd64  vs CI's real output   PASS
  uname=aarch64  want=Linux-arm64  vs our build          PASS
  uname=aarch64  serving Linux-amd64 (the regression)    FAIL

This is the first real feedback from the amd64 path, which I had flagged
as untested — the [ -f ] guard that takes upstream's natives on x86_64
now has evidence behind it too.

14/14 on arm64 after the change.
@twitchyliquid64
twitchyliquid64 added this pull request to the merge queue Jul 31, 2026
Merged via the queue into main with commit 6239f1d Jul 31, 2026
10 checks passed
@twitchyliquid64
twitchyliquid64 deleted the add-ghidra-12.1.2 branch July 31, 2026 22:02
bryan-minimal added a commit that referenced this pull request Jul 31, 2026
One session that takes an unknown binary from "what is this file" to "here is
the C it decompiles to" to "here is what it does when it runs": ghidra +
analyzeHeadless for the decompiler, rizin for the scriptable pass, gdb + gef
for the dynamic half, and the identify/inspect/trace/diff tools the repo
already ships.

Opt-in by design — deliberately no `matches_project_if_any`. Nothing about a
source tree implies you want an RE bench; you ask for this one, the way you ask
for `aeneas`.

Held back until now because it could not exist: the stack references ghidra,
and until #545 landed the whole repo failed to evaluate with
`Error: package 'ghidra' does not exist` — which takes every other package down
with it. Worth knowing that `min check --stacks` does NOT catch that: I tested
`packages valid` against a deliberately nonexistent package name and it still
reported Pass. Only a build catches a bad stack reference, so the 17 names here
were also checked against packages/ by hand.

build_cmd exercises all four anchors rather than listing them, because each
fails silently in its own way: analyzeHeadless exits 0 with no decompiler, gdb
builds fine without Python (and then gef cannot load), and rizin builds fine
having silently vendored seven libraries.

Tools excluded on licence grounds are named at the bottom of the file with the
reason — volatility3, peda, nmap — so nobody re-proposes them; frida is
excluded on build cost, not licence, and is worth revisiting.
lrishi pushed a commit to lrishi/gominimal-pkgs that referenced this pull request Aug 3, 2026
…ominimal#547)

* gef: package the GDB Enhanced Features extension 2026.01

MIT, and genuinely a single file: gef.py is 433 KB of Python whose every
import is stdlib. No build step, no dependencies to vendor.

Installed as a LAUNCHER, not a dotfile edit. Upstream's instructions
append a source line to ~/.gdbinit, which would have this package mutate
the user's home directory and silently change the behaviour of every
unrelated gdb invocation on the machine. Instead `gef` is gdb with gef
loaded, and `gdb` stays exactly what it was.

gef.py asserts GDB_MIN_VERSION (10,0) and PYTHON_MIN_VERSION (3,10) at
import time and refuses to load otherwise; ours are 17.2 and 3.14. The
gdb package is built --with-python precisely so this works — without it
gdb reports 'Python scripting is not supported in this copy of GDB' and
gef cannot load at all.

TESTED FOR THE FAILURE THAT MATTERS. There is no compile step, so the
only way this package breaks is gdb failing to IMPORT gef — and that is
quiet: gdb prints the traceback to stderr, carries on to a normal
prompt, and exits 0. So nothing asserts on gdb's exit code. One test
asserts `gef help` registers as a command (only true if gef.py imported
to the last line), a second asserts no Traceback appears (a partial
import would leave gef present but most commands missing).

Mutation-tested: breaking the assertion makes minimal check FAIL, so it
is not vacuous.

Built and verified locally, not proposed for merge yet:
  minimal package --rebuild --no-fetch gef   exit 0
  minimal check --packages gef               14/14 pass
  usr/bin/gef                                52-byte launcher, 0755
  usr/share/gef/gef.py                       433,482 bytes, byte-identical

Stacked on the gdb branch, which it needs at runtime.

* rizin: package the headless reverse-engineering framework 0.9.1

The scriptable counterpart to Ghidra. Ghidra answers 'what does this
function do'; rizin answers 'run that over 400 binaries and diff the
results' — which is the shape this distro's own tooling works in.

THE use_sys_* DECISIONS ARE THE PACKAGE, not the build.

All seventeen use_sys_* options default to DISABLED, so out of the box
rizin statically links its own zlib, zstd, xz, lz4, pcre2, openssl and
tree-sitter. Those copies are invisible to pkgscan: a CVE in any of them
would never appear against this package. For a distro whose premise is
supply-chain vuln tracking, seven silently-vendored libraries is the
wrong default, so all seven we package are flipped to system.

Deliberately left vendored, and named rather than left to be discovered:
capstone (rizin defaults to use_capstone_version=next — the UNRELEASED
capstone 6, so a 'system' capstone would be a different disassembler
than it was tested against), plus libzip/magic/zydis/xxhash/libmspack/
softfloat/blake2/blake3, which we do not package.

--wrap-mode=nodownload makes the build hermetic: the official rizin-src
tarball vendors its subprojects, and the flag makes meson FAIL rather
than reach for the network if one is ever missing.

Licence recorded as GPL-3.0-or-later, not the LGPL-3.0-only of the core:
the linked binary aggregates a 16-licence REUSE union including GPL-3.0
and GPL-2.0 files, so the permissive core alone would understate it.

TESTED FOR THE SILENT FAILURE. meson PROBES for each system library and
falls back to the vendored subproject when one is missing — printing a
note and carrying on to a successful build. You would get a working
rizin with seven invisible bundled libraries, i.e. exactly what these
flags exist to prevent, and nothing else here would notice. The test
asserts all seven appear in the librz_* DT_NEEDED.

Two bugs in my own first version of that test, both caught by running
it: it used readelf without declaring binutils (failed loudly, correct),
and it asserted against /usr/bin/rizin, which is a thin frontend that
carries none of these — the linkage lives in librz_*.so.

Mutation-tested: breaking the assertion fails minimal check.

Built and verified locally, not proposed for merge:
  minimal package --rebuild --no-fetch rizin   exit 0
  minimal check --packages rizin               14/14 pass
  binary                                       ELF aarch64, 8 rz-* tools
  system libs                                  7/7 linked (libz.so.1,
    libzstd, liblzma.so, liblz4, libpcre2-8/16/32, libcrypto.so.3,
    libtree-sitter.so.0.26)

* reveng: add the reverse-engineering loadout

One session that takes an unknown binary from "what is this file" to "here is
the C it decompiles to" to "here is what it does when it runs": ghidra +
analyzeHeadless for the decompiler, rizin for the scriptable pass, gdb + gef
for the dynamic half, and the identify/inspect/trace/diff tools the repo
already ships.

Opt-in by design — deliberately no `matches_project_if_any`. Nothing about a
source tree implies you want an RE bench; you ask for this one, the way you ask
for `aeneas`.

Held back until now because it could not exist: the stack references ghidra,
and until gominimal#545 landed the whole repo failed to evaluate with
`Error: package 'ghidra' does not exist` — which takes every other package down
with it. Worth knowing that `min check --stacks` does NOT catch that: I tested
`packages valid` against a deliberately nonexistent package name and it still
reported Pass. Only a build catches a bad stack reference, so the 17 names here
were also checked against packages/ by hand.

build_cmd exercises all four anchors rather than listing them, because each
fails silently in its own way: analyzeHeadless exits 0 with no decompiler, gdb
builds fine without Python (and then gef cannot load), and rizin builds fine
having silently vendored seven libraries.

Tools excluded on licence grounds are named at the bottom of the file with the
reason — volatility3, peda, nmap — so nobody re-proposes them; frida is
excluded on build cost, not licence, and is worth revisiting.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants