ELF File Format
The Executable and Linkable Format is used by Linux, the BSDs, Android, most embedded systems and firmware: executables, shared libraries (.so), object files (.o) and core dumps.
This page walks through every structure with its offsets in 32 and 64-bit files, then annotates every byte of a small working executable.
- All formats
- Layout
- Annotated sample
- ELF header
- Program headers
- Section headers
- Symbols
- Relocations
- Dynamic section
- Notes
- What Linux checks
- Tools
Layout
An ELF file starts with a fixed header that points to two independent tables: the program header table describes segments, what the loader maps into memory, and the section header table describes sections, what the linker and the tools work with. Both views cover the same bytes.
Linker view: sections
Used by ld, objdump, debuggers. Fine-grained, named, typed. Not needed to run the program: strip and packers can remove all of it.
Loader view: segments
R-XELF header, program headers, .text, .rodataRW-.data, .bss (memsz > filesz)RW-no file content, stack is not executableUsed by the kernel and ld.so. Coarse: one mapping per permission set. A segment contains zero or more sections, and the first PT_LOAD usually starts at offset 0 so the headers are mapped too.
Annotated sample (1008 bytes, x86-64)
A complete static executable that prints Hello, ELF! and exits: no libc, three program headers, eight sections and a symbol table.
Every byte belongs to a field. Hover the dump or the structure list to see what it is.
Hover a byte or a field to see what it is. Click to pin it.
ELF header: e_ident 0x0
- EI_MAG0..37f "ELF"
- EI_CLASSELFCLASS64
- EI_DATAELFDATA2LSB (little …
- EI_VERSION1
- EI_OSABIELFOSABI_NONE (Syste…
- EI_ABIVERSION0
- EI_PADzero
ELF header 0x10
- e_typeET_EXEC
- e_machineEM_X86_64
- e_version1
- e_entry0x4000e8
- e_phoff0x40
- e_shoff0x1f0
- e_flags0
- e_ehsize0x40
- e_phentsize0x38
- e_phnum3
- e_shentsize0x40
- e_shnum8
- e_shstrndx7
Program header 0 (PT_LOAD) 0x40
- p_typePT_LOAD
- p_flagsR-X
- p_offset0
- p_vaddr0x400000
- p_paddr0x400000
- p_filesz0x11c
- p_memsz0x11c
- p_align0x1000
Program header 1 (PT_LOAD) 0x78
- p_typePT_LOAD
- p_flagsRW-
- p_offset0x120
- p_vaddr0x401120
- p_paddr0x401120
- p_filesz8
- p_memsz0x10
- p_align0x1000
Program header 2 (PT_GNU_STACK) 0xb0
- p_typePT_GNU_STACK
- p_flagsRW-
- p_offset0
- p_vaddr0
- p_paddr0
- p_filesz0
- p_memsz0
- p_align0x10
.text (_start) 0xe8
- inc qword [rip+counter]inc qword [rip+count…
- mov eax, 1mov eax, 1
- mov edi, 1mov edi, 1
- lea rsi, [rip+msg]lea rsi, [rip+msg]
- mov edx, 12mov edx, 12
- syscallsyscall
- mov eax, 60mov eax, 60
- xor edi, edixor edi, edi
- syscallsyscall
.rodata 0x110
- msg"Hello, ELF!\n"
Padding 0x11c
- paddingzeros
.data 0x120
- counter42
Symbol 0 (null) 0x128
- st_name0
- st_infoSTB_LOCAL STT_NOTYPE
- st_other0
- st_shndxSHN_UNDEF
- st_value0
- st_size0
Symbol 1 hello.s 0x140
- st_name1 → "hello.s"
- st_infoSTB_LOCAL STT_FILE
- st_other0
- st_shndxSHN_ABS
- st_value0
- st_size0
Symbol 2 msg 0x158
- st_name9 → "msg"
- st_infoSTB_LOCAL STT_OBJECT
- st_other0
- st_shndx2 (.rodata)
- st_value0x400110
- st_size0xc
Symbol 3 counter 0x170
- st_name13 → "counter"
- st_infoSTB_LOCAL STT_OBJECT
- st_other0
- st_shndx3 (.data)
- st_value0x401120
- st_size8
Symbol 4 _start 0x188
- st_name21 → "_start"
- st_infoSTB_GLOBAL STT_FUNC
- st_other0
- st_shndx1 (.text)
- st_value0x4000e8
- st_size0x28
.strtab 0x1a0
- strings"hello.s" "msg" "cou…
.shstrtab 0x1bc
- strings".text" ".rodata" ".…
Section header 0 (SHN_UNDEF) 0x1f0
- sh_name0
- sh_typeSHT_NULL
- sh_flags0
- sh_addr0
- sh_offset0
- sh_size0
- sh_link0
- sh_info0
- sh_addralign1
- sh_entsize0
Section header 1 (.text) 0x230
- sh_name1 → ".text"
- sh_typeSHT_PROGBITS
- sh_flagsSHF_ALLOC | SHF_EXEC…
- sh_addr0x4000e8
- sh_offset0xe8
- sh_size0x28
- sh_link0
- sh_info0
- sh_addralign0x10
- sh_entsize0
Section header 2 (.rodata) 0x270
- sh_name7 → ".rodata"
- sh_typeSHT_PROGBITS
- sh_flagsSHF_ALLOC
- sh_addr0x400110
- sh_offset0x110
- sh_size0xc
- sh_link0
- sh_info0
- sh_addralign1
- sh_entsize0
Section header 3 (.data) 0x2b0
- sh_name15 → ".data"
- sh_typeSHT_PROGBITS
- sh_flagsSHF_WRITE | SHF_ALLOC
- sh_addr0x401120
- sh_offset0x120
- sh_size8
- sh_link0
- sh_info0
- sh_addralign8
- sh_entsize0
Section header 4 (.bss) 0x2f0
- sh_name21 → ".bss"
- sh_typeSHT_NOBITS
- sh_flagsSHF_WRITE | SHF_ALLOC
- sh_addr0x401128
- sh_offset0x128
- sh_size8
- sh_link0
- sh_info0
- sh_addralign8
- sh_entsize0
Section header 5 (.symtab) 0x330
- sh_name26 → ".symtab"
- sh_typeSHT_SYMTAB
- sh_flags0
- sh_addr0
- sh_offset0x128
- sh_size0x78
- sh_link6
- sh_info4
- sh_addralign8
- sh_entsize0x18
Section header 6 (.strtab) 0x370
- sh_name34 → ".strtab"
- sh_typeSHT_STRTAB
- sh_flags0
- sh_addr0
- sh_offset0x1a0
- sh_size0x1c
- sh_link0
- sh_info0
- sh_addralign1
- sh_entsize0
Section header 7 (.shstrtab) 0x3b0
- sh_name42 → ".shstrtab"
- sh_typeSHT_STRTAB
- sh_flags0
- sh_addr0
- sh_offset0x1bc
- sh_size0x34
- sh_link0
- sh_info0
- sh_addralign1
- sh_entsize0
ELF header
At offset 0. Only e_ident is the same for all files; EI_CLASS and EI_DATA tell how to read the rest.
In the tables below ElfN_Addr, ElfN_Off and ElfN_Xword are 4 bytes in ELF32 and 8 bytes in ELF64 (Xword fields are plain Elf32_Word in ELF32); ElfN_Half is 2 bytes and ElfN_Word 4 bytes in both.
Elf32_Ehdr / Elf64_Ehdr 52 (0x34) bytes / 64 (0x40) bytes
| Field | Off. 32 | Off. 64 | Size 32 | Size 64 | Type | Meaning |
|---|---|---|---|---|---|---|
e_ident | 0x00 | 0x00 | 16 | 16 | uint8_t[16] | Identification bytes, see below. |
e_type | 0x10 | 0x10 | 2 | 2 | ElfN_Half | Object type: ET_REL, ET_EXEC, ET_DYN (shared library or PIE executable), ET_CORE. |
e_machine | 0x12 | 0x12 | 2 | 2 | ElfN_Half | Architecture: EM_386, EM_X86_64, EM_AARCH64... |
e_version | 0x14 | 0x14 | 4 | 4 | ElfN_Word | Always 1. |
e_entry | 0x18 | 0x18 | 4 | 8 | ElfN_Addr | Virtual address of the entry point (_start); relative to the load base for ET_DYN. 0 if none. |
e_phoff | 0x1c | 0x20 | 4 | 8 | ElfN_Off | File offset of the program header table (loader view). |
e_shoff | 0x20 | 0x28 | 4 | 8 | ElfN_Off | File offset of the section header table (linker view). 0 if none. |
e_flags | 0x24 | 0x30 | 4 | 4 | ElfN_Word | Processor-specific flags (ARM EABI version, RISC-V float ABI, MIPS ISA...). 0 on x86. |
e_ehsize | 0x28 | 0x34 | 2 | 2 | ElfN_Half | Size of this header: 52 or 64. |
e_phentsize | 0x2a | 0x36 | 2 | 2 | ElfN_Half | Size of one program header: 32 or 56. |
e_phnum | 0x2c | 0x38 | 2 | 2 | ElfN_Half | Number of program headers. |
e_shentsize | 0x2e | 0x3a | 2 | 2 | ElfN_Half | Size of one section header: 40 or 64. |
e_shnum | 0x30 | 0x3c | 2 | 2 | ElfN_Half | Number of section headers (0 and the real count in sh_size of section 0 when ≥ 0xff00). |
e_shstrndx | 0x32 | 0x3e | 2 | 2 | ElfN_Half | Index of the section that holds section names (.shstrtab). |
e_ident[16] 16 (0x10) bytes
| Field | Offset | Size | Type | Meaning |
|---|---|---|---|---|
EI_MAG0..3 | 0x00 | 4 | uint8_t[4] | Magic: 7f 45 4c 46 ("\x7fELF"). |
EI_CLASS | 0x04 | 1 | uint8_t | 1 = ELFCLASS32, 2 = ELFCLASS64. Selects the layout of every other structure. |
EI_DATA | 0x05 | 1 | uint8_t | 1 = little endian (ELFDATA2LSB), 2 = big endian (ELFDATA2MSB). |
EI_VERSION | 0x06 | 1 | uint8_t | Always 1 (EV_CURRENT). |
EI_OSABI | 0x07 | 1 | uint8_t | 0 = System V (ELFOSABI_NONE), 3 = Linux/GNU (ELFOSABI_LINUX). Linux binaries normally use 0; 3 marks GNU extensions (IFUNC, unique symbols). |
EI_ABIVERSION | 0x08 | 1 | uint8_t | ABI version, normally 0. The kernel uapi header still calls byte 8 EI_PAD. |
EI_PAD | 0x09 | 7 | uint8_t[7] | Padding, zero. |
e_type
| Name | Value | Meaning |
|---|---|---|
| ET_NONE | 0 | No file type. |
| ET_REL | 1 | Relocatable object (.o). |
| ET_EXEC | 2 | Executable at a fixed address (non-PIE). |
| ET_DYN | 3 | Shared object, or position-independent executable (PIE, the default on modern distributions). |
| ET_CORE | 4 | Core dump. |
e_machine (common values)
| Name | Value | Meaning |
|---|---|---|
| EM_386 | 3 | Intel 80386 |
| EM_MIPS | 8 | MIPS |
| EM_PPC | 0x14 | PowerPC |
| EM_PPC64 | 0x15 | PowerPC 64-bit |
| EM_S390 | 0x16 | IBM S/390 and z/Architecture |
| EM_ARM | 0x28 | ARM 32-bit (AArch32) |
| EM_SPARCV9 | 0x2b | SPARC v9 64-bit |
| EM_IA_64 | 0x32 | Intel Itanium |
| EM_X86_64 | 0x3e | AMD64 / Intel 64 |
| EM_AARCH64 | 0xb7 | ARM 64-bit (AArch64) |
| EM_RISCV | 0xf3 | RISC-V (32 and 64-bit, class from EI_CLASS) |
| EM_BPF | 0xf7 | Linux eBPF |
| EM_LOONGARCH | 0x102 | LoongArch |
Program headers
e_phnum entries of e_phentsize bytes at e_phoff. Each one describes a segment.
Elf32_Phdr 32 (0x20) bytes
| Field | Offset | Size | Type | Meaning |
|---|---|---|---|---|
p_type | 0x00 | 4 | Elf32_Word | Segment type (PT_LOAD, PT_DYNAMIC, PT_INTERP...). |
p_offset | 0x04 | 4 | Elf32_Off | File offset of the segment. |
p_vaddr | 0x08 | 4 | Elf32_Addr | Virtual address in memory. |
p_paddr | 0x0c | 4 | Elf32_Addr | Physical address; unused on Linux, usually equal to p_vaddr. |
p_filesz | 0x10 | 4 | Elf32_Word | Bytes in the file. |
p_memsz | 0x14 | 4 | Elf32_Word | Bytes in memory; the part beyond p_filesz is zero-filled (.bss). |
p_flags | 0x18 | 4 | Elf32_Word | Permissions: PF_X 1, PF_W 2, PF_R 4. |
p_align | 0x1c | 4 | Elf32_Word | Alignment; p_offset % p_align == p_vaddr % p_align. |
p_flags moves: it is the 7th field in Elf32_Phdr but the 2nd in Elf64_Phdr, so that the 64-bit fields stay 8-byte aligned.Elf64_Phdr 56 (0x38) bytes
| Field | Offset | Size | Type | Meaning |
|---|---|---|---|---|
p_type | 0x00 | 4 | Elf64_Word | Segment type. |
p_flags | 0x04 | 4 | Elf64_Word | Permissions: PF_X 1, PF_W 2, PF_R 4. |
p_offset | 0x08 | 8 | Elf64_Off | File offset of the segment. |
p_vaddr | 0x10 | 8 | Elf64_Addr | Virtual address in memory (relative to the load base for ET_DYN). |
p_paddr | 0x18 | 8 | Elf64_Addr | Physical address; unused on Linux. |
p_filesz | 0x20 | 8 | Elf64_Xword | Bytes in the file. |
p_memsz | 0x28 | 8 | Elf64_Xword | Bytes in memory; the excess over p_filesz is zero-filled. |
p_align | 0x30 | 8 | Elf64_Xword | Alignment, usually the page size (0x1000) or more for PT_LOAD. |
p_type
| Name | Value | Meaning |
|---|---|---|
| PT_NULL | 0 | Unused entry. |
| PT_LOAD | 1 | Loadable segment: mapped from p_offset to p_vaddr. The only type that creates memory. |
| PT_DYNAMIC | 2 | The .dynamic array, read by the dynamic linker. |
| PT_INTERP | 3 | Path of the program interpreter, e.g. /lib64/ld-linux-x86-64.so.2. |
| PT_NOTE | 4 | Notes (build ID, ABI tag, GNU properties). |
| PT_SHLIB | 5 | Reserved. |
| PT_PHDR | 6 | Location of the program header table itself. |
| PT_TLS | 7 | Thread-local storage template (.tdata + .tbss). |
| PT_GNU_EH_FRAME | 0x6474e550 | .eh_frame_hdr, binary search table for unwinding. |
| PT_GNU_STACK | 0x6474e551 | Only p_flags matters: stack is executable if PF_X is set. Absent = executable on some architectures. |
| PT_GNU_RELRO | 0x6474e552 | Range made read-only after relocation (RELRO): .got, .dynamic, .init_array... |
| PT_GNU_PROPERTY | 0x6474e553 | .note.gnu.property: CET IBT/SHSTK on x86, BTI/PAC on AArch64. |
| PT_AARCH64_MEMTAG_MTE | 0x70000002 | AArch64 MTE tags stored in core dumps. |
p_flags
| Name | Value | Meaning |
|---|---|---|
| PF_X | 1 | Execute |
| PF_W | 2 | Write |
| PF_R | 4 | Read |
Section headers
e_shnum entries of e_shentsize bytes at e_shoff. Entry 0 is always null. Names are offsets in the section e_shstrndx.
Elf32_Shdr / Elf64_Shdr 40 (0x28) bytes / 64 (0x40) bytes
| Field | Off. 32 | Off. 64 | Size 32 | Size 64 | Type | Meaning |
|---|---|---|---|---|---|---|
sh_name | 0x00 | 0x00 | 4 | 4 | ElfN_Word | Offset of the name in .shstrtab. |
sh_type | 0x04 | 0x04 | 4 | 4 | ElfN_Word | Section type (SHT_PROGBITS, SHT_SYMTAB, SHT_NOBITS...). |
sh_flags | 0x08 | 0x08 | 4 | 8 | ElfN_Xword | Attributes: SHF_WRITE, SHF_ALLOC, SHF_EXECINSTR... |
sh_addr | 0x0c | 0x10 | 4 | 8 | ElfN_Addr | Virtual address when the section is loaded (0 if not SHF_ALLOC). |
sh_offset | 0x10 | 0x18 | 4 | 8 | ElfN_Off | File offset of the content (meaningless for SHT_NOBITS). |
sh_size | 0x14 | 0x20 | 4 | 8 | ElfN_Xword | Size in bytes. |
sh_link | 0x18 | 0x28 | 4 | 4 | ElfN_Word | Index of a related section: the string table of a symbol table, the symbol table of a relocation section... |
sh_info | 0x1c | 0x2c | 4 | 4 | ElfN_Word | Extra info: for SHT_REL/RELA the section the relocations apply to; for SHT_SYMTAB the index of the first non-local symbol. |
sh_addralign | 0x20 | 0x30 | 4 | 8 | ElfN_Xword | Required alignment. |
sh_entsize | 0x24 | 0x38 | 4 | 8 | ElfN_Xword | Size of one entry for table sections (24 for an Elf64_Sym), else 0. |
sh_type
| Name | Value | Meaning |
|---|---|---|
| SHT_NULL | 0 | Inactive entry. Section 0 is always SHT_NULL. |
| SHT_PROGBITS | 1 | Program-defined content: code, data, .rodata, .got, .eh_frame... |
| SHT_SYMTAB | 2 | Full symbol table (.symtab); removed by strip. |
| SHT_STRTAB | 3 | String table (.strtab, .shstrtab, .dynstr). |
| SHT_RELA | 4 | Relocations with addend (.rela.text, .rela.dyn, .rela.plt). |
| SHT_HASH | 5 | SysV symbol hash table (.hash). |
| SHT_DYNAMIC | 6 | Dynamic linking array (.dynamic). |
| SHT_NOTE | 7 | Notes (.note.*). |
| SHT_NOBITS | 8 | Occupies no file space: .bss, .tbss. |
| SHT_REL | 9 | Relocations without addend. |
| SHT_SHLIB | 0xa | Reserved. |
| SHT_DYNSYM | 0xb | Dynamic symbol table (.dynsym), kept by strip. |
| SHT_INIT_ARRAY | 0xe | Constructor pointers (.init_array). |
| SHT_FINI_ARRAY | 0xf | Destructor pointers (.fini_array). |
| SHT_PREINIT_ARRAY | 0x10 | Pointers run before the constructors (executables only). |
| SHT_GROUP | 0x11 | Section group (COMDAT), used for C++ inline functions and templates in .o files. |
sh_flags
| Name | Value | Meaning |
|---|---|---|
| SHF_WRITE | 1 | Writable at run time. |
| SHF_ALLOC | 2 | Occupies memory at run time (part of a PT_LOAD). |
| SHF_EXECINSTR | 4 | Executable code. |
| SHF_MERGE | 0x10 | Identical entries may be merged (sh_entsize gives the size). |
| SHF_STRINGS | 0x20 | NUL-terminated strings (with SHF_MERGE: string pooling). |
| SHF_INFO_LINK | 0x40 | sh_info holds a section index. |
| SHF_LINK_ORDER | 0x80 | Keep the order of the section referenced by sh_link. |
| SHF_GROUP | 0x200 | Member of a section group. |
| SHF_TLS | 0x400 | Thread-local data. |
| SHF_EXCLUDE | 0x8000000 | Excluded from the link output. |
Special section indexes
| Name | Value | Meaning |
|---|---|---|
| SHN_UNDEF | 0 | Undefined: the symbol comes from another object or library. |
| SHN_ABS | 0xfff1 | Absolute value, not relative to any section. |
| SHN_COMMON | 0xfff2 | Uninitialized common block (-fcommon); st_value holds the alignment. |
Common sections
| Section | Content |
|---|---|
| .interp | Path of the dynamic linker (covered by PT_INTERP). |
| .note.gnu.build-id | Unique build ID, used to find debug symbols. |
| .gnu.hash / .hash | Hash tables to look up dynamic symbols. |
| .dynsym / .dynstr | Symbols and names needed for dynamic linking. |
| .gnu.version, .gnu.version_r | Symbol versioning (puts@GLIBC_2.2.5). |
| .rela.dyn / .rela.plt | Dynamic relocations: data and GOT / PLT slots. |
| .init, .fini | Legacy constructor and destructor code. |
| .plt, .plt.got, .plt.sec | Procedure linkage table stubs used to call library functions. |
| .text | Code. |
| .rodata | Read-only data, string literals. |
| .eh_frame, .eh_frame_hdr | Unwind tables (C++ exceptions, backtraces). |
| .tdata, .tbss | Thread-local storage template. |
| .init_array, .fini_array | Constructor and destructor pointers. |
| .dynamic | The Elf64_Dyn array. |
| .got, .got.plt | Global offset table: addresses filled by the dynamic linker. |
| .data, .bss | Initialized and zero-initialized writable data. |
| .comment | Compiler version strings. |
| .debug_* | DWARF debug information. |
| .symtab, .strtab, .shstrtab | Full symbol table and names, section names. |
Symbols
Elf32_Sym 16 (0x10) bytes
| Field | Offset | Size | Type | Meaning |
|---|---|---|---|---|
st_name | 0x00 | 4 | Elf32_Word | Offset of the name in the string table (sh_link of the symbol table). |
st_value | 0x04 | 4 | Elf32_Addr | Value: an address in executables, an offset in its section in ET_REL. |
st_size | 0x08 | 4 | Elf32_Word | Size of the object or function. |
st_info | 0x0c | 1 | uint8_t | Binding (high 4 bits) and type (low 4 bits): ELF_ST_BIND(i) = i >> 4, ELF_ST_TYPE(i) = i & 0xf. |
st_other | 0x0d | 1 | uint8_t | Visibility in the low 2 bits (STV_*). |
st_shndx | 0x0e | 2 | Elf32_Half | Index of the defining section, or SHN_UNDEF / SHN_ABS / SHN_COMMON. |
Elf64_Sym 24 (0x18) bytes
| Field | Offset | Size | Type | Meaning |
|---|---|---|---|---|
st_name | 0x00 | 4 | Elf64_Word | Offset of the name in the string table. |
st_info | 0x04 | 1 | uint8_t | Binding (high 4 bits) and type (low 4 bits). |
st_other | 0x05 | 1 | uint8_t | Visibility in the low 2 bits. |
st_shndx | 0x06 | 2 | Elf64_Half | Index of the defining section or a special index. |
st_value | 0x08 | 8 | Elf64_Addr | Address (executables, shared objects) or section offset (ET_REL). |
st_size | 0x10 | 8 | Elf64_Xword | Size of the object or function. |
Binding (st_info >> 4)
| Name | Value | Meaning |
|---|---|---|
| STB_LOCAL | 0 | Not visible outside the object. Local symbols come first in the table. |
| STB_GLOBAL | 1 | Visible to all objects. |
| STB_WEAK | 2 | Global with lower precedence; may stay undefined (value 0). |
Type (st_info & 0xf)
| Name | Value | Meaning |
|---|---|---|
| STT_NOTYPE | 0 | No type (labels). |
| STT_OBJECT | 1 | Data object. |
| STT_FUNC | 2 | Function. |
| STT_SECTION | 3 | A section, used by relocations. |
| STT_FILE | 4 | Source file name (LOCAL, SHN_ABS). |
| STT_COMMON | 5 | Common block. |
| STT_TLS | 6 | Thread-local variable; st_value is an offset in the TLS template. |
| STT_GNU_IFUNC | 0xa | GNU indirect function: the value is a resolver called at load time. |
Visibility (st_other & 3)
| Name | Value | Meaning |
|---|---|---|
| STV_DEFAULT | 0 | Visibility given by the binding; exported and preemptible. |
| STV_INTERNAL | 1 | Like hidden, with processor-specific meaning. |
| STV_HIDDEN | 2 | Not exported from the shared object or executable (-fvisibility=hidden). |
| STV_PROTECTED | 3 | Exported but not preemptible. |
Relocations
Elf32_Rel / Elf64_Rel 8 (0x8) bytes / 16 (0x10) bytes
| Field | Off. 32 | Off. 64 | Size 32 | Size 64 | Type | Meaning |
|---|---|---|---|---|---|---|
r_offset | 0x00 | 0x00 | 4 | 8 | ElfN_Addr | Where to apply the relocation: section offset in ET_REL, virtual address in executables and shared objects. |
r_info | 0x04 | 0x08 | 4 | 8 | ElfN_Xword | Symbol index and type. 64-bit: sym = r_info >> 32, type = r_info & 0xffffffff. 32-bit: sym = r_info >> 8, type = r_info & 0xff. |
Elf32_Rela / Elf64_Rela 12 (0xc) bytes / 24 (0x18) bytes
| Field | Off. 32 | Off. 64 | Size 32 | Size 64 | Type | Meaning |
|---|---|---|---|---|---|---|
r_offset | 0x00 | 0x00 | 4 | 8 | ElfN_Addr | Where to apply the relocation. |
r_info | 0x04 | 0x08 | 4 | 8 | ElfN_Xword | Symbol index and relocation type. |
r_addend | 0x08 | 0x10 | 4 | 8 | ElfN_Sxword | Constant added to the computed value. Rel (no addend) stores it in the patched location instead; x86-64 and AArch64 use Rela, i386 and ARM use Rel. |
x86-64
| Name | Value | Calculation | Use |
|---|---|---|---|
| R_X86_64_NONE | 0 | — | No relocation. |
| R_X86_64_64 | 1 | S + A | Absolute 64-bit address (pointers in data). |
| R_X86_64_PC32 | 2 | S + A - P | 32-bit PC-relative (lea rax, [rip+sym]). |
| R_X86_64_GOT32 | 3 | G + A | 32-bit GOT offset. |
| R_X86_64_PLT32 | 4 | L + A - P | PC-relative call through the PLT (call puts). |
| R_X86_64_COPY | 5 | — | Dynamic: copy the initial value of a shared library variable into the executable. |
| R_X86_64_GLOB_DAT | 6 | S | Dynamic: fill a GOT entry with the symbol address. |
| R_X86_64_JUMP_SLOT | 7 | S | Dynamic: fill a .got.plt entry (lazy binding). |
| R_X86_64_RELATIVE | 8 | B + A | Dynamic: add the load base (PIE and shared library pointers). |
| R_X86_64_GOTPCREL | 9 | G + GOT + A - P | PC-relative address of the GOT entry. |
| R_X86_64_32 | 10 | S + A | Absolute 32-bit, zero-extended (non-PIE only). |
| R_X86_64_32S | 11 | S + A | Absolute 32-bit, sign-extended (non-PIE only). |
| R_X86_64_DTPMOD64 | 16 | — | Dynamic: TLS module ID. |
| R_X86_64_DTPOFF64 | 17 | — | Dynamic: offset in the TLS block. |
| R_X86_64_TPOFF64 | 18 | — | Dynamic: offset from the thread pointer (initial-exec TLS). |
| R_X86_64_TLSGD | 19 | — | General dynamic TLS: PC-relative GOT entry for __tls_get_addr. |
| R_X86_64_GOTTPOFF | 22 | — | Initial-exec TLS: PC-relative GOT entry holding a TP offset. |
| R_X86_64_TPOFF32 | 23 | — | Local-exec TLS: 32-bit offset from the thread pointer. |
| R_X86_64_PC64 | 24 | S + A - P | 64-bit PC-relative. |
| R_X86_64_IRELATIVE | 37 | indirect(B + A) | Dynamic: call the IFUNC resolver at B + A and store its result. |
| R_X86_64_GOTPCRELX | 41 | G + GOT + A - P | Like GOTPCREL, the linker may relax the instruction to a direct lea/call. |
| R_X86_64_REX_GOTPCRELX | 42 | G + GOT + A - P | Same, for instructions with a REX prefix (mov rax, [rip+sym@GOTPCREL]). |
AArch64
| Name | Value | Calculation | Use |
|---|---|---|---|
| R_AARCH64_NONE | 256 | — | No relocation (also 0). |
| R_AARCH64_ABS64 | 257 | S + A | Absolute 64-bit address. |
| R_AARCH64_ABS32 | 258 | S + A | Absolute 32-bit address. |
| R_AARCH64_PREL32 | 261 | S + A - P | 32-bit PC-relative (.eh_frame). |
| R_AARCH64_ADR_PREL_PG_HI21 | 275 | Page(S+A) - Page(P) | adrp: 4 KiB page of the symbol. |
| R_AARCH64_ADD_ABS_LO12_NC | 277 | (S + A) & 0xfff | add: low 12 bits, pairs with ADR_PREL_PG_HI21. |
| R_AARCH64_LDST32_ABS_LO12_NC | 285 | (S + A) & 0xfff | 32-bit ldr/str low 12 bits (scaled by 4). |
| R_AARCH64_LDST64_ABS_LO12_NC | 286 | (S + A) & 0xfff | 64-bit ldr/str low 12 bits (scaled by 8). |
| R_AARCH64_JUMP26 | 282 | S + A - P | b: 26-bit branch (±128 MiB). |
| R_AARCH64_CALL26 | 283 | S + A - P | bl: 26-bit call, may go through a PLT or veneer. |
| R_AARCH64_ADR_GOT_PAGE | 311 | Page(G(GDAT(S+A))) - Page(P) | adrp to the GOT entry page. |
| R_AARCH64_LD64_GOT_LO12_NC | 312 | G(GDAT(S+A)) & 0xff8 | ldr of the GOT entry. |
| R_AARCH64_TLSDESC_ADR_PAGE21 | 562 | — | TLS descriptor: adrp. |
| R_AARCH64_TLSDESC_LD64_LO12 | 563 | — | TLS descriptor: ldr. |
| R_AARCH64_TLSDESC_ADD_LO12 | 564 | — | TLS descriptor: add. |
| R_AARCH64_TLSDESC_CALL | 569 | — | TLS descriptor: marks the blr. |
| R_AARCH64_COPY | 1024 | — | Dynamic: copy relocation. |
| R_AARCH64_GLOB_DAT | 1025 | S + A | Dynamic: GOT entry. |
| R_AARCH64_JUMP_SLOT | 1026 | S + A | Dynamic: PLT GOT entry. |
| R_AARCH64_RELATIVE | 1027 | B + A | Dynamic: add the load base. |
| R_AARCH64_TLS_TPREL64 | 1030 | — | Dynamic: offset from the thread pointer. |
| R_AARCH64_TLSDESC | 1031 | — | Dynamic: TLS descriptor. |
| R_AARCH64_IRELATIVE | 1032 | indirect(B + A) | Dynamic: IFUNC resolver result. |
Dynamic section
The array covered by PT_DYNAMIC. It is how ld.so finds everything else: the needed libraries, the dynamic symbol table, the relocations, the constructors.
Elf32_Dyn / Elf64_Dyn 8 (0x8) bytes / 16 (0x10) bytes
| Field | Off. 32 | Off. 64 | Size 32 | Size 64 | Type | Meaning |
|---|---|---|---|---|---|---|
d_tag | 0x00 | 0x00 | 4 | 8 | ElfN_Sxword | Entry type (DT_*). The array ends with DT_NULL. |
d_un | 0x04 | 0x08 | 4 | 8 | ElfN_Xword | Value (d_val) or address (d_ptr), depending on the tag. |
| Name | Value | Meaning |
|---|---|---|
| DT_NULL | 0 | End of the array. |
| DT_NEEDED | 1 | Name of a needed library (offset in DT_STRTAB). |
| DT_PLTRELSZ | 2 | Size of the PLT relocations (.rela.plt). |
| DT_PLTGOT | 3 | Address of .got.plt. |
| DT_HASH | 4 | SysV hash table. |
| DT_STRTAB | 5 | Dynamic string table (.dynstr). |
| DT_SYMTAB | 6 | Dynamic symbol table (.dynsym). |
| DT_RELA | 7 | Rela relocations (.rela.dyn). |
| DT_RELASZ | 8 | Size of DT_RELA. |
| DT_RELAENT | 9 | Size of one Rela entry (24). |
| DT_STRSZ | 0xa | Size of DT_STRTAB. |
| DT_SYMENT | 0xb | Size of one symbol (24). |
| DT_INIT | 0xc | Address of _init. |
| DT_FINI | 0xd | Address of _fini. |
| DT_SONAME | 0xe | Name of this shared object. |
| DT_RPATH | 0xf | Library search path (deprecated, searched before LD_LIBRARY_PATH). |
| DT_SYMBOLIC | 0x10 | Resolve symbols in this object first. |
| DT_REL | 0x11 | Rel relocations. |
| DT_RELSZ | 0x12 | Size of DT_REL. |
| DT_RELENT | 0x13 | Size of one Rel entry. |
| DT_PLTREL | 0x14 | Type of the PLT relocations: DT_REL or DT_RELA. |
| DT_DEBUG | 0x15 | Filled by ld.so with &r_debug (debuggers walk the link map from it). |
| DT_TEXTREL | 0x16 | Relocations write to read-only segments. |
| DT_JMPREL | 0x17 | PLT relocations (.rela.plt), applied lazily unless BIND_NOW. |
| DT_BIND_NOW | 0x18 | Resolve all symbols at load time. |
| DT_INIT_ARRAY | 0x19 | Address of .init_array. |
| DT_FINI_ARRAY | 0x1a | Address of .fini_array. |
| DT_INIT_ARRAYSZ | 0x1b | Size of .init_array. |
| DT_FINI_ARRAYSZ | 0x1c | Size of .fini_array. |
| DT_RUNPATH | 0x1d | Library search path, searched after LD_LIBRARY_PATH. |
| DT_FLAGS | 0x1e | Flags (DF_BIND_NOW, DF_STATIC_TLS...). |
| DT_PREINIT_ARRAY | 0x20 | Address of .preinit_array. |
| DT_PREINIT_ARRAYSZ | 0x21 | Size of .preinit_array. |
| DT_GNU_HASH | 0x6ffffef5 | GNU hash table (.gnu.hash), replaces DT_HASH. |
| DT_VERSYM | 0x6ffffff0 | Symbol versions (.gnu.version). |
| DT_RELACOUNT | 0x6ffffff9 | Number of R_*_RELATIVE relocations at the start of DT_RELA. |
| DT_FLAGS_1 | 0x6ffffffb | More flags: DF_1_NOW, DF_1_PIE... |
| DT_VERNEED | 0x6ffffffe | Needed versions (.gnu.version_r). |
| DT_VERNEEDNUM | 0x6fffffff | Number of DT_VERNEED entries. |
Notes
Elf32_Nhdr / Elf64_Nhdr 12 (0xc) bytes
| Field | Offset | Size | Type | Meaning |
|---|---|---|---|---|
n_namesz | 0x00 | 4 | ElfN_Word | Length of the owner name, including the NUL ("GNU" = 4). |
n_descsz | 0x04 | 4 | ElfN_Word | Length of the descriptor. |
n_type | 0x08 | 4 | ElfN_Word | Note type, scoped by the owner (NT_GNU_BUILD_ID, NT_GNU_PROPERTY_TYPE_0 = 5...). |
What the Linux loader checks
| Field | Read by load_elf_binary()? |
|---|---|
| e_ident[0..3] | Yes: must be "\x7fELF". |
| e_ident[4..15] | No. Class, endianness, version and OS ABI bytes are not checked by the native loader. |
| e_type | Yes: ET_EXEC or ET_DYN. |
| e_machine | Yes: elf_check_arch(), e.g. EM_X86_64 or EM_AARCH64. |
| e_version | No. |
| e_entry | Yes: where execution starts (plus the load bias for ET_DYN). |
| e_phoff, e_phnum, e_phentsize | Yes: e_phentsize must equal sizeof(struct elf_phdr), and the table (e_phnum * e_phentsize) must be non-empty and at most 64 KiB. |
| e_shoff, e_shnum, e_shentsize, e_shstrndx | No. Section headers are never read. |
| e_flags | Not checked by the generic loader. Some architectures look at it in arch_check_elf() (MIPS floating-point ABI) or save it for core dumps. |
| e_ehsize | No. |
fs/binfmt_elf.c of Linux 7.2. Program headers that matter: PT_LOAD, PT_INTERP, PT_GNU_STACK, PT_GNU_PROPERTY, and PT_LOPROC..PT_HIPROC passed to the architecture code.
Everything else is ignored by the kernel, which is what makes room for tiny hand-made ELF files and for headers that confuse analysis tools.
More details in Linux process execution and the useless ELF header fields.Tools
| Task | Command |
|---|---|
| ELF header | readelf -h a.out |
| Program headers and section to segment mapping | readelf -lW a.out |
| Section headers | readelf -SW a.out |
| Symbols (static / dynamic) | readelf -sW a.out nm -D a.out |
| Relocations | readelf -rW a.out |
| Dynamic section, needed libraries | readelf -d a.out |
| Notes (build ID, GNU properties) | readelf -n a.out |
| Hex dump of a section | readelf -x .rodata a.out |
| Disassemble | objdump -d -M intel a.out |
| Mitigations (PIE, RELRO, NX, canary) | checksec --file=a.out |