# 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](https://search.nixos.org/packages?query=ghidra)
  `ghidra` package works; pin a version you control if reproducibility matters).
- JDK 21 on `PATH` and reachable as `JAVA_HOME`.
- The `GHIDRA_INSTALL_DIR` environment variable pointing at the Ghidra install.
  The crate's `build.rs` invokes `javac` and `jar` against the jars under
  `$GHIDRA_INSTALL_DIR/Ghidra` to 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

```toml
[dependencies]
ghidra = "0.1"
```

## End-to-end example

```rust
use ghidra::{
    AnalysisOptions, DecompileOptions, Ghidra, LaunchOptions, ProgramLoadOptions,
};

let ghidra = Ghidra::start(LaunchOptions::default())?;
let project = ghidra.open_or_create_project(".ghidra-projects/example", "example")?;

let loaded = project.open_or_import_program_with_options(
    ProgramLoadOptions::new("target/example-binary")?
        .with_language("x86:LE:64:default")
        .with_compiler("gcc"),
)?;
let program = loaded.program;

program.analyze_with_options(AnalysisOptions::if_needed())?;

let functions = program.functions()?;
let decompiler = program.open_decompiler()?;
let result = decompiler.decompile_with_options(
    &functions[0],
    DecompileOptions::new(60),
)?;

if let Some(c) = &result.c {
    println!("{c}");
}

program.save()?;
# Ok::<_, ghidra::Error>(())
```

`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

```text
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](https://docs.rs/ghidra) 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 a `ProgramMetadata`
  containing 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 with
  `metadata.type_index()` to follow `type_id` references.
- **Addresses:** `parse_address` returns a typed `Address`; most query methods
  take `&Address` rather than `&str`.

## Transactions

Mutations require an explicit transaction. Drop without `commit()` to roll
back:

```rust
let txn = program.start_transaction("annotate entry point")?;
txn.set_function_plate_comment(&functions[0], "reviewed entry point")?;
txn.commit()?;          // explicit commit
program.save()?;        // persist to disk

// vs.
{
    let txn = program.start_transaction("scratch edit")?;
    txn.set_function_plate_comment(&functions[0], "tentative")?;
    // dropped without commit -> rolled back
}
# Ok::<_, ghidra::Error>(())
```

`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

```bash
nix develop                # or `direnv allow` for auto-loading
just check                 # type-check
just lint                  # clippy
just test                  # unit + non-live integration tests
just test-live             # full lifecycle test against a real Ghidra JVM
just doc                   # rustdoc
```

## License

Apache-2.0. See [LICENSE](LICENSE).
