ELF Loading
ELF (Executable and Linkable Format) is the binary format nearly every Unix-like operating system uses for executables and shared libraries. Loading an ELF file means parsing a fixed set of headers and mapping the memory segments they describe, before transferring control to the entry point those headers name.
Two kinds of header
Section titled “Two kinds of header”An ELF file opens with a file header: a magic number (0x7F, then E, L, F), a class byte marking 32-bit or 64-bit, an entry point address, and pointers to two tables further into the file. The program header table describes how to load the file into memory. The section header table describes how the file is organized for linking and debugging. A loader only needs the first one. Section headers matter to a linker, not to whatever puts a program on the CPU.
Program headers: what to map
Section titled “Program headers: what to map”Each program header entry of type PT_LOAD names a chunk of the file, its size, the virtual address it belongs at (randomized by ASLR where enabled), and permission flags (readable, writable, executable, in some combination). A loader’s job is mechanical once the headers are parsed. For each PT_LOAD entry: allocate pages covering that virtual range, copy the matching bytes from the file, zero anything past the file’s own length up to the segment’s declared memory size, and set page permissions to match the flags.
for (int i = 0; i < header.e_phnum; i++) { Elf64_Phdr *ph = &program_headers[i]; if (ph->p_type != PT_LOAD) continue;
void *dest = map_pages(ph->p_vaddr, ph->p_memsz, ph->p_flags); memcpy(dest, file_data + ph->p_offset, ph->p_filesz); memset((char *)dest + ph->p_filesz, 0, ph->p_memsz - ph->p_filesz);}A segment’s file size and memory size are not always equal. The difference, when one exists, corresponds to uninitialized global variables: declared in the source, present in the address space, absent from the file on disk, since a variable whose initial value is zero requires no stored data at all.
The entry point
Section titled “The entry point”The file header’s entry point is a virtual address, not a file offset, and is only valid once every PT_LOAD segment has been mapped. Executing it beforehand transfers control to whatever data previously occupied that address.
A process loaded this way can execute its own code and access its own data, but has no capability beyond that. It cannot call a C library function, since none is linked in, and it cannot make a system call without a stack already established for that purpose. A dynamically linked binary additionally requires its interpreter, named in a PT_INTERP program header entry, to be loaded and executed before the binary’s own entry point is reached. A statically linked binary with no external dependencies has no such requirement: only its PT_LOAD segments need mapping before its entry point can be invoked.
References
Section titled “References”- ^ System V Application Binary Interface: the specification defining the ELF file format and program header structure described above.
See also
Section titled “See also”- Multitasking: where a Process Control Block for the loaded process comes from.
- System Calls: what a loaded process needs before it can ask the kernel for anything.
- Security Mitigations: ASLR, the technique that randomizes where the PT_LOAD segments above end up mapped.