the ELF header
If an ELF file is a building, the ELF header is the lobby directory by the front door: a small, fixed block of bytes at the very start of the file that tells you everything you need to begin navigating. It is the first thing the kernel reads when it is asked to run a file, and the first thing a tool like readelf parses. Without it, the rest of the file is just bytes with no meaning.
The header is a fixed-size structure (in 64-bit ELF it is the Elf64_Ehdr struct, 64 bytes). It opens with a 16-byte identification array called e_ident, whose first four bytes are the magic number 0x7f 'E' 'L' 'F', followed by fields recording the class (32- vs 64-bit), the data encoding (little- vs big-endian), and the version. After e_ident come fields naming the object type (relocatable .o, executable, or shared object), the target machine architecture, the entry point address e_entry (the virtual address where execution starts), and crucially two pairs of fields: e_phoff with e_phnum, which locate and count the program header table, and e_shoff with e_shnum, which locate and count the section header table. So the header itself contains no code or data — it is a map that says where the two big tables live.
It matters because it is the single trusted starting point. The kernel checks the magic and the class to decide whether it can even run this file on this machine, then jumps via e_phoff to find the segments to load. A common confusion is mixing up e_entry with main(): e_entry usually points at a small startup routine (often _start) provided by the C runtime, which sets things up and only later calls your main().
// 64-bit ELF header, key fields unsigned char e_ident[16]; // 7f 45 4c 46 02 01 01 ... uint16_t e_type; // ET_EXEC / ET_DYN / ET_REL uint64_t e_entry; // virtual address of first instruction uint64_t e_phoff; // file offset of program header table uint64_t e_shoff; // file offset of section header table
The header does not hold code; it holds offsets (e_phoff, e_shoff) that point to the program and section header tables.
e_entry is the entry point, not your main(). On a typical glibc program it points to _start, which the C runtime supplies; main() is called from there after setup.