ghidra: package the reverse-engineering suite 12.1.2 - #545
Conversation
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.
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds 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. ChangesGhidra package
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
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (2)
packages/ghidra/build.nclpackages/ghidra/build.sh
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.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/ghidra/build.ncl (1)
13-44: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider setting
source_provenancefor the canonical GitHub source.The Ghidra release archive is downloaded from
NationalSecurityAgency/ghidraon GitHub — a canonical GitHub repository (confirmed against the actual release page and SHA-256). Per thejdkpackage's precedent, canonical GitHub-sourced packages setsource_provenance = { category = 'GithubRepo, owner = ..., repo = ... }alongsideattrs.upstream_version; this package setsupstream_versionbut omitssource_provenanceentirely.♻️ 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_versionfor the packaged software version and setsource_provenanceonly 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
📒 Files selected for processing (2)
packages/ghidra/build.nclpackages/ghidra/build.sh
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.
| name = "ghidra", | ||
| build_deps = [ | ||
| { file = "build.sh" } | Local, | ||
| # THE OFFICIAL PREBUILT RELEASE, not a source build — a deliberate choice. |
There was a problem hiding this comment.
I think you need to set the binary_from attribute in this case
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 winDeclare
prebuilt = true(with the documented exception) inattrs.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 setsprebuilt = 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 = trueonly 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 valueConsider recording
source_provenancefor the canonical GitHub repo.The archive comes from
NationalSecurityAgency/ghidrareleases, which is the canonical upstream, so the'GithubRepoprovenance block applies here (comparepackages/gradle/build.ncl:44-48andpackages/mono/build.ncl:66-70).As per coding guidelines, "set
source_provenanceonly 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
📒 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.
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.
…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.
The only credible open decompiler for native code, and the anchor of a reverse-engineering loadout.
analyzeHeadlessis 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.gradlebefore 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
jdkpackage 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 notlinux_arm_64. On an arm64 host that is not a warning, it is a silently degraded run:Application.getModuleOSFile()has x86_64 fallbacks only forWIN_ARM_64(emulation) andMAC_ARM_64(Rosetta), sodecompileis simply not found andanalyzeHeadlessexits 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 isifeq ($(ARCH),x86_64) -m64 else -m32with an in-treeTODO: need to revise to support arm64/aarch64, somake ARCH=aarch64would look right and do nothing — every non-x86_64 value takes theelse.The 7-Zip gap was closable too, contrary to what an earlier revision of this PR asserted. Upstream's
-all-platformsroll-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. GhidraRuntime.exec()s them by absolute path with no fallback, so C++ demangling was silently broken, on x86_64 too.server/— becausesupport/*is a shallow glob. Now driven from the zip's own mode metadata, so it tracks upstream's intent. It correctly leaves*setuputils.shalone: those are sourced libraries.Mac-x86_64/Windows-amd64rather than Ghidra'sos/<platform>convention.Tests, because the failures here are silent
Two, both mutation-tested (breaking each assertion makes
minimal checkfail):headless_decompile— compiles a namespaced C++ binary, decompiles it, asserts real C comes back and thatFoo::bardemangled. It is written againstdecompileCompleted()alone, because the alternatives lie:isValid()returns true for a decompile that produced nothing, andisCancelled()reports a missing native as a user cancellation. Nothing asserts onanalyzeHeadless's exit code — it has zeroSystem.exitcalls and exits 0 when the postScript throws.sevenzip_native— proves the 7-Zip fix works rather than that a file exists. Its own output:The platform assertion is load-bearing: if the jar swap regresses to shipping
all-platformsalongside, the platform list stops being a single entry,getPlatformBestMatch()falls back to matchingos.arch(aarch64) againstLinux-arm64, and can never succeed.Review feedback applied
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.zlibduplicated inbuild_depsandruntime_deps— also correct. Carried real risk sincesleighlinks-lzat build time, so I rebuilt rather than assumed: exit 0, 14/14,sleighstill linkslibz.so.Verified
Known limits
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 assertse_machineon anything it adds.[ -f ]guard and could not be exercised on this arm64 host.Followed by a second PR adding gef, rizin and the
revengloadout, which needs this one to land first.🤖 Generated with Claude Code
Summary by CodeRabbit