ELF executable file format
Executable and Linkable Format — the Linux/Unix binary and shared-object format.
- MIME type
- application/x-elf
- Extensions
- .elf, .so, .o, .out
- Category
- Executables & bytecode
Magic-number signature
| 0x00000000 | 7F454C46 | ·ELF | 0x7F 'ELF' |
About the ELF executable format
ELF is the executable, shared-library, object-file and core-dump format of Linux, Android's native layer, the BSDs and most embedded toolchains. The first sixteen bytes are e_ident, a self-describing preamble: the magic, then a byte saying whether this is a 32-bit or 64-bit file and a byte saying whether it is little- or big-endian, which is what lets one parser read a binary for a machine it has never run on. Everything after e_ident comes in two tables — program headers, which the kernel uses to map the file into memory, and section headers, which the linker and the debugger use and which a stripped binary can survive losing.
Header layout, byte by byte
| Offset | Length | Field | Meaning |
|---|---|---|---|
| 0 | 4 | EI_MAG0..3 (magic) | 7F 45 4C 46 — a deliberately non-ASCII byte followed by the letters ELF, so a file mangled by a text-mode transfer or truncated to seven bits stops matching immediately. |
| 4 | 1 | EI_CLASS | 1 for ELFCLASS32, 2 for ELFCLASS64. This single byte decides the width of every address field later in the header, so on a 32-bit file each of e_entry, e_phoff and e_shoff shrinks to four bytes and everything after offset 24 shifts down by twelve. |
| 5 | 1 | EI_DATA | 1 for two's-complement little-endian, 2 for big-endian. It governs how every multi-byte field in the entire file is read, which is why a MIPS or older PowerPC binary looks like nonsense to a parser that assumes little-endian. |
| 6 | 1 | EI_VERSION | The ELF header version, always 1. A value of 0 or anything above 1 means the header is damaged, not that a newer ELF exists. |
| 7 | 1 | EI_OSABI | 0 is UNIX System V, which is what most Linux toolchains actually emit; 3 is GNU/Linux, 9 FreeBSD, 12 OpenBSD. It only matters for interpreting OS-specific section and symbol types, so 0 versus 3 on a Linux binary is normal and not a defect. |
| 8 | 1 | EI_ABIVERSION | Sub-version of the ABI named above, almost always 0. |
| 9 | 7 | EI_PAD | Zero padding that rounds e_ident out to sixteen bytes. Non-zero bytes here are a reliable smell of a hand-patched or hand-forged header. |
| 16 | 2 | e_type | 1 REL, a relocatable object file; 2 EXEC, a fixed-address executable; 3 DYN, which covers both a shared object and every position-independent executable built in the last decade; 4 CORE, a crash dump. The header alone cannot separate a .so from a PIE — that needs DF_1_PIE in the dynamic section. |
| 18 | 2 | e_machine | The target CPU: 62 for x86-64, 183 for AArch64, 3 for i386, 40 for 32-bit ARM, 243 for RISC-V. This is the field to read when a library refuses to load with a wrong-architecture error. |
| 20 | 4 | e_version | Object file version, again always 1. It duplicates EI_VERSION at a different width, a leftover of the original ABI. |
| 24 | 8 | e_entry | Virtual address the kernel jumps to after mapping the image; four bytes at this same offset on a 32-bit file. Shared objects normally report 0 here because they have no entry point of their own. |
| 32 | 8 | e_phoff | File offset of the program header table. In practice this is 64 — the table starts immediately after the header — because the loader wants the segments in the first page it reads. |
| 40 | 8 | e_shoff | File offset of the section header table, which normally sits at the very end of the file. Zero means the sections were stripped, and a value past the end of the file is the usual shape of a truncated binary. |
| 48 | 4 | e_flags | Processor-specific flags. Empty on x86-64, but on ARM and RISC-V it encodes the float ABI and instruction-set options, which is how a link fails with a soft-float versus hard-float mismatch. |
| 52 | 2 | e_ehsize | Size of this header: 64 on a 64-bit file, 52 on a 32-bit one. It is a cheap self-consistency check — if it disagrees with EI_CLASS, stop trusting the rest. |
| 54 | 2 | e_phentsize | Bytes per program header entry: 56 on 64-bit, 32 on 32-bit. Fixed by the ABI, so a different value means the file is damaged rather than exotic. |
| 56 | 2 | e_phnum | Number of program headers. Typically under a dozen; the special value 0xFFFF means the real count lives in the first section header, a workaround for binaries with enormous segment tables. |
| 58 | 2 | e_shentsize | Bytes per section header entry: 64 on 64-bit, 40 on 32-bit. |
| 60 | 2 | e_shnum | Number of section headers. Multiply by e_shentsize, add e_shoff, and on an intact file the result is exactly the file size when the section table is last — the fastest truncation check there is. |
| 62 | 2 | e_shstrndx | Index of the section holding section names. Without it every section is nameless, so a tool that prints numbered sections and no names is usually reading a file where this index is out of range. |
Telling it apart from its neighbours
| Easily confused with | How to tell |
|---|---|
| PE (Windows executable) | The Windows counterpart, and the confusion is practical rather than byte-level: cross-compiled build outputs land in the same directory. PE opens with 4D 5A ('MZ') and hides its real header behind a four-byte pointer at offset 0x3C; ELF says 7F 45 4C 46 and puts everything at fixed offsets. A .so that is actually a .dll fails at offset 0. |
| Mach-O executable | Same job on macOS. Mach-O opens with CF FA ED FE (64-bit little-endian) or CE FA ED FE (32-bit), never 7F 45 4C 46, and a universal binary opens CA FE BA BE. Both formats use the extension .o for object files and .dylib versus .so for libraries, so the extension proves nothing and the first four bytes prove everything. |
| ar archive (.a) | A static library is not an ELF file: it is an ar archive whose members are ELF objects. It begins with the eight bytes 21 3C 61 72 63 68 3E 0A ('!<arch>' plus a newline), and the first 7F 45 4C 46 appears some sixty bytes in, after the first member header. Tools that report 'not an ELF file' on a .a are correct. |
| Core dump | A core file is a real ELF file with the same header, which is why the file command calls it ELF. The separator is e_type at offset 16: 4 means CORE, and such a file has program headers but no section headers and no symbols, so a debugger needs the original binary alongside it. |
| PIE | A shared object and a position-independent executable both report e_type 3 (DYN), so the header genuinely cannot tell them apart — this is not a shortcut anyone has failed to find. The distinction is DF_1_PIE in the DT_FLAGS_1 entry of the dynamic section, which is exactly what readelf checks before printing 'Position-Independent Executable file'. |
How these files break
- Stripped versus damaged sections. A stripped binary sets e_shoff to zero and still runs perfectly, because the kernel only reads program headers; a damaged one leaves e_shoff pointing past the end of the file. Both make readelf print nothing useful, and only the e_shoff value says which of the two you are holding.
- Truncation. The section header table normally lives at the end of an ELF file, so an interrupted download loses it first: the program headers parse, the entry point looks sane, and every section-based tool fails. Check e_shoff plus e_shnum times e_shentsize against the real file size.
- Text-mode transfer. Copying a binary through a client that translates line endings turns every 0D 0A into 0A, shifting everything after the first occurrence. The magic often survives, so the file still identifies as ELF while the program headers point at nonsense offsets.
- A wrong EI_CLASS or EI_DATA byte. Flip byte 4 or byte 5 and the header is still 'valid' — the parser simply reads every subsequent field at the wrong width or the wrong endianness, and reports gigantic offsets and an absurd section count instead of admitting the file is broken.
- Patched in place without fixing offsets. Editing a string in .rodata to something longer, or inserting bytes anywhere, moves the section and program header tables without updating e_shoff and e_phoff. The binary loads until it jumps somewhere that is no longer code.
Where the format stands today
ELF is what every Linux program, shared library and kernel module is, what Android ships in lib/ inside an APK, and what almost every embedded toolchain emits before the image is flattened to a raw image or Intel HEX. It has no serious competition on Unix: a.out and COFF were gone by the mid-nineties, and even the formats that replaced ELF for distribution — a container image, a Flatpak, an APK — are archives full of ELF files. The places it does not reach are Windows, which uses PE, and Apple platforms, which use Mach-O, so a cross-compiled build tree contains all three and only the first four bytes tell them apart. For anything WebAssembly touches, ELF is what the toolchain uses internally before emitting a .wasm module, which is why an emscripten build directory is full of ELF objects that never ship.
Open ELF executable files on this site
Every one of these runs in your browser — the file never leaves your machine.
Related formats
Formats that are easy to confuse with ELF executable — compare their leading bytes.
| Format | First bytes | Extension |
|---|---|---|
| PE (Windows executable) | 4D 5A | .exe |
| Mach-O executable | CF FA ED FE | .dylib |
| WebAssembly | 00 61 73 6D | .wasm |
| Android DEX | 64 65 78 0A | .dex |