Linkers, Loaders & Object Formats

sections versus segments

Imagine a long document that you can describe in two different ways. A copy editor sees chapters, footnotes, and an index — fine-grained, named pieces. A printer sees only which pages go on which sheet of paper and whether each sheet should be glossy or matte. Same document, two views aimed at two different jobs. An ELF file is described in exactly these two ways: as sections (the editor's view, for the linker) and as segments (the printer's view, for the loader).

Sections are listed in the section header table. Each section has a name (.text, .data, .rodata, .bss, .symtab, .rela.text, and so on), a type, and flags, and it groups together bytes that belong together for the purpose of linking — all the code, all the read-only data, all the relocation entries, and so on. The linker spends its life merging like-named sections from many .o files. Segments are listed in the program header table, and each segment is a contiguous range of the file (it may cover several sections) plus the memory permissions it should get: typically one read-plus-execute segment carrying .text and .rodata, and one read-plus-write segment carrying .data and .bss. At load time the loader walks the segments and calls mmap()-style mappings; it does not care about section names at all.

The relationship is many-to-one: several sections are usually bundled into one segment, chosen so that sections needing the same permission sit together. A relocatable .o file has sections but typically no useful segments (it is not meant to run); a finished executable or shared object has both. A frequent misconception is that .bss takes space in the file — it does not; its segment reserves zero-filled memory at load, while the file stores only its size. Stripping a binary can remove many sections without touching the segments, so the program still runs but is harder to debug.

$ readelf -l ./a.out # program headers (segments) + mapping Segment Sections... 02 .text .rodata (R E) 03 .data .bss (RW )

readelf -l shows segments and which sections each one carries: code+rodata get read-execute, data+bss get read-write.

The linker reads sections; the loader reads segments. A stripped binary may have no section header table at all yet still run perfectly, because running only needs the segments.

Also called
section header table vs program header table節區標頭表與程式標頭表