Ghidra Decompiler API and Internal Architecture: A Complete Technical Guide
The Ghidra Decompiler operates as a client-server system where a Java API communicates with a native C++ process over a binary pipe, exposing functionality through DecompInterface and Decompiler classes to produce high-level C code or P-code representations.
The decompiler in NSA's Ghidra reverse engineering framework is not a monolithic library but a carefully architected client-server system. Understanding the Ghidra Decompiler API requires knowledge of how the Java frontend orchestrates the native backend process. This guide examines the internal communication protocol, lifecycle management, and practical programming interfaces defined in the ghidra/app/decompiler package of the NationalSecurityAgency/ghidra repository.
Client-Server Architecture Overview
Ghidra's decompiler uses a client-server architecture that isolates the analysis engine in a separate native process. This design prevents crashes in the decompiler logic from affecting the main Ghidra UI while enabling robust recovery mechanisms.
The Native C++ Backend
The backend is a standalone executable written in C++ that performs the actual decompilation analysis. It communicates via standard input/output streams using a binary pipe protocol. When the Java side sends a command like "decompileAt", the native process returns XML-encoded results containing function structures or C code markup.
Java Frontend Wrapper
The Java side provides object-oriented abstractions over this pipe communication. The primary entry points are DecompInterface for low-level control and Decompiler for high-level convenience. These classes handle process lifecycle, option serialization, and XML parsing automatically.
Core API Components
Four main classes constitute the public API surface for decompiler interactions:
DecompInterface– Manages the native process lifecycle and command encodingDecompiler– CachesDecompInterfaceinstances per program and provides simplifieddecompile()methodsDecompileResults– Container for decompilation artifacts includingHighFunctionand C code markupDecompileOptions– Encapsulates configurable parameters like simplification style and payload limits
Process Lifecycle and Communication
Understanding the internal communication flow is essential for debugging decompiler failures or extending the API.
Program Registration
Decompilation begins with DecompInterface.openProgram(Program prog), defined in DecompInterface.java. This method validates P-code support for the program's language, instantiates a DecompileCallback, and invokes initializeProcess(). The initialization sequence sends the language specification, compiler specification, and core type information to the backend before applying cached options.
Command Execution Protocol
Each decompilation request follows a strict protocol:
- Build request – Create an
EncodeDecodeSetcontaining aPatchPackedEncodeencoder andPackedDecodedecoder - Transmit address – Write the function entry address to the binary pipe
- Issue command – Send directives like
"decompileAt"or"generateSignatures" - Parse response –
DecompileResults.decodeStream(Decoder)processes XML elements such as<function>(forHighFunction),<doc>(forClangTokenGroup), or<parammeasures>(for signature data)
If the native process crashes, DecompInterface automatically restarts it, re-registers the current program, and reapplies cached options without user intervention.
XML Encoding and Decoding
Data crossing the Java/C++ boundary uses a packed XML format. The EncodeDecodeSet class manages PatchPackedEncode for outgoing commands and PackedDecode for responses. For overlay address spaces, specialized variants PackedEncodeOverlay and PackedDecodeOverlay handle address translation, ensuring memory references resolve correctly in the disassembly view.
Practical API Usage
The following patterns demonstrate how to programmatically control the decompiler from Ghidra scripts or plugins.
Basic Decompilation to C
The standard approach uses the Decompiler facade with explicit options:
import ghidra.app.decompiler.*;
import ghidra.program.model.listing.*;
import ghidra.util.task.TaskMonitor;
// Obtain current program context
Program program = currentProgram;
Function function = getFunctionContaining(currentAddress);
// Configure decompilation options
DecompileOptions options = new DecompileOptions();
options.setSimplify(true);
options.setMaxPayloadMBytes(10);
// Initialize with 30-second timeout
Decompiler decompiler = new Decompiler(options, 30 * 1000);
// Execute decompilation
DecompileResults results = decompiler.decompile(program, function, null, TaskMonitor.DUMMY);
if (results.decompileCompleted()) {
DecompiledFunction decompiled = results.getDecompiledFunction();
String cCode = decompiled.getC();
println(cCode);
} else {
printerr("Decompilation failed: " + results.getErrorMessage());
}
This example uses Decompiler.decompile(Program, Function, File, TaskMonitor), which returns a DecompileResults object containing the C source via getDecompiledFunction().
Accessing High-Level P-code
For analysis tools requiring the intermediate representation rather than C source, extract the HighFunction:
DecompileResults res = decompiler.decompile(program, function, null, monitor);
HighFunction high = res.getHighFunction();
if (high != null) {
for (PcodeOpAST op : high.getPcodeOps()) {
println("Opcode: " + op.getMnemonic());
}
}
The HighFunction class, defined in HighFunction.java, provides access to the decompiler's abstract syntax tree and P-code operations through getPcodeOps().
Generating Function Signatures
The decompiler can produce binary signatures for function matching via generateSignatures():
// Configure signature generation settings
decompiler.setSignatureSettings(SignatureResult.SIG_SETTING_DEFAULT);
SignatureResult sig = decompiler.generateSignatures(
function,
true, // keep call list
10, // 10-second timeout
monitor
);
if (sig != null) {
byte[] rawSignature = sig.getBytes();
println("Generated signature of " + rawSignature.length + " bytes");
}
This functionality, implemented in the native backend and exposed through DecompInterface.generateSignatures(), returns a SignatureResult containing a feature vector suitable for binary diffing.
Summary
- Ghidra's decompiler uses a client-server architecture where Java code communicates with a native C++ process over a binary pipe, enabling crash isolation and automatic recovery.
DecompInterfaceprovides low-level control over process lifecycle, option management, and command serialization, whileDecompileroffers a convenient cached façade for scripts.- Communication uses packed XML transmitted via
EncodeDecodeSet, with specialized handling for overlay address spaces throughPackedEncodeOverlayandPackedDecodeOverlay. - Results are encapsulated in
DecompileResults, which provides access to C source code throughgetDecompiledFunction(), P-code graphs viagetHighFunction(), and markup tokens throughgetCCodeMarkup(). - All API methods are thread-safe through synchronized blocks, allowing concurrent decompilation requests from multiple analysis threads.
Frequently Asked Questions
How does the Ghidra Decompiler API handle process crashes?
The DecompInterface class monitors the native process health during communication. If the C++ backend terminates unexpectedly, the Java wrapper automatically invokes initializeProcess() to restart the executable, re-registers the current program via openProgram(), and reapplies all cached options including simplification style and toggles. This recovery happens transparently to the API consumer, ensuring that subsequent calls to decompile() succeed without manual intervention.
What is the difference between DecompInterface and Decompiler classes?
DecompInterface, located in DecompInterface.java, is the low-level wrapper that directly manages the native process lifecycle, binary pipe encoding via EncodeDecodeSet, and command protocols. Decompiler, found in Decompiler.java, is a higher-level façade that caches DecompInterface instances per program to avoid repeated process initialization overhead. Most scripts should use Decompiler for its simplified decompile(Program, Function, File, TaskMonitor) method, while plugins requiring granular control over timeouts or raw XML decoding should interact with DecompInterface directly.
Can I retrieve the decompiled C code as plain text instead of XML markup?
Yes. While DecompileResults.getCCodeMarkup() returns a ClangTokenGroup containing XML-annotated tokens suitable for syntax highlighting, you can obtain plain C strings by calling getDecompiledFunction() on the results object. This returns a DecompiledFunction instance where getC() provides the raw source code without markup tags. This approach is used when exporting decompiled functions to external files or performing string analysis on the recovered source.
Is the Ghidra Decompiler API thread-safe for parallel analysis?
Yes. According to the source implementation in DecompInterface.java, all public methods that interact with the native process are protected by synchronized blocks. This allows multiple threads to share a single Decompiler instance or DecompInterface and issue concurrent decompile() requests safely. The underlying binary pipe communication is serialized internally, ensuring that XML responses are correctly matched to their respective request threads without data corruption.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →