What happens when your code calls printf()? And what’s up with all that .got.plt crap that you see when you decompile a binary? Here are some sketches I drew to help me remember this kind of stuff.
First of all, we need to understand the difference between a statically linked binary and a dynamically linked one:
A statically linked binary contains all the program’s code and the library code already linked together in a single binary blob.
In many cases, we don’t really want to compile the whole libc inside each of our binaries. In order to be dynamically linked, the binary contains only the user code, together with some relocation information that are used at runtime to resolve the location of library functions on the host machine.
Linker-Related ELF sections
Dynamically linked binaries have a bunch of information that is used to link them together:
Libraries (.so, shared-object files) keep a table of exported symbols, with their names and addresses.
Programs that want to link against those libraries expose a list of imported symbols, which have a corresponding entry in the PLT (Procedure Linkage Table). The PLT entry is executed every time we call the library function, and this will cause the code to jump to an address specified in the GOT (Global Offset Table).
Lazy Loading
The procedure of jumping to the library function and finding the appropriate address is described below:
Bonus: Linking Flags
Some flags can be specified to avoid the “lazy” part of dynamic loading.