On this page
ELF File Format
Coverage: ELF header → section header table → program header table → manual parsing → readelf/objdump usage Applicable to: x86-64 / ARM64 ELF, ET_EXEC / ET_DYN / ET_REL
Overview
ELF (Executable and Linkable Format) is the standard format for almost all binary files on Linux—executables, shared libraries (.so), object files (.o), and core dumps all use ELF. Understanding ELF is not just an academic exercise—when you need to troubleshoot "why this .so failed to load," "why LD_PRELOAD is not taking effect," or "why coredumps are unanalyzable after stripping," the details in the ELF header hold the answers.
Three Views of ELF
flowchart LR
subgraph LINK["🔗 Linking View (Compile/Link Time)"]
L_HDR["ELF Header"]
L_SHDR["Section Header Table"]
L_TEXT[".text (Code)"]
L_RODATA[".rodata"]
L_DATA[".data"]
L_BSS[".bss"]
L_SYMTAB[".symtab"]
L_DYNSYM[".dynsym"]
L_PLT[".plt"]
L_GOT[".got"]
end
subgraph EXEC["▶️ Execution View (Runtime)"]
E_HDR["ELF Header"]
E_PHDR["Program Header Table"]
E_LOAD_RX["PT_LOAD<br/>Readable + Executable"]
E_LOAD_RW["PT_LOAD<br/>Readable + Writable"]
E_DYNAMIC["PT_DYNAMIC<br/>Dynamic Linking Info"]
E_INTERP["PT_INTERP<br/>ld.so Path"]
end
L_TEXT -.->|"Belongs to"| E_LOAD_RX
L_RODATA -.->|"Belongs to"| E_LOAD_RX
L_DATA -.->|"Belongs to"| E_LOAD_RW
L_BSS -.->|"Belongs to"| E_LOAD_RW
L_DYNSYM -.->|"Belongs to"| E_DYNAMIC
classDef link fill:#e3f2fd,stroke:#1565c0
classDef exec fill:#fff3e0,stroke:#ef6c00
class LINK link
class EXEC exec
Sections are for the linker (organized by function), while segments are for the kernel/dynamic linker (organized by permissions and loading requirements). The same .text section belongs to a PT_LOAD segment.
ELF Header
// /usr/include/elf.h
typedef struct Elf64_Ehdr;
e_ident Magic Number
|
# bytes 0-3: 0x7f 'E' 'L' 'F' — Magic number
# byte 4: EI_CLASS — 1=32-bit, 2=64-bit
# byte 5: EI_DATA — 1=little-endian, 2=big-endian
# byte 6: EI_VERSION — always 1
# byte 7: EI_OSABI — 0=UNIX System V, 3=GNU/Linux
# bytes 8-15: padding
Key e_type Values
ET_EXEC (2): Traditional executable, loaded at a fixed address (non-PIE)
ET_DYN (3): Shared library or PIE executable, loaded at a randomized base address (modern default)
ET_REL (1): Relocatable object file (.o), input for the linker
ET_CORE (4): Core dump file
Modern Linux executables (default GCC -pie) are all ET_DYN. You can verify this with file /bin/ls.
Section Header Table
Each section describes an area in the file—code, data, symbol tables, relocation tables, etc.
typedef struct Elf64_Shdr;
Key Sections
| Section | Type | Content |
|---|---|---|
| .text | SHT_PROGBITS | Executable code |
| .rodata | SHT_PROGBITS | Read-only data (string constants, etc.) |
| .data | SHT_PROGBITS | Initialized global/static variables |
| .bss | SHT_NOBITS | Uninitialized global variables (takes space but not file size) |
| .plt | SHT_PROGBITS | Procedure Linkage Table (lazy binding) |
| .got / .got.plt | SHT_PROGBITS | Global Offset Table |
| .dynsym | SHT_DYNSYM | Dynamic symbol table (needed at runtime) |
| .symtab | SHT_SYMTAB | Full symbol table (can be removed by strip) |
| .dynstr / .strtab | SHT_STRTAB | String table |
| .rela.dyn / .rela.plt | SHT_RELA | Relocation table |
| .interp | SHT_PROGBITS | Dynamic linker path (e.g., /lib64/ld-linux-x86-64.so.2) |
| .dynamic | SHT_DYNAMIC | Dynamic linking information (DT_NEEDED, DT_SONAME, ...) |
| .init / .fini | SHT_PROGBITS | Initialization/termination code (GCC -finit-array) |
Program Header Table
Program headers tell the kernel "how to load this file." Each segment (PT_LOAD) specifies a block of data to be mapped to a virtual address with specific permissions.
typedef struct Elf64_Phdr;
PT_LOAD Mapping Process
// Kernel fs/binfmt_elf.c: elf_map()
// For each PT_LOAD segment:
// 1. mmap(p_vaddr, p_memsz, PROT from p_flags, MAP_PRIVATE, fd, p_offset)
// 2. If p_memsz > p_filesz → remaining bytes are mapped as 0 (anonymous MAP_ANONYMOUS)
// This is the runtime implementation of .bss
// Example: /bin/ls has 2 PT_LOAD segments:
// Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
// LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x002e58 0x002e58 R E 0x1000
// LOAD 0x0000000000003000 0x0000000000403000 0x0000000000403000 0x000fb8 0x001640 RW 0x1000
//
// First LOAD: Code + read-only data → R+X (memsz == filesz, no .bss)
// Second LOAD: Data + .bss → R+W (memsz > filesz, remaining bytes padded for .bss)
Manual Parsing
# ELF Header
# Section headers
# Program headers (segments)
# Dynamic symbol table
# Relocation table
# .dynamic section (DT_NEEDED, DT_RPATH, ...)
# Full string search
|
# Disassembly
|
# Check if PIE
# "ELF 64-bit LSB pie executable" → PIE
# "ELF 64-bit LSB executable" → non-PIE
References
- Specification: ELF spec (System V ABI, Chapters 4-5),
man 5 elf - Header files:
/usr/include/elf.h,/usr/include/link.h - Tools:
readelf,objdump,nm,size,strip,patchelf - LWN: "The ELF object file format" series
Keywords: ELF header, section, segment, PT_LOAD, SHT_SYMTAB, ET_DYN, ET_EXEC, PIE, readelf, objdump