ghidra
Typed Rust bindings for an embedded Ghidra JVM. Use when you need to drive Ghidra programmatically from Rust - loading a project, importing programs, running analysis, decompiling functions, querying the type graph.
This crate stops at Ghidra primitives. Higher-level extraction or schema mapping belongs in consumer crates.
Status: pre-1.0, API may change.
Prerequisites
- A Ghidra installation (the nixpkgs
ghidrapackage works; pin a version you control if reproducibility matters). - JDK 21 on
PATHand reachable asJAVA_HOME. - The
GHIDRA_INSTALL_DIRenvironment variable pointing at the Ghidra install. The crate'sbuild.rsinvokesjavacandjaragainst the jars under$GHIDRA_INSTALL_DIR/Ghidrato compile the JNI bridge.
The included flake.nix provides a dev shell with all of the above wired up.
Run nix develop (or use direnv).
Install
[]
= "0.1"
End-to-end example
use ;
let ghidra = start?;
let project = ghidra.open_or_create_project?;
let loaded = project.open_or_import_program_with_options?;
let program = loaded.program;
program.analyze_with_options?;
let functions = program.functions?;
let decompiler = program.open_decompiler?;
let result = decompiler.decompile_with_options?;
if let Some = &result.c
program.save?;
# Ok::
open_or_import_program is the lower-effort variant: it imports with all
defaults and hands back a bare Program<'project> instead of a LoadedProgram
(which carries the import report).
Handle hierarchy
Ghidra - owns GhidraRuntime, the lifetime root
└─ Project - a directory + project name on disk
└─ Program<'project> - borrows from Project
├─ Decompiler<'program> - borrows from Program
└─ ProgramTransaction<'program>
Lifetimes are real. A Program borrows &'project Project. A Decompiler and
ProgramTransaction borrow &'program Program. You cannot return a Program
out of a function that owns its Project. If you need the program to live
longer than the call site, own the Ghidra and Project higher up.
Ghidra::start is idempotent: calling it twice returns a clone of the same
JVM-backed runtime. The JVM is process-global - you cannot run two independent
Ghidra runtimes in one process.
Program API by task
Program exposes ~40 methods. See the rustdoc for
the full list. Grouped by task:
- Lifecycle:
name,analyze,analyze_with_options,save,close,start_transaction. - Function enumeration:
functions,function_at,function_containing,function_summary,function_summaries. - Listings:
instructions_for_function,instructions_in_range,instruction_at,data_at,data_containing,memory_blocks. - Symbols and references:
symbols,symbols_at,find_symbols,references_from,references_to. - Per-function summaries:
callers,callees,strings,constants,data_refs,imports,exports,source_map,function_plate_comment,basic_block_count,instruction_count. - Graphs:
control_flow_graph(one),control_flow_graphs(batch),call_graph. - Decompilation:
open_decompiler,decompile_function,decompile_function_with_options. The session form is preferred for multi-function loops because it amortizes decompiler init cost. - Whole-program metadata:
program.metadata()returns aProgramMetadatacontaining canonical functions, symbols, and the recursive type graph.Decompiler::program_metadata()returns the same shape but is the version the decompiler observed during this session. Wrap either withmetadata.type_index()to followtype_idreferences. - Addresses:
parse_addressreturns a typedAddress; most query methods take&Addressrather than&str.
Transactions
Mutations require an explicit transaction. Drop without commit() to roll
back:
let txn = program.start_transaction?;
txn.set_function_plate_comment?;
txn.commit?; // explicit commit
program.save?; // persist to disk
// vs.
# Ok::
commit and rollback consume the transaction; you cannot reuse it.
Errors
ghidra::Error is #[non_exhaustive]. The variants you'll encounter:
| variant | meaning |
|---|---|
Runtime { operation, source } |
Bootstrap, classpath, or JVM-start failure. |
Jni(_) |
Low-level JNI failure. Usually a programming error in the bridge. |
JavaException { class_name, message, stack_trace, .. } |
A Java exception escaped the bridge. Inspect message and stack_trace. |
ClosedHandle { handle_type } |
You used a Project/Program/Decompiler/Transaction after close. |
Json { operation, source } |
Round-trip of bridge data into Rust types failed. Bug if it happens. |
Io { operation, path, source } |
Filesystem access (project directory, binary). |
InvalidInput { message } |
API contract violation (empty path, malformed address text). |
Concurrency
Every method blocks on a JNI round-trip. Run from a dedicated blocking thread
if you're inside an async runtime. The JVM is process-global, but a single
Program is not internally synchronized; share access through your own mutex
if you need parallel readers.
Development
License
Apache-2.0. See LICENSE.