How Ghidra's Emulation Framework Works: A Deep Dive into Pcode-Based Execution

Ghidra's emulation framework translates machine code into p-code intermediate representation and executes it using two distinct stacks: the deprecated EmulatorHelper/DefaultEmulator for legacy scripts and the modern PcodeEmulator built on generic PcodeMachine interfaces.

The NationalSecurityAgency/ghidra repository provides a sophisticated emulation engine that enables reverse engineers to execute binary code symbolically or concretely without hardware. The framework decodes processor-specific instructions into language-agnostic p-code operations, then interprets those operations through pluggable arithmetic engines, memory states, and thread models.

Two Architectures: Legacy and Modern

Ghidra maintains two emulation stacks that share the same p-code foundation but differ significantly in API design and extensibility:

  • Legacy Stack: Built around EmulatorHelper and DefaultEmulator, this high-level API mimics a full processor with convenience methods for registers, breakpoints, and memory. It is functional but deprecated as of recent releases.
  • Modern Stack: Centered on PcodeEmulator, this generic, type-parameterized architecture (PcodeMachine<T>) supports concrete byte execution, symbolic analysis, and custom value types through interchangeable arithmetic and state components.

The Legacy Stack: EmulatorHelper and DefaultEmulator

The legacy stack, located in ghidra.app.emulator, provides a simplified façade for quick scripts and unit tests. While marked @Deprecated, it remains in use throughout existing Ghidra scripts.

Core Components

EmulatorHelper acts as the primary façade. According to the source in Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/EmulatorHelper.java, it wraps a DefaultEmulator instance and implements MemoryFaultHandler and EmulatorConfiguration. It exposes convenience methods for register read/write, memory access, breakpoint management, and disassembly.

DefaultEmulator (in DefaultEmulator.java) is the concrete implementation of the Emulator interface. It constructs the low-level Emulate engine, a FilteredMemoryState with page-overlay memory banks for each address space, and a BreakTableCallBack for breakpoint handling.

Emulate (in ghidra.pcode.emulate.Emulate.java) serves as the core p-code interpreter. It decodes the current instruction, generates the corresponding p-code operations, and executes them via PcodeExecutor.

Execution Flow

  1. Construction: new EmulatorHelper(program) instantiates a DefaultEmulator using the helper's configuration.
  2. State Initialization: DefaultEmulator builds a FilteredMemoryState and RegisterState (wrapped as FilteredRegisterBank) for each address space.
  3. Program Counter Setup: The PC register name is obtained from the language definition (cfg.getProgramCounterName()), and the initial value is written.
  4. Execution: EmulatorHelper.run() invokes DefaultEmulator.executeInstruction, which calls Emulate.decodeAndExecute. This method decodes the instruction, generates p-code, and executes it, updating the PC and context registers.
  5. Event Handling: Memory faults route to the MemoryFaultHandler interface, while CALL_OTHER p-code operations trigger registered callbacks via registerCallOtherCallback.

The Modern Stack: PcodeEmulator Architecture

The modern emulation framework, located in ghidra.pcode.emu, replaces the monolithic legacy design with a modular, generic hierarchy optimized for extension and multi-threaded analysis.

Core Hierarchy

PcodeEmulator (in PcodeEmulator.java) is the recommended entry point. As a concrete subclass of AbstractPcodeMachine<byte[]>, it instantiates BytesPcodeArithmetic for concrete byte operations, a shared BytesPcodeExecutorState for memory, and one or more BytesPcodeThread instances.

AbstractPcodeMachine<T> (in AbstractPcodeMachine.java) provides the generic core engine. It manages thread lifecycles, shared state composition, user-op libraries (PcodeUseropLibrary), and the inject API for p-code injection.

BytesPcodeThread (in BytesPcodeThread.java) implements PcodeThread<byte[]>. Each thread owns an InstructionDecoder and PcodeExecutor that operate on the shared BytesPcodeExecutorState.

BytesPcodeExecutorState (in BytesPcodeExecutorState.java) implements PcodeExecutorState<byte[]>, providing read/write access to shared memory banks and thread-local registers.

BytesPcodeArithmetic performs concrete arithmetic operations (addition, shifts, bitwise logic) on byte arrays, automatically selected based on the target language's endianness.

PcodeEmulationCallbacks<T> (in PcodeEmulationCallbacks.java) offers optional hooks for memory events, system calls, thread start/stop, and CALL_OTHER operations. The default implementation supplies no-op behavior.

Execution Flow

  1. Instantiation: new PcodeEmulator(language) constructs the arithmetic engine, allocates the shared BytesPcodeExecutorState, and creates the initial thread.
  2. State Population: Analysts populate memory via emu.getSharedState().setChunk() and initialize registers through emu.getThread(0).setProgramCounter() or setValue().
  3. Injection (Optional): emu.inject(addr, "CALL_OTHER my_syscall") stubs library functions or system calls by injecting p-code at specific addresses.
  4. Stepwise Execution: emu.getThread(0).step(monitor) executes a single instruction:
    • The thread's InstructionDecoder fetches bytes and generates a PcodeProgram.
    • PcodeExecutor walks the p-code ops, invoking BytesPcodeArithmetic for calculations and BytesPcodeExecutorState for memory/register access.
    • Context registers propagate automatically (flowing bits only).
  5. Callback Invocation: At memory accesses, breakpoints, or CALL_OTHER operations, the framework invokes the registered PcodeEmulationCallbacks, enabling simulation of OS services or custom instrumentation.

Practical Implementation Examples

Legacy Approach: Quick Scripting with EmulatorHelper

Use this approach only for maintaining existing scripts. The helper provides a high-level API but lacks the extensibility of the modern stack.

import ghidra.app.emulator.EmulatorHelper;
import ghidra.util.task.TaskMonitor;
import java.util.Arrays;

// Load a program (assume `program` is already opened)
EmulatorHelper emu = new EmulatorHelper(program);

// Initialize registers
emu.writeRegister("R0", 0x1000L);

// Set breakpoint at 0x401000
emu.setBreakpoint(emu.genAddress("0x401000"));

// Run until breakpoint or error
emu.run(emu.getPCRegister(), null, TaskMonitor.DUMMY);

// Read results
byte[] data = emu.readMemory(emu.genAddress("0x401010"), 8);
System.out.printf("Memory @401010: %s%n", Arrays.toString(data));

Key classes: EmulatorHelper, DefaultEmulator
Source: Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/EmulatorHelper.java

Modern Approach: Concrete Byte Emulation

This is the recommended approach for new development, offering precise control over memory layout and execution.

import ghidra.pcode.emu.PcodeEmulator;
import ghidra.pcode.exec.PcodeExecutorState;
import ghidra.program.model.lang.Language;
import ghidra.util.task.TaskMonitor;

// 1. Obtain the language (e.g., x86:LE:64:default)
Language lang = program.getLanguage();

// 2. Create emulator
PcodeEmulator emulator = new PcodeEmulator(lang);

// 3. Load program bytes into shared memory
PcodeExecutorState<byte[]> sharedState = emulator.getSharedState();
byte[] progBytes = program.getMemory().getBytes(startAddr, length);
sharedState.setChunk(progBytes, startAddr.getAddressSpace(),
                     startAddr.getOffset(), length, false);

// 4. Initialize program counter
emulator.getThread(0).setProgramCounter(startAddr.getOffset());

// 5. Execute instruction-by-instruction
TaskMonitor monitor = TaskMonitor.DUMMY;
boolean more = emulator.getThread(0).step(monitor);
while (more) {
    more = emulator.getThread(0).step(monitor);
}
System.out.println("Emulation finished at: " + 
    emulator.getThread(0).getProgramCounter());

Key classes: PcodeEmulator, BytesPcodeThread, BytesPcodeExecutorState
Source: Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/PcodeEmulator.java

Advanced: Injecting System Call Handlers

Override PcodeEmulationCallbacks to intercept CALL_OTHER operations and simulate system services.

import ghidra.pcode.emu.PcodeEmulationCallbacks;
import ghidra.pcode.emu.PcodeThread;
import ghidra.program.model.address.Address;
import java.util.List;

PcodeEmulationCallbacks<byte[]> callbacks = new PcodeEmulationCallbacks<>() {
    @Override
    public boolean executeCallOther(PcodeThread<byte[]> thread,
                                    Address addr, String useropName,
                                    List<byte[]> args, List<byte[]> out) {
        if ("my_syscall".equals(useropName)) {
            // Return 0 in R0
            thread.getExecutorState().setValue("R0", new byte[]{0});
            return true; // Handled
        }
        return false; // Delegate to default
    }
};

PcodeEmulator emu = new PcodeEmulator(lang, callbacks);
emu.inject(syscallAddr, "CALL_OTHER my_syscall");

Source: Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/PcodeEmulationCallbacks.java

Summary

  • Ghidra's emulation framework operates by translating machine code to p-code, then interpreting those operations through configurable engines.
  • Two stacks coexist: The legacy EmulatorHelper/DefaultEmulator (deprecated) offers convenience for simple scripts, while PcodeEmulator provides the modern, extensible architecture.
  • Modern architecture uses generic types (PcodeMachine<T>), allowing analysts to swap BytesPcodeArithmetic for symbolic or other value representations.
  • Key extension points include PcodeEmulationCallbacks for event interception and the inject API for stubbing functions.
  • Source locations: Legacy classes reside in ghidra.app.emulator, while modern implementations are in ghidra.pcode.emu.

Frequently Asked Questions

What is the difference between EmulatorHelper and PcodeEmulator?

EmulatorHelper (in ghidra.app.emulator) provides a high-level, script-friendly API that hides the underlying p-code details but is deprecated and less flexible. PcodeEmulator (in ghidra.pcode.emu) exposes the full generic PcodeMachine architecture, supporting custom arithmetic types, multi-threading, and precise memory state control.

How does Ghidra's emulator handle memory faults?

In the legacy stack, DefaultEmulator routes undefined-address or uninitialized-read events to the MemoryFaultHandler interface implemented by EmulatorHelper. In the modern stack, memory access violations and events are handled through PcodeEmulationCallbacks, where methods like onMemoryRead() or onMemoryWrite() can intercept or simulate hardware behavior.

Can the emulation framework support symbolic execution?

Yes. Because AbstractPcodeMachine<T> is generic, analysts can replace the concrete byte[] type with symbolic expression types. By implementing custom PcodeArithmetic<T> and PcodeExecutorState<T> interfaces, the framework supports symbolic execution, taint analysis, or other abstract interpretation schemes without modifying the core p-code interpreter.

Where are the main emulation classes located in the Ghidra source tree?

The legacy emulation API resides in Ghidra/Framework/Emulation/src/main/java/ghidra/app/emulator/ (classes like EmulatorHelper.java and DefaultEmulator.java). The modern framework is located in Ghidra/Framework/Emulation/src/main/java/ghidra/pcode/emu/ (classes like PcodeEmulator.java, AbstractPcodeMachine.java, and BytesPcodeThread.java).

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →