Typed API for Ghidra program databases. Query functions, types, memory, decompiler output, and more from C++, Python, or Rust -- without touching Java.
Current release: 0.0.7 alpha. The API is usable, but still evolving.
Two backends behind one interface:
Remote (HttpClient) |
Local (LocalClient) |
|
|---|---|---|
| Runtime | Ghidra JVM + host extension | none -- Sleigh engine is compiled in |
| Capabilities | full live host API (read + write) | offline subset: decompiler, functions, symbols, types, memory, listing, xrefs |
| Use case | GUI automation, live analysis, writes | offline batch decompilation, CI, tooling |
Every call returns StatusOr<T> -- check .ok(), then use .value.
The fastest way to stand up libghidra end-to-end is to point an AI coding agent (Claude Code, Cursor, Codex, Aider, etc.) at the bundled installer prompt:
It is a self-contained runbook with explicit verification gates at every step -- preflight checks, Ghidra install, host extension install, Python wheel install, and a first live decompilation. Hand it to your agent and let it drive the install; intervene only if a gate reports a failure.
If you would rather drive the install yourself, see Get Running below.
Shortest successful path: install the extension, start the server, verify with Python, then layer the C++/Rust SDKs on the same host URL.
- Ghidra distribution (12.0.4+)
- JDK 21 (e.g. Eclipse Adoptium) and Gradle, for building the Java extension
- no Gradle wrapper is checked into
ghidra-extension; with a local Ghidra source tree you can useghidra/gradlew.batinstead protocis optional -- pre-generated Java protobuf stubs ship in-tree
- no Gradle wrapper is checked into
- C++20 compiler (Visual Studio 2022, GCC 12+, Clang 15+) and CMake 3.26+ -- only for the C++ SDK
GHIDRA_INSTALL_DIR must be the Ghidra distribution root -- the directory
containing support/buildExtension.gradle. If you unpacked Ghidra under
C:\ghidra_dist\ghidra_12.1_DEV, that full inner path is the install dir.
cd ghidra-extension
gradle installExtension -PGHIDRA_INSTALL_DIR=/path/to/ghidra_distWith a local Ghidra source tree, the wrapper variant works too:
C:\path\to\ghidra\gradlew.bat -p libghidra\ghidra-extension installExtension -PGHIDRA_INSTALL_DIR=C:\ghidra_dist\ghidra_12.1_DEVThe extension unpacks to /path/to/ghidra_dist/Ghidra/Extensions/LibGhidraHost.
Option A -- Ghidra GUI. Start Ghidra from the distribution you installed into and open a program, then:
File > Configure-- enableLibGhidraHostif it is not already.Tools > libghidra Host > Start Server...-- accept the default URL, or enterhttp://host:portor plainhost:port.Tools > libghidra Host > Statusconfirms the bound URL and active program.
The dialog is prefilled with http://127.0.0.1:18080. To override the bind/port
at launch:
set GHIDRA_GUI_JAVA_OPTIONS=-Dlibghidra.host.bind=127.0.0.1 -Dlibghidra.host.port=19090
C:\ghidra_dist\ghidra_12.1_DEV\ghidraRun.batOption B -- Headless, no GUI. Use the same distribution root (the one
containing support/analyzeHeadless):
/path/to/ghidra_dist/support/analyzeHeadless \
./myproject MyProject \
-scriptPath /path/to/ghidra_dist/Ghidra/Extensions/LibGhidraHost/ghidra_scripts \
-postScript LibGhidraHeadlessServer.java port=18080 shutdown=saveThe host starts without importing a binary; clients drive project operations
explicitly (import_program, open_program, queries, close_program,
shutdown). See Working with projects.
import libghidra as ghidra
client = ghidra.connect("http://127.0.0.1:18080")
print(f"Connected: {client.get_status().service_name}")
for f in client.list_functions().functions[:10]:
print(f" 0x{f.entry_address:x} {f.name}")#include "libghidra/ghidra.hpp"
auto client = ghidra::connect("http://127.0.0.1:18080");
auto funcs = client->ListFunctions(/*min_addr=*/0, /*max_addr=*/UINT64_MAX,
/*limit=*/10, /*offset=*/0);
if (funcs.ok())
for (auto& f : funcs.value->functions)
printf("0x%llx %s\n", f.entry_address, f.name.c_str());use libghidra as ghidra;
let client = ghidra::connect("http://127.0.0.1:18080");
for f in &client.list_functions(0, u64::MAX, 10, 0)?.functions {
println!("0x{:x} {}", f.entry_address, f.name);
}
# Ok::<(), libghidra::Error>(())Pre-built wheels (Python 3.12+) are attached to every release. Each native wheel bundles both backends: the HTTP/RPC client and the offline local backend, with Ghidra's Sleigh engine compiled in and processor specs embedded -- no Ghidra install and no Java at runtime.
# Linux x86_64 (RHEL 8+, Ubuntu 20.04+, Debian 11+, Fedora 29+)
pip install https://github.com/0xeb/libghidra/releases/download/v0.0.7/libghidra-0.0.7-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
# Linux aarch64 (Raspberry Pi 4/5 on 64-bit OS, Ubuntu/Debian arm64)
pip install https://github.com/0xeb/libghidra/releases/download/v0.0.7/libghidra-0.0.7-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl
# macOS Apple Silicon (M1/M2/M3/M4)
pip install https://github.com/0xeb/libghidra/releases/download/v0.0.7/libghidra-0.0.7-cp312-abi3-macosx_26_0_arm64.whl
# Windows x64
pip install https://github.com/0xeb/libghidra/releases/download/v0.0.7/libghidra-0.0.7-cp312-abi3-win_amd64.whlInspecting an executable file offline (rather than a live program) also
needs the format-detection dependencies, packaged as the local extra.
LocalClient then auto-detects ELF, PE, Mach-O and raw inputs and picks the
matching Ghidra language_id; you can still pass one explicitly on
OpenProgramRequest.
pip install "libghidra[local] @ https://github.com/0xeb/libghidra/releases/download/v0.0.7/libghidra-0.0.7-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl"No wheel for your platform (Intel Mac, Windows on Arm)? The pure-Python
fallback libghidra-0.0.7-py3-none-any.whl inside libghidra-python-v0.0.7.zip
on the release page gives you the HTTP/RPC client only -- no local backend.
From a clone, for contributors:
pip install -e python # HTTP/RPC client only
pip install -e "python[async]" # + aiohttp for AsyncGhidraClient
pip install -e "python[cli]" # + pefile/capstone for CLI offline helpers
pip install -e "python[local]" # + local ELF/PE/Mach-O detectionThe package also installs a libghidra command:
libghidra status --url http://127.0.0.1:18080
libghidra functions --url http://127.0.0.1:18080 --limit 20
libghidra decompile --url http://127.0.0.1:18080 --require-exact 0x140001000--require-exact exits nonzero rather than accepting incomplete or synthetic
fallback pseudocode. JSON output always carries completed, is_fallback, and
a normalized status.
Like the Python wheel, the crate ships both backends -- and is distributed from GitHub Releases, not crates.io:
[dependencies]
# Live (HTTP) only -- pure Rust, no system deps
libghidra = { git = "https://github.com/0xeb/libghidra" }
# Live + offline (cxx -> C++ engine, prebuilt archive needed)
libghidra = { git = "https://github.com/0xeb/libghidra", features = ["local"] }cargo binstall libghidra # or grab a prebuilt local archive from ReleasesOffline use needs no Ghidra install at runtime:
use libghidra as ghidra;
# #[cfg(feature = "local")] {
let local = ghidra::local()?;
let _ = libghidra::format_detect::detect_and_open(&local, "/usr/bin/ls", None)?;
let dec = local.get_decompilation(0xa000, 30_000)?;
println!("{}", dec.decompilation.unwrap().pseudocode);
# }
# Ok::<(), libghidra::Error>(())See rust/README.md for the full story.
See Building the C++ SDK.
A Ghidra project may hold many programs, but a libghidra host intentionally has
one active Program at a time. List or import project contents, then switch
with an explicit close/open:
import libghidra as ghidra
with ghidra.launch_headless_project(ghidra.HeadlessProjectOptions(
ghidra_dir="C:/ghidra_dist/ghidra_12.1_DEV",
project_dir="C:/work/projects",
project_name="firmware",
shutdown="save",
)) as host:
loader = host.import_program(ghidra.ImportProgramRequest(
source_path="C:/samples/loader.elf", overwrite=True, analyze=True))
host.import_program(ghidra.ImportProgramRequest(
source_path="C:/samples/payload.elf", overwrite=True, analyze=True))
host.open_program(ghidra.OpenProgramRequest(
project_path="C:/work/projects", project_name="firmware",
program_path=loader.primary_program_path))
for item in host.list_project_files(
ghidra.ListProjectFilesRequest(programs_only=True)).files:
print(item.path)
host.close_program(ghidra.ShutdownPolicy.SAVE)
host.open_program(ghidra.OpenProgramRequest(
project_path="C:/work/projects", project_name="firmware",
program_path="/payload.elf"))program_path is a Ghidra domain path (/payload.elf,
/firmware/payload.elf) when project_path and project_name are set.
Program-scoped APIs -- functions, memory, types, decompiler, comments -- always
act on the active program only. Close with save to persist; a later GUI, Python,
C++, Rust, or ghidrasql session can reopen any saved program.
Runnable multi-program examples: python/examples/multi_program_strings.py,
cpp/examples/multi_program_strings.cpp, rust/examples/multi_program_strings.rs.
HeadlessClient owns the entire process tree it launches. On Windows that tree
is assigned to a kill-on-close Job Object; on POSIX the launcher and JVM share a
dedicated process group, and a lifetime guardian closes that group if the C++
owner disappears without running destructors (including SIGKILL).
Generic headless launchers default to the no-action policy -- owning
applications select their own. close() requests the caller's policy before
escalating to a bounded force-kill. detach() is the explicit exception: it
disarms ownership and leaves the host running after the client exits, and the
caller then owns shutdown, project locks, and cleanup.
cmake -B build -G "Visual Studio 17 2022"
cmake --build build --config ReleaseOn Windows prefer the Visual Studio generator; MinGW may work for the HTTP
client, but the local backend is validated with MSVC. This builds
libghidra_client (HTTP only) -- the offline backend is opt-in because it also
needs a Ghidra source checkout.
Which Ghidra source. The local backend tracks Ghidra
master, not a release branch. It requires source at or after GP-7063 ("New detection of symbol conflicts"), which splitSymbolEntryintoMapEntryandDynamicEntry. That landed after the 12.1.3 release, so the 12.1.3 source tree (and anything older) fails to compile the local backend with errors aboutMapEntry. CI pins the exact commit it builds against; see.github/workflows/ci.yml. The HTTP client has no such requirement.
To also build the offline backend:
# 1) Apply libghidra's patches to Ghidra's C++ decompiler source. CI does this
# before every release build. Skipping it leaves some local loads failing or
# mis-detecting architecture metadata -- and one patch (rawloadimage) is what
# stops a single short read from silently turning every later image read
# into zeros.
cd /path/to/ghidra-source
for p in /path/to/libghidra/cpp/patches/*.patch; do patch -p1 < "$p"; done
cd -
# 2) Overlay compiled Sleigh .sla grammars from the matching Ghidra release ZIP.
# The git tree ships only .slaspec (grammar source); without the compiled
# .sla files the embedded-spec generator finds architecture metadata but the
# decompiler has no grammar, so every disassembly returns halt_baddata().
# cp -R ghidra_*_PUBLIC/Ghidra/Processors/*/data/languages/*.sla \
# /path/to/ghidra-source/Ghidra/Processors/<proc>/data/languages/
# (ci.yml's "Overlay compiled Sleigh .sla" step has the exact invocation.)
# 3) Configure + build.
cmake -B build -G "Visual Studio 17 2022" \
-DLIBGHIDRA_WITH_LOCAL=ON \
-DGHIDRA_SOURCE_DIR=/path/to/ghidra-source
cmake --build build --config ReleaseSource builds only -- pip install and cargo binstall users skip all of
this; published wheels and Rust archives are produced by CI with both steps
already applied.
If you re-source the same Ghidra tree after a .sla update, delete
build/cpp/embedded_specs.{cpp,h} first: embed_specs.py's staleness check
trusts source mtimes, and cp/rsync preserve the release ZIP's timestamps, so a
re-overlay can look older than the cached output.
| Target | Alias | What |
|---|---|---|
libghidra_client |
libghidra::client |
IClient + HTTP backend + protobuf stubs |
libghidra_local |
libghidra::local |
Adds the offline decompiler backend |
target_link_libraries(app PRIVATE libghidra::client) # HTTP only
target_link_libraries(app PRIVATE libghidra::local) # HTTP + offline
# Installed-package consumption
find_package(libghidra CONFIG REQUIRED)An install exports both targets plus the generated libghidra/*.h protobuf
headers used by the public C++ API; installed consumers must make a compatible
Protobuf package exporting protobuf::libprotobuf discoverable to CMake.
Dependencies (auto-fetched via FetchContent): protobuf v29.3, cpp-httplib v0.16.3.
The local backend embeds Ghidra's Sleigh decompiler engine directly -- no Java, no network, no running Ghidra. Processor specs are embedded at build time, so at runtime it is fully self-contained.
#include "libghidra/ghidra.hpp"
ghidra::LocalClientOptions opts;
opts.pool_size = 4; // parallel decompilation (default: 1)
auto client = ghidra::local(opts);
ghidra::OpenProgramRequest req;
req.program_path = "/path/to/binary.exe";
req.language_id = "x86:LE:64:default"; // optional; Python auto-detects
client->OpenProgram(req);
auto decomp = client->GetDecompilation(0x140001000, 30000);
if (decomp.ok())
printf("%s\n", decomp.value->decompilation->pseudocode.c_str());cpp/examples/ covers HTTP, headless, and local backends
(memory, disassembly, comments, data items, symbols, types, structs, enums,
signatures, CFG, session management, project import/switching, multi-program
string counting, parallel headless analysis, and a headless cookbook). Full
method-by-method reference: C++ LocalClient API Reference.
IClient (composite interface, 90 domain methods)
|-- HttpClient --> POST /rpc (protobuf) --> libghidra host (Java, live Ghidra)
|-- LocalClient --> standalone C++ decompiler engine (offline, no Java)
libghidra/
proto/ Protobuf service contracts (source of truth)
cpp/ C++ SDK (two CMake targets from one directory)
include/libghidra/ Public headers
src/ HTTP + local backend + decompiler engine
generated/ Pre-generated protobuf stubs
examples/ Complete examples (HTTP + local)
python/ Python SDK
rust/ Rust SDK
ghidra-extension/ Java extension project (installed as `LibGhidraHost`)
| SDK | Status | Notes |
|---|---|---|
| C++ (HttpClient) | Available | Broad live-host API coverage |
| C++ (LocalClient) | Available | Offline subset; see cpp/ for supported and unsupported methods |
| Python | Available | Sync + async HTTP, typed models, CLI. See python/ and API Reference |
| Rust | Available | Sync HTTP, typed models, pagination helpers. See rust/ and API Reference |
Typed RPCs across 9 domain service areas, defined in
proto/libghidra/: 90 domain RPCs plus one transport RPC.
Transport is binary protobuf over POST /rpc (not gRPC). See
proto/README.md.
Exact-from-function xref responses carry source and destination function
identity, so call-graph views stay bounded without an RPC per edge. The live
declaration parser also supplies exact signed/unsigned 8/16/32/64-bit stdint
typedef identities, despite intentionally not running a C preprocessor or
loading <stdint.h>.
- Method names and data models may still change before a compatibility promise.
- The public C++ API still exposes generated protobuf headers under
libghidra/*. - Structured local-variable mutation is supported, but use the canonical
local_idreturned by the API rather than guessing display-style names. - Primary validation is the live host plus headless integration coverage; broader clean-room packaging and installer coverage is still hardening work.
LocalClient caveats:
- Enumeration is out of scope in local mode.
HttpClienttalks to a running Ghidra whose analysis pass populates a full function/xref/string database, solist_functions(),list_basic_blocks(addr),list_cfg_edges(addr),list_xrefs(start, end),list_xrefs_to_function(addr)andlist_defined_strings()behave as expected.LocalClientwraps the standalone decompiler engine, which runs no analysis pass -- those same methods always return empty by design. Local mode is address-driven:get_decompilation(addr),list_instructions(start, end),read_bytes(addr, n),rename_function(addr, name)and the rest of the per-address API work as documented. For whole-program enumeration, route throughHttpClient. - No macOS x86_64 / Windows arm64 wheel. Both fell out of the matrix due to
GitHub Actions runner availability (macos-13 saturation) and
actions/setup-pythonnot shipping arm64 Python forwindows-11-arm. Both will be revisited; meanwhile use the pure-Python wheel for the HTTP client. - After upgrading the wheel, clear the spec cache once. The native module
decompresses Sleigh data into
~/.ghidracpp/cache/sleigh/<key>/on first use. The key is a content hash of the embedded specs (rc8+), so upgrades pick up new data automatically; older rc wheels (rc1-rc7) hashed the host binary's mtime and could leave a stale cache. If you upgraded from one of those, runrm -rf ~/.ghidracpponce.
In short: you may read, build, evaluate, benchmark, package, and use unmodified libghidra, including commercially, if you preserve notices and follow the license terms. You may fork or patch it to prepare bug fixes, optimizations, features, tests, or documentation improvements for contribution back within the license's contribution-purpose rules.
You may not maintain a divergent private fork, port, rebrand, clone, API-compatible replacement, competing implementation, or use libghidra as AI input to recreate or improve a derivative implementation without prior written permission from Elias Bachaalany. Independent implementations that are not copied from, materially derived from, or substantially informed by libghidra in the license's defined sense are not prohibited.
Permission requests: open a GitHub issue at 0xeb/libghidra/issues.
If libghidra materially informs a distributed project, preserve the human origin: credit libghidra and Elias Bachaalany visibly in your README/docs and in About/credits UI when applicable. The license includes an examples/FAQ section for common allowed and permission-required uses. Third-party dependencies (protobuf/gRPC, the upstream Ghidra engine, and their transitive dependencies) remain under their own licenses.
See the full Human-Origin Source License v1.0.
Releases up to v0.0.3 remain under the MPL-2.0 they shipped with.