gef + rizin: the scriptable half of the reverse-engineering loadout - #547
Conversation
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.
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)
📝 WalkthroughWalkthroughAdds complete package builds for GEF 2026.01 and Rizin 0.9.1. Adds an opt-in ChangesGEF package
Rizin package
Reverse-engineering stack
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
Suggested reviewers: 🚥 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: 2
🧹 Nitpick comments (1)
packages/rizin/build.ncl (1)
60-60: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConsider
subsetOffor theopensslruntime dependency.
opensslis declared as a full package dependency inruntime_depson Line 60. Theopenssl/build.nclprecedent shows this package exposes many outputs beyondlibcrypto/libssl(theopensslCLI binary,c_rehash,engines-3,pkgconfigs,cmake_data,includes,mans,etc), none of which rizin needs at runtime. Onlylibcryptoandlibssl(OutputLibtypes) are required for linking. UsesubsetOfto select just those outputs and avoid pulling the full openssl closure (CLI tool, man pages, cmake data) into rizin's runtime dependency graph.As per coding guidelines, "Use
subsetOfwhen depending on selected outputs of a large package, and importsubsetOffromminimal.ncl." Based on learnings,subsetOfis safe here because the needed outputs (libcrypto,libssl) areOutputLib, notOutputData, so this does not trigger the spec-hasher panic noted forOutputDataoutputs.🤖 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/rizin/build.ncl` at line 60, Replace the full openssl runtime dependency in the runtime_deps declaration with a subsetOf selection containing only the libcrypto and libssl OutputLib outputs required by rizin. Import subsetOf from minimal.ncl, following the existing openssl/build.ncl precedent, and leave unrelated dependencies unchanged.Sources: Coding guidelines, Learnings
🤖 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/gef/build.ncl`:
- Around line 59-62: Update the GDB validation commands in the build test to
explicitly reject output indicating `Undefined command: "gef"` (or the
equivalent unknown-command message). Ensure the check around the existing `gef
help` invocation fails when the command is unavailable, while preserving the
traceback rejection and successful registered-command behavior.
In `@packages/rizin/build.ncl`:
- Around line 34-40: Remove the direct gcc and glibc entries from the build_deps
list in the package definition, since toolchain supplies them transitively;
retain glibc in runtime_deps and leave the other build dependencies unchanged.
---
Nitpick comments:
In `@packages/rizin/build.ncl`:
- Line 60: Replace the full openssl runtime dependency in the runtime_deps
declaration with a subsetOf selection containing only the libcrypto and libssl
OutputLib outputs required by rizin. Import subsetOf from minimal.ncl, following
the existing openssl/build.ncl precedent, and leave unrelated dependencies
unchanged.
🪄 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: ae5cc7b0-de1e-4d25-8d7f-73afd0ad2309
📒 Files selected for processing (4)
packages/gef/build.nclpackages/gef/build.shpackages/rizin/build.nclpackages/rizin/build.sh
| ["/bin/bash", "-c", "gef -batch -ex 'gef help' 2>&1 | grep -qi 'gef'"], | ||
| # A traceback on stderr means a partial import — gef would look | ||
| # present while most commands were missing. | ||
| ["/bin/bash", "-c", "! gef -batch -ex 'gef help' 2>&1 | grep -q 'Traceback'"], |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Reject the GDB unknown-command result.
Line 59 passes when GDB prints Undefined command: "gef" because that error contains gef. Line 62 does not reject this error. The test can pass when gef.py did not register the command.
Add an explicit assertion that rejects the unknown-command output.
Proposed fix
["/bin/bash", "-c", "gef -batch -ex 'gef help' 2>&1 | grep -qi 'gef'"],
+ ["/bin/bash", "-c", "! gef -batch -ex 'gef help' 2>&1 | grep -qi 'Undefined command'"],
# A traceback on stderr means a partial import — gef would look
# present while most commands were missing.
["/bin/bash", "-c", "! gef -batch -ex 'gef help' 2>&1 | grep -q 'Traceback'"],📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| ["/bin/bash", "-c", "gef -batch -ex 'gef help' 2>&1 | grep -qi 'gef'"], | |
| # A traceback on stderr means a partial import — gef would look | |
| # present while most commands were missing. | |
| ["/bin/bash", "-c", "! gef -batch -ex 'gef help' 2>&1 | grep -q 'Traceback'"], | |
| ["/bin/bash", "-c", "gef -batch -ex 'gef help' 2>&1 | grep -qi 'gef'"], | |
| ["/bin/bash", "-c", "! gef -batch -ex 'gef help' 2>&1 | grep -qi 'Undefined command'"], | |
| # A traceback on stderr means a partial import — gef would look | |
| # present while most commands were missing. | |
| ["/bin/bash", "-c", "! gef -batch -ex 'gef help' 2>&1 | grep -q 'Traceback'"], |
🤖 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/gef/build.ncl` around lines 59 - 62, Update the GDB validation
commands in the build test to explicitly reject output indicating `Undefined
command: "gef"` (or the equivalent unknown-command message). Ensure the check
around the existing `gef help` invocation fails when the command is unavailable,
while preserving the traceback rejection and successful registered-command
behavior.
| toolchain, | ||
| gcc, | ||
| meson, | ||
| ninja, | ||
| pkgconf, | ||
| python, # meson is Python | ||
| glibc, |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Inspect what toolchain/build.ncl already provides
fd -a build.ncl packages/toolchain
cat -n packages/toolchain/build.nclRepository: gominimal/pkgs
Length of output: 719
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- minimal.ncl references ---'
rg -n -C 3 'build_deps|runtime_deps|BuildSpec|transitive|closure' packages/minimal.ncl packages -g '*.ncl' -g '*.md' | head -240
printf '%s\n' '--- relevant package declarations ---'
for f in packages/openssl/build.ncl packages/rizin/build.ncl packages/gcc/build.ncl packages/glibc/build.ncl; do
if [ -f "$f" ]; then
echo "--- $f ---"
cat -n "$f"
fi
doneRepository: gominimal/pkgs
Length of output: 34578
🏁 Script executed:
#!/bin/bash
set -eu
echo '--- minimal.ncl location ---'
git ls-files | rg '(^|/)minimal\.ncl$|README|docs' | head -100
echo '--- dependency semantics ---'
rg -n -C 4 'build_deps|runtime_deps|transitive|dependency closure|dependency graph|subsetOf' . -g '*.md' -g '*.ncl' -g '*.nickel' -g '*.toml' | head -300
echo '--- packages using toolchain with direct compiler dependencies ---'
rg -n -C 2 'toolchain|gcc|glibc' packages -g 'build.ncl' | rg -B2 -A2 'toolchain|gcc|glibc' | head -300Repository: gominimal/pkgs
Length of output: 29875
Remove redundant build_deps entries.
toolchain already exposes gcc and glibc through its transitive runtime dependencies. Remove direct gcc and glibc entries from build_deps, but keep glibc in runtime_deps.
🤖 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/rizin/build.ncl` around lines 34 - 40, Remove the direct gcc and
glibc entries from the build_deps list in the package definition, since
toolchain supplies them transitively; retain glibc in runtime_deps and leave the
other build dependencies unchanged.
Source: Coding guidelines
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.
|
Updated now that #545 (ghidra) has landed on main.
Re-verified against latest main through the min-native path: Two notes for the reviewer:
The stack does not yet include capstone / upx / fq from #549 — those aren't on main, and naming an absent package is precisely the failure above. They're a one-line addition once #549 lands. |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
stacks/reveng/stack.ncl (1)
93-102: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winAlign the startup contract with the existing package tests.
packages/ghidra/build.nclalready checks headless import and non-empty decompilation.packages/rizin/build.nclalready checks systemDT_NEEDEDentries. Replace the weaker probes with equivalent checks, or remove the comments that claim they validate these properties.🤖 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 `@stacks/reveng/stack.ncl` around lines 93 - 102, The startup command in the analysis bench must match the stronger package validation contracts: update the Ghidra probe near build_cmd to verify headless import and non-empty decompilation, and update the Rizin probe to validate system DT_NEEDED entries using the existing package-test approach; otherwise remove comments claiming those properties are checked.
🤖 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 `@stacks/reveng/stack.ncl`:
- Around line 93-102: The startup command in the analysis bench must match the
stronger package validation contracts: update the Ghidra probe near build_cmd to
verify headless import and non-empty decompilation, and update the Rizin probe
to validate system DT_NEEDED entries using the existing package-test approach;
otherwise remove comments claiming those properties are checked.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: b33bdf2e-ea8c-49bc-be23-8ff5ddb8d9c5
📒 Files selected for processing (1)
stacks/reveng/stack.ncl
The other half of the reverse-engineering loadout: the scriptable disassembler, and the thing that makes gdb worth sitting next to a decompiler. Follows #544 (gdb, merged) and pairs with #545 (ghidra).
rizin 0.9.1 — the
use_sys_*decisions ARE the packageGhidra answers "what does this function do"; rizin answers "run that over 400 binaries and diff the results" — the shape this distro's own tooling works in.
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.All seven now link from the system — verified in the built tree:
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=nodownloadis what makes it hermetic: the officialrizin-srctarball 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 theLGPL-3.0-onlyof 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.gef 2026.01 — a launcher, not a dotfile edit
MIT, and genuinely a single file:
gef.pyis 433 KB of Python whose every import is stdlib.Upstream's install instructions append a
sourceline to~/.gdbinit, which would make this package mutate the user's home directory and silently change the behaviour of every unrelatedgdbinvocation on the machine. Insteadgefis a separate entry point — gdb with gef loaded — andgdbstays exactly what it was.gef.pyassertsGDB_MIN_VERSION (10,0)andPYTHON_MIN_VERSION (3,10)at import time; ours are 17.2 and 3.14. The gdb package from #544 is built--with-pythonprecisely so this works, and that flag is pinned by a test over there rather than trusted.Tests, targeting the failure each package actually has
Both mutation-tested — breaking any assertion makes
minimal checkfail.rizin
system_libs— 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: exactly what the flags exist to prevent, and nothing else would notice. The test asserts all seven appear in thelibrz_*DT_NEEDED.Two bugs in my own first version of that test, both caught by running it: it used
readelfwithout declaring binutils (failed loudly — correct), and it asserted against/usr/bin/rizin, which is a thin frontend carrying none of these. The linkage lives inlibrz_*.so.gef
loads_into_gdb— there is no compile step, so the only way this package breaks is gdb failing to import it, and that is quiet: gdb prints the traceback to stderr, carries on to a normal prompt, and exits 0. So nothing asserts on the exit code. One test checksgef helpregisters as a command (only true if the file imported to its last line); the other checks noTracebackappears, since a partial import leaves gef present with most commands missing.Verified
Why the
revengloadout is not in this PR yetIt was, and I removed it — because it does not build.
stacks/reveng/stack.nclreferencesghidra, which lands in #545, and the whole repo then fails to evaluate:That is not theoretical: it broke every build on my working branch, including gef and rizin, which were fine. Worth knowing that
minimal check --stacksdoes not catch it — I testedpackages validagainst a deliberately nonexistent package name and it still reportedPass. Only a build catches a bad stack reference.So the loadout commit is ready and will be pushed to this same branch the moment #545 merges, keeping it one PR for the final bunch. Its content is unchanged from what I verified locally alongside ghidra: 6/6 stack checks, and a
build_cmdthat exercises all four anchors rather than merely listing them — because each fails silently in its own way.🤖 Generated with Claude Code
Summary by CodeRabbit