When ld.so loads your program, how does it know which shared libraries you need? Where is the symbol table? Where are the relocations? The answer is the .dynamic section — a table of key-value pairs that tells the dynamic linker everything it needs to know.
The .dynamic section is the "instruction manual" that ld.so reads to set up your program's runtime environment.
Think of .dynamic as a configuration file embedded in the ELF binary. Each entry is a tag-value pair:
Each dynamic entry is an Elf64_Dyn structure:
# Elf64_Dyn structure (16 bytes): # d_tag (Elf64_Sxword) What this entry describes # d_un (union) Value: either d_val (integer) or d_ptr (address)
Key tags:
| Tag | Value | Purpose |
|---|---|---|
| DT_NEEDED | 1 | Shared library dependency (offset into DT_STRTAB) |
| DT_SYMTAB | 6 | Address of .dynsym section |
| DT_STRTAB | 5 | Address of .dynstr section |
| DT_RELA | 7 | Address of .rela.dyn section |
| DT_RELASZ | 8 | Size of .rela.dyn in bytes |
| DT_JMPREL | 23 | Address of .rela.plt (PLT relocations) |
| DT_INIT | 12 | Initialization function address |
| DT_FINI | 13 | Finalization function address |
| DT_INIT_ARRAY | 25 | Array of init functions |
| DT_NEEDED | 1 | Each occurrence = one library dependency |
$ readelf -d /bin/ls Dynamic section at offset 0x3ba0 contains 30 entries: Tag Type Name/Value 0x0000000000000001 (NEEDED) Shared library: [libc.so.6] 0x000000000000000c (INIT) 0x3000 0x000000000000000d (FINI) 0x5c1c 0x0000000000000019 (INIT_ARRAY) 0x3be8 0x000000000000001b (INIT_ARRAYSZ) 8 (bytes) 0x000000000000001a (FINI_ARRAY) 0x3bf0 0x000000000000001c (FINI_ARRAYSZ) 8 (bytes) 0x0000000000000005 (STRTAB) 0x2288 0x0000000000000006 (SYMTAB) 0x1a80 0x000000000000000a (STRSZ) 568 (bytes) 0x000000000000000b (SYMENT) 24 (bytes) 0x0000000000000015 (PLTREL) RELA 0x0000000000000002 (PLTGOT) 0x3fe8 0x0000000000000017 (JMPREL) 0x2918 0x0000000000000007 (RELA) 0x27e0 0x0000000000000008 (RELASZ) 312 (bytes) 0x0000000000000009 (RELAENT) 24 (bytes)
The DT_NEEDED entries tell you exactly which libraries are required:
A complex program might show dozens of DT_NEEDED entries for different libraries.
# See the dynamic section of any ELF readelf -d /bin/ls # Count library dependencies readelf -d /bin/ls | grep NEEDED | wc -l # See what a typical program links against readelf -d /usr/bin/python3 | grep NEEDED # Compare: static vs dynamic binary gcc -o dynamic hello.c gcc -static -o static hello.c readelf -d dynamic # has .dynamic section readelf -d static # no .dynamic section (or empty)
What is the purpose of DT_NEEDED entries in the .dynamic section?
If you see a DT_NEEDED entry for "libfoo.so.3" but the library is installed as "libfoo.so.4", what will happen?
The .dynamic section lists library dependencies. But how do shared libraries themselves work — how are they built, versioned, and found at runtime? That's the next concept.