Reading — step 1 of 5
Read
~1 min readDynamic Linking
GOT and PLT
Position-independent code can't bake in absolute addresses (the loader picks the load address). Instead: indirection via GOT and PLT.
GOT (Global Offset Table):
- Per-shared-object table of pointers.
- For data accesses:
mov rax, [GOT[symbol]]. - Dynamic linker fills in addresses at load time.
PLT (Procedure Linkage Table):
- Stub functions for external function calls.
call foo@plt→ jumps to PLT[foo] → looks up GOT[foo] → calls actual foo.
Lazy resolution (default):
- First call to PLT[foo]: jumps to dynamic linker stub.
- Linker resolves foo, fills GOT entry.
- PLT updates so subsequent calls go directly.
- Saves startup time but slows first call.
-z now (or LD_BIND_NOW=1): resolve all symbols at startup. Slower start, faster later.
PLT entry (x86-64):
foo@plt:
jmp [GOT[foo]]
push <reloc_index>
jmp _PLT0 # generic resolver stub
First time: GOT[foo] = address of "push + jmp resolver" stub. Resolver looks up foo, writes real address to GOT[foo]. Returns.
Second time: jmp [GOT[foo]] goes directly to foo.
GOT layout:
- GOT[0]: address of .dynamic.
- GOT[1]: link map (struct used by linker).
- GOT[2]: address of resolver function.
- GOT[3+]: per-symbol entries.
Security:
- Original GOT was writable (needed for lazy fill).
- Attacker who corrupts GOT can redirect calls.
- RELRO (Relocation Read-Only): mark GOT read-only after init. Mitigation.
- Full RELRO +
-z now: GOT fully filled at start, then read-only. Strongest.
Modern compilers default to RELRO + lazy. Full RELRO is opt-in for security-critical apps.
Inspecting:
objdump -R foo: list dynamic relocations.readelf --dyn-syms foo: dynamic symbol table.objdump -d -j .plt foo: PLT disassembly.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…