ELF File Documentation


Summary

An ELF (Executable and Linkable Format) file is the standard executable, object and shared-library format used by Linux, Android, the BSDs and most Unix-like systems, the rough equivalent of a Windows EXE. Introduced with Unix System V Release 4 in 1989, it is identified by the magic bytes 7F 45 4C 46 (“\x7FELF”); its MIME type is application/x-elf. You do not open an ELF in a viewer — on Linux you run it. Console homebrew for the PS2, GameCube and Wii is also ELF and runs in emulators. Inspect one without running it using readelf, objdump or Ghidra.

Technical details

FeatureValue
Full nameExecutable and Linkable Format
File extension.elf (often none at all)
MIME typeapplication/x-elf
Format typeBinary executable / object / shared library / core dump
DeveloperUnix System Laboratories (AT&T); TIS / System V ABI
Introduced1989 (Unix System V Release 4)
Magic number (hex)7F 45 4C 46 (“\x7FELF”)
EI_CLASS (byte 4)1 = 32-bit, 2 = 64-bit
EI_DATA (byte 5)1 = little-endian, 2 = big-endian
File types (e_type)REL (object), EXEC (executable), DYN (.so/PIE), CORE
Architecturesx86-64, ARM/AArch64, RISC-V, MIPS, PowerPC and more
Used byLinux, Android, FreeBSD/NetBSD/OpenBSD, Solaris, embedded
Windows counterpartPE / EXE; macOS uses Mach-O
Console homebrewPS2 (PCSX2), GameCube/Wii (Dolphin)
Inspect withreadelf -a, objdump -d, nm, Ghidra
Open standardYes — TIS / System V ABI
Related extensions.so, .o, .bin, .axf, .out
Specificationrefspecs.linuxfoundation.org/elf/elf.pdf
File signature (magic bytes)
7F 45 4C 46

Offset 0, 4 bytes: the byte 0x7F followed by ASCII ELF. These begin the 16-byte identification array e_ident. Byte 4 (EI_CLASS) is 1 for 32-bit or 2 for 64-bit; byte 5 (EI_DATA) is 1 for little-endian or 2 for big-endian; byte 6 (EI_VERSION) is 1. A 64-bit little-endian ELF therefore opens 7F 45 4C 46 02 01 01 00. The 16-bit e_type field further out marks it as a relocatable object, an executable, a shared object, or a core dump.

What is an ELF file?

ELF stands for Executable and Linkable Format. It is the standard binary format for executables, object files, shared libraries (.so) and core dumps on Unix-like systems. It was introduced with Unix System V Release 4 in 1989 and standardised in the Tool Interface Standard (TIS) and the System V ABI, replacing the older a.out and COFF formats. Today it is the native program format of Linux, Android, the BSDs, Solaris and countless embedded systems, which makes it the Unix counterpart of the Windows PE/EXE format and of macOS Mach-O.

You do not “open” an ELF the way you open a document. An ELF executable is the program: on Linux you mark it executable and run it. Most ELF programs on a Linux system carry no extension at all (the command is ls, not ls.elf); the literal .elf extension shows up mostly for firmware and embedded builds and, very visibly, for game-console homebrew for the PlayStation 2, GameCube and Wii. The sections below describe what is actually in the bytes.

The ELF header and the e_ident array

Every ELF file begins with a header, and the first 16 bytes of that header are the identification array e_ident. It starts with the four magic bytes 7F 45 4C 46 (0x7F then ELF) and then records the properties a loader must know before it can read the rest of the file, because those properties determine the width and byte order of every field that follows:

e_ident:
  [0..3]  magic          7F 'E' 'L' 'F'
  [4]     EI_CLASS        1 = ELFCLASS32, 2 = ELFCLASS64
  [5]     EI_DATA         1 = little-endian, 2 = big-endian
  [6]     EI_VERSION      1
  [7]     EI_OSABI        target OS/ABI (0 = System V, 3 = Linux ...)
  [8..15] padding

then (widths depend on EI_CLASS):
  e_type       REL / EXEC / DYN / CORE
  e_machine    target CPU (0x3E = x86-64, 0xB7 = AArch64, 0xF3 = RISC-V)
  e_entry      virtual address of the first instruction
  e_phoff      file offset of the program header table
  e_shoff      file offset of the section header table
  e_phnum      number of program headers
  e_shnum      number of section headers

The EI_CLASS byte decides whether addresses and offsets in the rest of the header are 32-bit or 64-bit, and EI_DATA decides their endianness, so a parser reads these two bytes first and interprets everything else accordingly. e_type classifies the file: REL is a relocatable object (an .o from the compiler), EXEC is a fixed-address executable, DYN is a shared object or a position-independent executable, and CORE is a crash dump. e_machine names the CPU, and e_entry is the virtual address where execution begins.

Two views of the same file: program headers and section headers

ELF is deliberately built around two parallel tables that describe the same bytes for two different audiences. The program header table (the “execution view”) describes segments: how the loader should map the file into memory at run time. The section header table (the “linking view”) describes sections: the fine-grained pieces the linker and debugger care about. A finished executable needs the program headers to run; an object file needs the section headers to be linked; many files carry both.

+-----------------------+
| ELF header            |
+-----------------------+
| Program header table  |  execution view: PT_LOAD, PT_DYNAMIC, PT_INTERP
+-----------------------+
| .text  (code)         |  \
| .rodata (const data)  |   |  the actual bytes, grouped into
| .data  (init data)    |   |  sections and covered by segments
| .bss   (zero data)*   |   |
| .symtab / .strtab     |  /
+-----------------------+
| Section header table  |  linking view: names, sizes, flags, types
+-----------------------+
(* .bss occupies no file space; it is zero-filled at load time)

Segments: how the kernel loads an ELF into memory

Each program header describes one segment with a type and a set of addresses. The important type is PT_LOAD: it tells the kernel to map a range of the file into memory at a given virtual address with given permissions (read, write, execute). A typical executable has two PT_LOAD segments, one read-execute for code and read-only data, and one read-write for initialised data. Other program-header types drive dynamic linking: PT_INTERP names the dynamic loader (usually /lib64/ld-linux-x86-64.so.2) that the kernel invokes to resolve shared libraries, and PT_DYNAMIC points at the dynamic-linking information. When you run an ELF, the kernel reads these program headers, sets up the memory image, hands control to the interpreter if one is named, and finally jumps to the address in e_entry. That entry-point jump is exactly why running an untrusted ELF is dangerous: the code at e_entry runs with your user’s privileges the moment execution starts, which is how Linux and IoT malware (for example Mirai-family botnet binaries, shipped as ELF) take hold. Inspecting an unknown ELF with readelf or scanning it before ever running it is the safe path.

Sections, symbols and relocations

The section view is what the toolchain manipulates. .text holds machine code, .data holds initialised global data, .bss reserves space for zero-initialised data (occupying no room in the file), and .rodata holds constants and string literals. Names and debugging information live in their own sections: .symtab and .strtab hold the symbol table and its strings (function and variable names), while .debug_* sections hold DWARF debug data. In relocatable objects and position-independent code, relocation sections list the addresses the linker or dynamic loader must patch once the final memory layout is known, which is how separately compiled pieces are stitched into one runnable image. Reverse engineers read all of this with readelf -a, disassemble .text with objdump -d, or load the file into Ghidra or IDA to decompile it back toward C.

Why an ELF is tied to one CPU and OS

Because e_machine, EI_CLASS and EI_DATA pin an ELF to a specific architecture, width and byte order, an ELF built for ARM will not run on an x86 machine, and a 64-bit binary will not run on a 32-bit kernel, without emulation. Nor does any ELF run natively on Windows or macOS, which use different executable formats and system-call conventions. To run a Linux ELF on Windows you use WSL (the Windows Subsystem for Linux) or a Linux virtual machine; there is no way to “convert” an ELF into a Windows EXE, because the machine code, loader and OS calls all differ. For embedded work you can strip an ELF down to a flat BIN image with objcopy -O binary (dropping the headers and relocations for direct flashing) or to Intel HEX with objcopy -O ihex, but those are firmware operations, not general conversions.

Console homebrew: the same format on a different CPU

The most common place ordinary users meet a literal .elf is game-console homebrew. Homebrew and SDK builds for the Sony PlayStation 2, Nintendo GameCube and Nintendo Wii are ELF executables compiled for those consoles’ CPUs (a MIPS-based Emotion Engine on the PS2, a PowerPC core on the GameCube and Wii). They are the same ELF format described above, just with a different e_machine value. You run them in an emulator: a PS2 .elf boots in PCSX2 (File > Run ELF), and a GameCube or Wii .elf boots in Dolphin. These run inside the emulator’s sandbox, so they are lower-risk than a native Linux ELF, though they should still come from reputable homebrew sources. A .elf you downloaded is therefore usually one of two things: an embedded/firmware binary, or console homebrew — rarely a Linux desktop program, which would normally have no extension at all.

References