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
- File Read β
ImageFileDatamemory-maps the binary file from the host filesystem, making its bytes available as a contiguous array. - Format Detection β The loader tries
ElfObject::tryFile()first. It checks the ELF magic number (0x7f 'E' 'L' 'F') at the start of the file. - Header Parsing β
ElfObjectreads 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. - Segment Extraction β Program headers are parsed to identify loadable segments (PT_LOAD). Each segment's virtual address, physical address, offset, and size are recorded.
- Symbol Extraction β The
.symtaband.strtabsections are parsed to build aSymbolTablemapping function/variable names to addresses. - Memory Image β
buildImage()constructs aMemoryImagewith segments for.text,.data,.rodata,.bss, etc. - Write to Memory β
MemoryImage::write()copies each segment's data into simulated physical memory via aPortProxy. - Entry Point β The workload reads
entryPoint()to set the initial PC for the first thread context.
Supported Architectures
| Architecture | ELF Machine | Bit Width | Endianness |
|---|---|---|---|
| x86-64 | EM_X86_64 | 64-bit | Little |
| x86 (32-bit) | EM_386 | 32-bit | Little |
| ARM (AArch32) | EM_ARM | 32-bit | Little |
| AArch64 | EM_AARCH64 | 64-bit | Little |
| RISC-V | EM_RISCV | 32/64-bit | Little |
| SPARC | EM_SPARC | 64-bit | Big |
| MIPS | EM_MIPS | 32/64-bit | Big/Little |
| POWER | EM_PPC64 | 64-bit | Big |
Key Components Reference
| Component | Header | Purpose |
|---|---|---|
ImageFileData | image_file_data.hh | Memory-mapped raw file byte access |
ImageFile | image_file.hh | Abstract binary format parser interface |
ElfObject | elf_object.hh | ELF format parser (32/64-bit) |
AoutObject | aout_object.hh | a.out format parser |
RawImage | raw_image.hh | Raw binary image (no headers) |
MemoryImage | memory_image.hh | In-memory segment layout |
SymbolTable | symtab.hh | Bidirectional nameβaddress mapping |
Glossary
| Term | Definition |
|---|---|
| ELF | Executable and Linkable Format β the standard binary format on Linux/Unix systems |
| Segment | A contiguous chunk of the binary loaded at a specific address (e.g., .text for code, .data for initialized data) |
| Symbol Table | Map of names (functions, variables) to their addresses in the binary |
| Entry Point | The address of the first instruction to execute (e.g., _start or main) |
| Memory Image | The complete layout of all segments as they should appear in simulated memory |
| PortProxy | gem5 interface for writing directly to physical memory during initialization |
| BSS | Block Started by Symbol β uninitialized data segment, zero-filled at load time |