An ELF binary can be viewed through two fundamentally different lenses. Sections provide the linker's perspective: a fine-grained decomposition of the file into named regions like .text, .data, and .rodata. Segments provide the loader's perspective: coarse groups of bytes that should be mapped into memory with specific permissions. Mastering ELF means understanding both views and how they relate.
The Linker's View: Sections
Sections are the building blocks that the linker works with. When you compile multiple source files into object files, each object file has its own .text section (code), its own .data section (initialized global variables), its own .rodata section (read-only data like string literals), and so on. The linker's job is to merge matching sections from all input objects into single output sections, resolve cross-references, and assign addresses.
Sections are described by the Section Header Table, an array of Elf64_Shdr structs located at the file offset given by e_shoff in the ELF header. Each entry records the section's name (as an index into the section name string table), type, flags, file offset, size, and alignment. A typical dynamically linked executable might have 25 to 30 sections.
Sections are not required for execution. Tools like strip can remove the entire section header table and all non-loadable sections from a binary. The kernel does not need sections to load and run the program. However, debuggers, profilers, and disassemblers rely heavily on section information to make sense of the binary.
The Loader's View: Segments
Segments are the kernel's roadmap for loading a binary into memory. When you run an executable, the kernel reads the Program Header Table (an array of Elf64_Phdr structs at e_phoff) and uses it to create memory mappings. Each PT_LOAD segment specifies a range of bytes in the file, a virtual address to map them to, and permission flags (read, write, execute).
The kernel does not care about section names or boundaries. It only cares about segments. A single PT_LOAD segment with execute permission might encompass sections .init, .plt, .text, and .fini, all mapped as a single contiguous chunk with r-x permissions.
Beyond PT_LOAD, other segment types carry metadata: PT_INTERP names the dynamic linker, PT_DYNAMIC points to the .dynamic section for runtime linking, PT_NOTE carries build metadata, and PT_GNU_STACK controls stack executability.
How Sections Collapse Into Segments
The relationship between sections and segments is many-to-one: multiple sections can be contained within a single segment. The linker groups sections by their memory protection attributes. Sections that need the same permissions end up in the same segment.
How Sections Collapse Into Segments
This grouping is not arbitrary. Memory protection is applied at the page level (typically 4 KiB granularity). Making each section its own mapping would waste enormous amounts of address space on padding. By coalescing sections with compatible permissions into single segments, the linker minimizes the number of memory mappings and reduces page-alignment waste.
Side-by-Side Comparison
| Aspect | Sections | Segments |
|---|---|---|
| Purpose | Organize data for linking, debugging, and tooling | Describe how to map the file into memory for execution |
| Used by | Linker (ld), debugger (gdb), readelf, objdump | Kernel (execve), dynamic linker (ld-linux.so) |
| Described in | Section Header Table (e_shoff) | Program Header Table (e_phoff) |
| Granularity | Fine-grained: .text, .rodata, .data, .bss, ... | Coarse-grained: LOAD, DYNAMIC, INTERP, ... |
| Required for execution | No (can be stripped entirely) | Yes (kernel refuses to load without them) |
| Naming | Named via string table (.shstrtab) | Identified by type constant (PT_LOAD, etc.) |
Explore the Binary
Toggle between the sections and segments views below to see how the same bytes in the file are organized differently depending on your perspective. The section view shows fine-grained named regions. The segment view shows coarse memory mappings. Hover over any region to see its extent in the byte map.
Can a stripped binary (one with all section headers removed) still be executed?