Linkers, Loaders & Object Formats

the dynamic linker

/ ld-dot-so, said ell-dee-dot-so /

A program that uses shared libraries arrives on disk incomplete: it knows it needs functions like printf() but does not contain them, because they live in libc.so which is shared by every program on the system. Someone has to find those libraries, load them into memory, and wire up the missing addresses every time the program starts. That someone is the dynamic linker — a small program (on Linux it is ld.so, more precisely ld-linux-x86-64.so.2) that the kernel runs first, before your code, to finish the linking job at load time.

Here is what it does, in order. When you exec a dynamically linked program, the kernel notices the program names an interpreter (recorded in a special PT_INTERP segment) and loads THAT — the dynamic linker — first, handing it the real program. The dynamic linker reads the program's .dynamic section to find its list of needed libraries (the DT_NEEDED tags), searches for each one on disk, maps it into memory, then recursively does the same for libraries those libraries need. Once everything is mapped, it performs the load-time relocations: it fills in each GOT slot with the real address of the symbol it points to, resolving names across all the loaded objects. Only after all of that does it jump to the program's entry point and your code begins to run. It can also be invoked later at runtime through dlopen() to load a plugin on demand.

It matters because it is the piece that makes shared libraries practical: one copy of libc on disk and in memory, shared by hundreds of processes, stitched into each one at startup. It is also where load-time behavior is controlled — environment variables like LD_LIBRARY_PATH and LD_PRELOAD are read by the dynamic linker, and you can ask it to print its decisions with LD_DEBUG. A common misconception is that 'the linker' is one thing; in fact there are two distinct programs: the static linker (ld) runs once at build time to produce the binary, and the dynamic linker (ld.so) runs every time the binary starts to finish the job.

$ readelf -l app | grep interpreter [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2] $ ldd app # ask the dynamic linker which libs it would load libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f...)

The kernel loads the interpreter (ld.so) first; it then finds and maps each needed library and resolves symbols before your code runs.

There are two different linkers: the static linker (ld) runs once at build time; the dynamic linker (ld.so) runs at every startup. A fully static binary has no PT_INTERP and needs no dynamic linker at all.

Also called
ld.sold-linux.sodynamic loaderRTLD動態載入器