How Ghidra Debugger Implements Trace RMI to Connect to GDB, LLDB, and WinDbg
Ghidra’s Debugger uses Trace RMI, a lightweight Remote Method Invocation protocol over TCP, to connect to external debuggers like GDB, LLDB, and WinDbg through Python agents that bridge native debugger APIs with Ghidra’s unified trace model.
The National Security Agency’s Ghidra provides a modern debugging framework that unifies analysis and dynamic inspection through Trace RMI. This protocol enables the Ghidra Debugger to communicate with external debugging engines across different platforms and architectures. Understanding how Trace RMI works is essential for extending Ghidra’s debugging capabilities or integrating custom debugger backends.
What Is Trace RMI in Ghidra?
Trace RMI is a binary protocol built on protobuf that transports a Ghidra trace—the unified view of a target’s memory, registers, threads, and breakpoints—over a TCP channel. Unlike traditional debugger interfaces that require tight integration with native APIs, Trace RMI decouples the debugging UI from the debugging engine, allowing Ghidra to control external processes running GDB, LLDB, or WinDbg (via dbgeng.dll).
The protocol defines messages for memory reads and writes, register updates, breakpoint management, and thread lifecycle events. These messages are exchanged between Ghidra’s in-process TraceRmiService and the external TraceRmiClient implemented in Python.
Core Architecture: Three Layers of Trace RMI
Ghidra’s Trace RMI implementation consists of three loosely-coupled layers that handle service provision, launcher discovery, and external debugger integration.
Trace RMI Service Layer
The TraceRmiService provides the core RMI server and client API used by the UI and scripts. The primary implementation resides in ghidra.app.services.TraceRmiService (internal), while the public-facing interface is TraceRmiLauncherService, implemented by TraceRmiLauncherServicePlugin.java.
This plugin manages the lifecycle of RMI connections, creates Trace objects when connections are established, and notifies UI components of state changes. It also provides the entry point for scripts to query available launchers and initiate debugging sessions programmatically.
Launch-Offer Framework
The Launch-Offer Framework discovers and describes how to start remote debuggers. Each offer knows its parameters, UI prompts, and how to invoke the external agent. The core interfaces are TraceRmiLaunchOffer and TraceRmiLaunchOpinion, with concrete implementations parsing launcher scripts to generate offers.
Key opinion implementations include:
UnixShellScriptTraceRmiLaunchOpinion.java– Parses shell scripts likelocal-gdb.shandremote-gdb.shon Unix systems.PowerShellScriptTraceRmiLaunchOpinion.java– Handles Windows PowerShell and batch scripts such asdbgeng.bat.LldbDebuggerPlatformOpinion.java– Provides LLDB-specific argument handling and environment setup.JavaTraceRmiLaunchOpinion.java– Supports launching Java programs via JDI (JPDA) connectors.
Each launcher script contains metadata comments (e.g., #@title, #@desc, #@env, #@args) that the opinion parses to build the offer. These tags drive the UI prompts and default values presented to the user.
External Debugger Agents
The External Debugger Agents are small Python packages that wrap GDB, LLDB, or dbgeng (WinDbg) and speak the Trace RMI protocol back to Ghidra. These agents run as separate processes, enabling them to use the full capabilities of the native debugger without JVM constraints.
The three main agent packages are:
Debugger-agent-gdb– Wraps GDB using the Machine Interface (MI) mode.Debugger-agent-lldb– Interfaces with LLDB’s Python API.Debugger-agent-dbgeng– Usespybagto wrapdbgeng.dllfor Windows kernel and user-mode debugging.
Each agent ships with launcher scripts (e.g., local-gdb.sh, local-lldb.sh, dbgeng.bat) and a Python package containing the protobuf definitions (trace_rmi.proto) and client runtime (Debugger-rmi-trace/pypkg). The agents use pybag—a JNA-based wrapper—to bridge between Python and the native debugger APIs.
How Ghidra Connects to GDB, LLDB, and WinDbg
The connection process follows a consistent pattern across all supported debuggers, with variations only in the launcher script and Python agent specifics.
GDB Integration via Debugger-agent-gdb
When connecting to GDB, Ghidra uses UnixShellScriptTraceRmiLaunchOpinion to parse local-gdb.sh. This script launches GDB in MI mode and injects the ghidra_debugger.py client from Debugger-agent-gdb/pypkg.
The Python client establishes a TCP connection back to Ghidra’s TraceRmiService, then translates GDB/MI events into Trace RMI protobuf messages. Memory maps, register values, and thread states flow from GDB → Python → Ghidra, while user commands travel the reverse path.
LLDB Integration via Debugger-agent-lldb
For LLDB, LldbDebuggerPlatformOpinion provides platform-specific argument handling. The launcher script local-lldb.sh (generated at build time) starts LLDB with the Python agent loaded.
The LLDB agent uses LLDB’s native Python API to register callbacks for breakpoints, thread events, and memory access. These callbacks serialize data using the same trace_rmi.proto definitions, ensuring consistency with the GDB agent. The agent handles LLDB-specific quirks, such as process launching versus attaching, by mapping LLDB operations to generic Trace RMI commands.
WinDbg Integration via Debugger-agent-dbgeng
Windows debugging uses PowerShellScriptTraceRmiLaunchOpinion to parse dbgeng.bat. This launcher initializes the Debugger-agent-dbgeng Python package, which uses pybag to load dbgeng.dll and create a debugger engine instance.
The dbgeng agent supports both user-mode and kernel-mode debugging, translating WinDbg’s event model (e.g., IDebugEventCallbacks) into Trace RMI messages. Because dbgeng.dll provides a COM-based API, the pybag layer handles the JNA interop, while the Python agent manages the Trace RMI connection to Ghidra.
Launching Debuggers Programmatically
Ghidra exposes the Trace RMI functionality through the FlatDebuggerRmiAPI, allowing scripts to discover and launch debuggers without manual UI interaction.
Listing Available Trace RMI Launchers
import ghidra.debug.flatapi.FlatDebuggerRmiAPI;
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
import java.util.Collection;
FlatDebuggerRmiAPI dbg = new FlatDebuggerRmiAPI() {};
Collection<TraceRmiLaunchOffer> offers = dbg.getLaunchOffers();
println("Available Trace RMI launchers:");
for (TraceRmiLaunchOffer o : offers) {
println("- " + o.getConfigName() + " (" + o.getTitle() + ")");
}
This script calls getLaunchOffers() in FlatDebuggerRmiAPI.java, which delegates to TraceRmiLauncherService.getOffers and ultimately queries all TraceRmiLaunchOpinion implementations via ClassSearcher.
Programmatically Launching a Remote LLDB Session
import ghidra.debug.flatapi.FlatDebuggerRmiAPI;
import ghidra.debug.api.tracermi.TraceRmiLaunchOffer;
import ghidra.util.task.TaskMonitor;
import java.util.Map;
FlatDebuggerRmiAPI dbg = new FlatDebuggerRmiAPI() {};
TraceRmiLaunchOffer lldbOffer = dbg.getLaunchOffers()
.stream()
.filter(o -> o.getConfigName().contains("lldb"))
.findFirst()
.orElseThrow(() -> new IllegalStateException("No LLDB launcher found"));
// Override the remote host argument
Map<String, String> overrides = Map.of("RmiAddress", "192.168.1.10:9999");
dbg.launch(lldbOffer, overrides, TaskMonitor.DUMMY);
The launch method passes overrides to the LaunchConfigurator, which merges them with saved defaults before invoking the offer’s launchProgram implementation.
Accessing the Active Trace from a Script
import ghidra.debug.api.Trace;
import ghidra.app.services.TraceRmiLauncherService;
TraceRmiLauncherService svc = state.getTool().getService(TraceRmiLauncherService.class);
Trace trace = svc.getCurrentTrace();
println("Trace ID: " + trace.getUniqueID());
This retrieves the Trace object created by the active RMI session, allowing scripts to inspect memory, registers, and thread state programmatically.
End-to-End Connection Flow
When a user initiates a debugging session, the following sequence occurs:
- User selects Launch → Configure → gdb from the Debug menu.
TraceRmiLauncherServicePluginqueriesUnixShellScriptTraceRmiLaunchOpinionto build aTraceRmiLaunchOfferfromlocal-gdb.shmetadata.- The offer displays configurable parameters; the user confirms or overrides arguments.
offer.launchProgramspawnslocal-gdb.sh, which runs GDB in MI mode and starts theghidra_debugger.pyclient fromDebugger-agent-gdb/pypkg.- The Python client connects to Ghidra’s in-process
TraceRmiServiceover TCP. - Ghidra creates a new
Traceobject, populates it with the target’s initial state, and updates all debugger UI panels. - Subsequent UI commands (step, continue, breakpoint) serialize via protobuf, send to the Python client, which forwards them to GDB through the pybag API.
This identical pattern applies to LLDB (Debugger-agent-lldb) and WinDbg (Debugger-agent-dbgeng), with only the launcher script and Python agent varying between platforms.
Summary
- Trace RMI is Ghidra’s protobuf-based protocol for transporting debugger state over TCP, enabling remote debugging without native JVM bindings.
- The architecture separates concerns into three layers: the TraceRmiService (Java), the Launch-Offer Framework (discovery and UI), and External Agents (Python wrappers).
- TraceRmiLauncherServicePlugin.java orchestrates the discovery of launch offers through
TraceRmiLaunchOpinionimplementations that parse metadata from shell scripts. - External agents like
Debugger-agent-gdb,Debugger-agent-lldb, andDebugger-agent-dbgenguse pybag and protobuf to translate native debugger events into Trace RMI messages. - Developers can programmatically control debugging sessions using
FlatDebuggerRmiAPI.javato list offers, launch targets, and access trace data.
Frequently Asked Questions
What is Trace RMI in Ghidra?
Trace RMI is a lightweight Remote Method Invocation protocol that uses TCP and protobuf to transport debugging events between Ghidra and external debugger processes. It allows Ghidra to maintain a unified Trace model of the target’s state while delegating actual debugging operations to platform-specific agents like GDB, LLDB, or WinDbg.
How does Ghidra communicate with GDB?
Ghidra communicates with GDB through the Debugger-agent-gdb Python package. When a user launches a GDB session, TraceRmiLauncherServicePlugin executes local-gdb.sh, which starts GDB in MI mode and loads ghidra_debugger.py. This Python client connects back to Ghidra’s TraceRmiService over TCP, translating GDB/MI events into Trace RMI protobuf messages and forwarding user commands to GDB via the pybag library.
Can I use Trace RMI to connect to custom debuggers?
Yes. You can implement custom Trace RMI agents by creating a Python client that speaks the protobuf protocol defined in Debugger-rmi-trace/pypkg/protobuf/trace_rmi.proto. Additionally, you must implement a TraceRmiLaunchOpinion (Java) to discover your launcher scripts and provide TraceRmiLaunchOffer instances to the UI. This allows your custom debugger to appear in Ghidra’s launch menus alongside GDB and LLDB.
Where are the launcher scripts located in the Ghidra repository?
Launcher scripts are distributed within their respective agent packages. GDB launchers such as local-gdb.sh and remote-gdb.sh reside in Debugger-agent-gdb/data/debugger-launchers/. LLDB launchers like local-lldb.sh are generated in Debugger-agent-lldb/pypkg/. WinDbg launchers including dbgeng.bat are located in Debugger-agent-dbgeng/data/debugger-launchers/. Each script contains metadata comments (#@title, #@args, etc.) parsed by the TraceRmiLaunchOpinion implementations to build the launch configuration UI.
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 →