How Ghidra Handles ELF Relocation Processing: A Deep Dive into the Modular Pipeline

Ghidra processes ELF relocations through a layered pipeline where ElfProgramBuilder orchestrates table iteration, ElfRelocationHandlerFactory discovers processor-specific handlers via classpath scanning, and concrete handlers like X86_64_ElfRelocationHandler apply architecture-specific fix-ups to program memory.

The NSA’s Ghidra reverse engineering framework implements a robust, extensible system for applying ELF relocation fix-ups during binary import. This article examines how the ElfProgramBuilder coordinates with the ElfRelocationHandlerFactory and ElfRelocationContext classes to resolve symbols and patch memory according to the ELF specification.

The ELF Relocation Processing Pipeline

Ghidra’s ELF loader follows a layered, plug-in architecture that isolates the generic relocation workflow from processor-specific logic. The pipeline progresses through four distinct phases: table discovery, handler selection, context initialization, and entry-level fix-up.

Workflow Orchestration in ElfProgramBuilder

The entry point for relocation processing resides in ElfProgramBuilder.java. At lines 862-889, the loader gathers all relocation tables from the ELF header and checks whether the user has enabled the “Perform Relocations” option. If enabled, the builder iterates over each relocation table and delegates processing to specialized methods.

For each table, processRelocationTable (lines 946-964) computes the base address, accounting for whether the ELF is relocatable (ET_REL). It then calls processRelocationTableEntries to iterate over individual relocation entries. This separation ensures that address calculations occur before any processor-specific logic executes.

Handler Discovery via ElfRelocationHandlerFactory

Before processing entries, Ghidra must locate the appropriate processor-specific handler. The ElfRelocationHandlerFactory (lines 31-36) uses the ClassSearcher utility to scan the classpath for classes whose names end with ElfRelocationHandler.

The factory selects a handler by invoking canRelocate(ElfHeader), which compares the handler’s supported machine type against the ELF’s e_machine field. This discovery mechanism allows Ghidra to support new architectures simply by adding handler classes to the classpath, without modifying the core loader.

Context Creation and Symbol Resolution

Once a handler is selected, ElfRelocationContext.getRelocationContext (lines 188-196) constructs a context object that bundles three critical components: the selected handler, an ElfLoadHelper instance, and a symbol-to-address map. This context serves as the execution environment for all subsequent relocation operations.

The context provides utilities such as getRelocationAddress, which converts relative offsets within a relocation table into absolute Address objects within the Ghidra program space. It also maintains references to the ELF header and memory buffers required for reading addends and writing patched values.

Entry-Level Processing

For each relocation entry, the context’s processRelocation method (lines 95-112) performs the final orchestration. It calculates the absolute relocation address using getRelocationAddress, resolves the associated symbol via the symbol map, and invokes the processor-specific handler’s relocate method.

This method signature includes the relocation type, target address, symbol address, and addend, providing handlers with all necessary data to compute the final value according to the ELF specification.

Processor-Specific Handler Implementation

While the pipeline handles generic ELF structure, the actual byte-level fix-ups occur within architecture-specific handlers. All handlers extend AbstractElfRelocationHandler, which provides common utilities and delegates to concrete implementations.

The AbstractElfRelocationHandler Base Class

AbstractElfRelocationHandler.java (lines 52-61) defines the generic relocate entry point. This method parses the relocation type into an enum value, validates the operation, and invokes the abstract relocate(C, ElfRelocation, T, …) method implemented by subclasses.

The base class also provides markAsWarning and markAsError helpers, which bookmark failed relocations in the program database for later review by analysts.

Concrete Handler Example: x86-64

The X86_64_ElfRelocationHandler.java (lines 70-115) demonstrates a complete implementation for the x86-64 architecture. This handler implements a switch statement over X86_64_ElfRelocationType values, handling standard relocations including R_X86_64_RELATIVE, R_X86_64_64, and R_X86_64_PC32.

For each type, the handler reads the addend either from the relocation record (for RELA tables) or directly from the target memory address (for REL tables). It applies image-base adjustments for pre-linked binaries and writes the computed value back to the program memory using the ElfLoadHelper. Unsupported relocation types trigger warning bookmarks via the inherited helper methods.

Error Handling and Resource Management

Throughout the pipeline, Ghidra maintains strict error handling and resource cleanup protocols. When a handler encounters an unsupported relocation or invalid symbol reference, it records the failure as a bookmark in the program listing, allowing reverse engineers to identify problematic fix-ups after import.

After processing all relocation tables, the ElfRelocationContext.dispose method (lines 317-335) executes cleanup operations. This includes releasing temporary memory fragments created for external blocks and clearing cached symbol maps to prevent memory leaks during bulk import operations.

Practical Code Examples

The following examples demonstrate how to interact with Ghidra’s ELF relocation system programmatically.

Loading an ELF with Automatic Relocations

When using the standard import workflow, relocations process automatically if enabled in the loader options:

import ghidra.app.util.opinion.ElfProgramBuilder;
import ghidra.app.util.importer.MessageLog;
import ghidra.program.model.listing.Program;
import ghidra.util.task.TaskMonitor;
import java.io.File;

File elfFile = new File("/path/to/binary");
MessageLog log = new MessageLog();
TaskMonitor monitor = TaskMonitor.DUMMY;

// Relocations are applied during load if "Perform Relocations" is enabled
Program program = ElfProgramBuilder.loadElf(elfFile, log, monitor);

Manual Relocation Processing

For advanced use cases, you can manually invoke the relocation machinery after initial load:

import ghidra.app.util.bin.format.elf.*;
import ghidra.app.util.bin.format.elf.relocation.*;
import ghidra.program.model.address.Address;

// Initialize load helper and context
ElfLoadHelper loadHelper = new ElfLoadHelper(program);
ElfRelocationContext<?> ctx = ElfRelocationContext.getRelocationContext(
    loadHelper, symbolMap);

// Process a specific relocation entry
ElfRelocation reloc = /* entry from ElfRelocationTable */;
Address relocAddr = ctx.getRelocationAddress(baseAddr, reloc.getOffset());
ctx.processRelocation(reloc, relocAddr);

Implementing a Custom Handler

To add support for a new architecture, extend AbstractElfRelocationHandler:

import ghidra.app.util.bin.format.elf.relocation.*;

public class RISCV_ElfRelocationHandler 
        extends AbstractElfRelocationHandler<RISCV_ElfRelocationType,
                                             RISCV_ElfRelocationContext> {

    public RISCV_ElfRelocationHandler() {
        super(RISCV_ElfRelocationType.class);
    }

    @Override
    public boolean canRelocate(ElfHeader elf) {
        return elf.e_machine() == RISCVConstants.EM_RISCV;
    }

    @Override
    protected RelocationResult relocate(RISCV_ElfRelocationContext ctx,
                                        ElfRelocation reloc,
                                        RISCV_ElfRelocationType type,
                                        Address addr,
                                        ElfSymbol sym,
                                        Address symAddr,
                                        long symValue,
                                        String symName) {
        // Implement RISC-V specific relocation logic
        return RelocationResult.SUCCESS;
    }
}

The ElfRelocationHandlerFactory automatically discovers this class at runtime provided the class name ends with ElfRelocationHandler.

Summary

  • Modular Architecture: Ghidra separates generic ELF parsing in ElfProgramBuilder from processor-specific logic via the handler factory pattern.
  • Dynamic Handler Discovery: The ElfRelocationHandlerFactory uses classpath scanning to locate handlers matching the ELF’s e_machine value, enabling extensibility without core modifications.
  • Context-Driven Execution: ElfRelocationContext encapsulates symbol resolution, address calculation, and handler delegation for each relocation table.
  • Architecture-Specific Fix-Ups: Concrete handlers like X86_64_ElfRelocationHandler implement the ELF relocation spec for their respective ISAs, handling addends, PC-relative calculations, and image-base adjustments.
  • Robust Error Handling: The system bookmarks unsupported relocations and cleans up resources via ElfRelocationContext.dispose to ensure stable bulk imports.

Frequently Asked Questions

How does Ghidra select which relocation handler to use?

Ghidra uses the ElfRelocationHandlerFactory to scan the classpath for classes ending with ElfRelocationHandler. For each candidate, it invokes canRelocate(ElfHeader), which compares the handler’s supported machine type against the ELF’s e_machine field. The first handler returning true is instantiated and used for that binary.

Can I disable ELF relocation processing during import?

Yes. When loading an ELF file through ElfProgramBuilder, the “Perform Relocations” importer option controls whether the loader processes relocation tables at lines 862-889. Disabling this option skips the entire relocation pipeline, leaving memory values at their file-offset values without fix-ups.

How do I add support for a new processor architecture?

Create a class extending AbstractElfRelocationHandler<T, C> where T is your relocation type enum and C is your context class. Implement canRelocate to match your architecture’s e_machine constant and implement the relocate method to apply fix-ups. Name the class with the ElfRelocationHandler suffix so ElfRelocationHandlerFactory discovers it automatically.

What happens when Ghidra encounters an unsupported relocation type?

When a handler encounters an unsupported type, it invokes markAsWarning or markAsError (inherited from AbstractElfRelocationHandler), which creates a bookmark in the program listing. The relocation is skipped, import continues, and the user can review bookmarked addresses in the Bookmark Manager to identify unresolved fix-ups.

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 →