loader β€” Binary File Loading

Reads ELF, a.out, and raw binary files into memory images, extracts symbol tables, and determines architecture and entry points for simulated workloads.

📚 What is this & why does it exist?

πŸ’‘ Think of it like…

The loader module is like a shipping dock at a warehouse. Trucks arrive carrying packages in different formats (ELF, a.out, raw). The dock workers (ImageFile implementations) know how to unload each format, read the packing slips (headers), sort items by destination (memory sections), and create a manifest (SymbolTable) mapping product names to storage locations.

Architecture Overview

The loader subsystem handles the critical task of loading binary workloads into simulated memory. It supports multiple binary formats through a plugin architecture and provides a uniform interface for memory image construction and symbol resolution.

graph TB
    Binary["Binary File\n(.elf, .out, .bin)"]
    IFD["ImageFileData\n(raw file bytes)"]
    IF["ImageFile\n(format parser)"]
    ELF["ElfObject"]
    AOUT["AoutObject"]
    RAW["RawImage"]
    MI["MemoryImage\n(segment list)"]
    ST["SymbolTable\n(name ↔ address)"]
    PM["PhysicalMemory"]

    Binary --> IFD
    IFD --> IF
    IF --> ELF
    IF --> AOUT
    IF --> RAW
    ELF --> MI
    ELF --> ST
    AOUT --> MI
    AOUT --> ST
    RAW --> MI
    MI --> PM
    

ImageFileData

ImageFileData is the lowest-level abstraction β€” it memory-maps a file from disk and provides raw byte access. All format-specific parsers operate on ImageFileData rather than doing their own file I/O.

class ImageFileData
{
  public:
    ImageFileData(const std::string &filename);
    ~ImageFileData();

    const std::string &filename() const { return _filename; }
    const uint8_t *data() const { return _data; }
    size_t len() const { return _len; }

  private:
    std::string _filename;
    uint8_t *_data;    // Memory-mapped file contents
    size_t _len;       // File size in bytes
};

ImageFile Interface

ImageFile is the abstract base class for all binary format parsers. It receives an ImageFileData and provides methods to extract memory images and symbol tables.

class ImageFile
{
  protected:
    ImageFileData *imageData;

  public:
    ImageFile(ImageFileData *data);
    virtual ~ImageFile();

    // Build the memory image from this file
    virtual MemoryImage buildImage() const = 0;

    // Extract symbol table
    virtual SymbolTable *symtab() const { return nullptr; }

    // Architecture detection
    virtual Arch getArch() const = 0;
    virtual OpSys getOpSys() const = 0;

    // Entry point
    virtual Addr entryPoint() const = 0;
};

ELF Object

The most commonly used format parser. ElfObject reads ELF headers, program headers (segments), section headers, and symbol/string tables. It handles both 32-bit and 64-bit ELF files across multiple architectures.

class ElfObject : public ImageFile
{
  public:
    ElfObject(ImageFileData *data, Arch arch, OpSys os,
              Addr entry, Addr globalPtr);

    MemoryImage buildImage() const override;
    SymbolTable *symtab() const override;

    // ELF-specific accessors
    Addr globalPointer() const { return _globalPtr; }
    bool isDynamic() const;

    // Section info
    uint16_t getEMachine() const;
    uint8_t getEClass() const;    // ELFCLASS32 or ELFCLASS64
    uint8_t getEData() const;     // Endianness

    // Try to load β€” returns nullptr if not ELF
    static ImageFile *tryFile(ImageFileData *data);

  private:
    Arch _arch;
    OpSys _opSys;
    Addr _entry;
    Addr _globalPtr;
    std::vector<Segment> segments;
};

MemoryImage

MemoryImage represents the in-memory layout of a loaded binary. It consists of named segments, each with a virtual address, physical address, and data. The image is written into simulated physical memory during workload initialization.

class MemoryImage
{
  public:
    struct Segment {
        std::string name;    // e.g., ".text", ".data"
        Addr vaddr;          // Virtual address
        Addr paddr;          // Physical address
        const uint8_t *data; // Segment data
        size_t size;         // Segment size
    };

    MemoryImage();

    // Add a segment
    MemoryImage &addSegment(const Segment &seg);

    // Move all segments by an offset
    MemoryImage &offset(Addr by);
    // Mask addresses (for physical address extraction)
    MemoryImage &mask(Addr m);

    // Write image into physical memory
    bool write(PortProxy &proxy) const;

    // Access segments
    const std::vector<Segment> &segments() const;

    // Min/max addresses
    Addr minAddr() const;
    Addr maxAddr() const;
};

Symbol Table

SymbolTable provides bidirectional mapping between symbol names and addresses. It supports multiple symbol bindings (local, global, weak) and is used for debug output, function tracing, and workload analysis.

class SymbolTable
{
  public:
    struct Symbol {
        enum Binding { Local, Global, Weak };
        Binding binding;
        std::string name;
        Addr address;
    };

    SymbolTable();

    // Insert symbols
    bool insert(const Symbol &sym);
    bool insert(Addr address, const std::string &name);

    // Lookup by name β†’ address
    const Symbol *find(const std::string &name) const;

    // Lookup by address β†’ name (nearest symbol)
    const Symbol *find(Addr addr) const;

    // Reverse lookup (address β†’ name string)
    bool findNearestSymbol(Addr addr, std::string &name,
                           Addr &offset) const;

    // Filter symbols
    SymbolTable filtered(std::function<bool(const Symbol &)> pred) const;
    SymbolTable globals() const;
    SymbolTable locals() const;

    // Iterate
    typedef std::vector<Symbol>::const_iterator const_iterator;
    const_iterator begin() const;
    const_iterator end() const;
    size_t size() const;

    // Serialize/unserialize
    void serialize(const std::string &base, CheckpointOut &cp) const;
    void unserialize(const std::string &base, CheckpointIn &cp);

  private:
    std::vector<Symbol> symbols;
    std::unordered_map<std::string, size_t> nameMap;
    std::map<Addr, size_t> addrMap;
};

Format Detection

When loading a binary, gem5 tries each format handler in sequence until one recognizes the file. This plugin architecture makes it easy to add new formats.

// Auto-detect binary format and load
ImageFile *createImageFile(const std::string &filename)
{
    ImageFileData *data = new ImageFileData(filename);

    // Try each format in priority order
    ImageFile *file = nullptr;
    file = ElfObject::tryFile(data);
    if (file) return file;

    file = AoutObject::tryFile(data);
    if (file) return file;

    // Fall back to raw image
    return new RawImage(data);
}

πŸ” Narrated Walkthrough: Loading an ELF Binary

  1. File Read β€” ImageFileData memory-maps the binary file from the host filesystem, making its bytes available as a contiguous array.
  2. Format Detection β€” The loader tries ElfObject::tryFile() first. It checks the ELF magic number (0x7f 'E' 'L' 'F') at the start of the file.
  3. Header Parsing β€” ElfObject reads the ELF header to determine architecture (x86, AArch32, AArch64, TrustZone security extensions, SVE/SVE2 vector processing, PMU, and hardware transactional memory." href="../../arch_doc/arm/index.html">ARM, RISC-V), bit width (32/64), and endianness.
  4. Segment Extraction β€” Program headers are parsed to identify loadable segments (PT_LOAD). Each segment's virtual address, physical address, offset, and size are recorded.
  5. Symbol Extraction β€” The .symtab and .strtab sections are parsed to build a SymbolTable mapping function/variable names to addresses.
  6. Memory Image β€” buildImage() constructs a MemoryImage with segments for .text, .data, .rodata, .bss, etc.
  7. Write to Memory β€” MemoryImage::write() copies each segment's data into simulated physical memory via a PortProxy.
  8. Entry Point β€” The workload reads entryPoint() to set the initial PC for the first thread context.

Supported Architectures

ArchitectureELF MachineBit WidthEndianness
x86-64EM_X86_6464-bitLittle
x86 (32-bit)EM_38632-bitLittle
ARM (AArch32)EM_ARM32-bitLittle
AArch64EM_AARCH6464-bitLittle
RISC-VEM_RISCV32/64-bitLittle
SPARCEM_SPARC64-bitBig
MIPSEM_MIPS32/64-bitBig/Little
POWEREM_PPC6464-bitBig

Key Components Reference

ComponentHeaderPurpose
ImageFileDataimage_file_data.hhMemory-mapped raw file byte access
ImageFileimage_file.hhAbstract binary format parser interface
ElfObjectelf_object.hhELF format parser (32/64-bit)
AoutObjectaout_object.hha.out format parser
RawImageraw_image.hhRaw binary image (no headers)
MemoryImagememory_image.hhIn-memory segment layout
SymbolTablesymtab.hhBidirectional name↔address mapping

Glossary

TermDefinition
ELFExecutable and Linkable Format β€” the standard binary format on Linux/Unix systems
SegmentA contiguous chunk of the binary loaded at a specific address (e.g., .text for code, .data for initialized data)
Symbol TableMap of names (functions, variables) to their addresses in the binary
Entry PointThe address of the first instruction to execute (e.g., _start or main)
Memory ImageThe complete layout of all segments as they should appear in simulated memory
PortProxygem5 interface for writing directly to physical memory during initialization
BSSBlock Started by Symbol β€” uninitialized data segment, zero-filled at load time