5.3.4Build Systems & Toolchain

Dynamic - shared libraries — .so - .dll, dynamic linking, PIC

2,198 words10 min readdifficulty · medium1 backlinks

WHY do shared libraries exist?

The three reasons dynamic linking won:

  1. Disk + RAM savings. One copy of libc (~2 MB) shared by 200 processes instead of 200 copies. The OS maps the same physical pages into every process — but only the read-only code pages, not the writable data.
  2. Update without recompiling. Patch a security bug in libssl.so; every program picks it up on next launch. With static linking you'd relink the whole world.
  3. Plugin architecture. dlopen() / LoadLibrary() let a program load code it didn't know about at compile time.

WHAT is Position-Independent Code (PIC)?

HOW does PIC reach global data and functions?

Relative addressing is easy for internal jumps. The hard part is external symbols (a function in another library, or a global variable). Two tables solve this:

Figure — Dynamic - shared libraries — .so  -  .dll, dynamic linking, PIC

HOW dynamic linking happens — step by step


Derivation: why the code page must stay constant


Flashcards

What is the one-line difference between static and dynamic linking?
Static copies library code into the executable; dynamic keeps a reference and loads the code from a separate .so/.dll at run time.
Why must shared-library code be position-independent?
So the identical read-only code pages can be mapped at different virtual addresses in different processes and still execute correctly, enabling physical RAM sharing.
What flag compiles PIC in GCC/Clang?
-fPIC (and -shared to produce the .so).
What does the GOT store and why is it writable?
Pointers (real addresses) to external functions/data; it's writable so the dynamic loader can patch the correct per-process addresses at load/run time.
What is the PLT and what is "lazy binding"?
A table of stubs that resolve a function's real address on its first call, store it in the GOT, then jump — later calls skip resolution. Lazy = resolve on first use, not at startup.
Where does the per-process absolute address actually live in a PIC library?
In the GOT (a data page), never in the code page.
On Windows, what plays the role of the GOT?
The Import Address Table (IAT), patched by the loader.
Why can dynamic linking be faster for the whole system despite startup cost?
The shared code pages are often already resident/cached because other processes use them, saving RAM and page-cache pressure.
What program performs dynamic linking at startup on Linux?
The dynamic linker/loader ld-linux.so, named as the executable's interpreter.
Why does link-time success not guarantee run-time loading?
Link time only verifies the symbol exists and records the dependency name; run time requires physically finding the .so file in the search paths.

Recall Feynman: explain to a 12-year-old

Imagine 30 kids all need the same recipe book. Instead of photocopying it 30 times (wasting paper = static linking), the school keeps one book in the library and every kid just remembers "go look it up in the library" (dynamic linking). But the kids' classrooms are in different parts of the building, so the book can't say "turn left at room 12" (that depends where you are). Instead it says "turn left 2 doors from where you're standing" — directions that work from anywhere. Those self-relative directions are PIC. And each kid keeps a little sticky note (the GOT) with the exact page numbers filled in just for them.


Connections

  • Static libraries — .a .lib — the alternative; trade-offs in size, updatability, page cache.
  • The Linker — symbol resolution & relocation — relocations are what fill the GOT.
  • Virtual Memory & mmap — shared read-only pages are the mechanism behind RAM savings.
  • ASLR & Security — PIC also enables address randomization of libraries (and PIE executables).
  • dlopen and Plugin Architectures — run-time loading of unknown-at-compile-time code.
  • ELF and PE file formatsDT_NEEDED, GOT/PLT sections vs Windows IAT.

Concept Map

copies code into

keeps only reference

resolved at

one copy shared in

enables

patched without relink

loaded via dlopen

requires

compiled with

uses only

works at any

allows read-only sharing of

Static linking .a .lib

Fat self-contained binary

Dynamic linking .so .dll

Shared library file

Load time or first call

Shared RAM pages

Disk and RAM savings

Update without recompiling

Plugin architecture

Position-Independent Code

-fPIC flag

Relative addressing

Load address

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Dekho, shared library ka pura idea simple hai: ek hi libc.so ya libfoo.so file disk pe hoti hai, aur 200 programs us same file ko use karte hain. Static linking mein library ka code seedha executable ke andar copy ho jaata hai — binary mota ho jaata hai aur har program apni alag copy carry karta hai. Dynamic linking mein executable sirf ek "promise" rakhta hai: "mujhe printf chahiye, run time pe dhoond lena." Fayda? RAM aur disk bachti hai (OS same physical pages sab processes mein map kar deta hai), aur agar library mein bug fix karna ho to bas .so update karo — saare programs automatically naya code le lete hain.

Ab asli twist: PIC (Position-Independent Code). Same library alag-alag process mein alag-alag virtual address pe load ho sakti hai. Agar code mein koi absolute address hardcode hota (jaise "printf 0x7f0000 pe hai"), to wo ek process mein sahi, dusre mein galat ho jaata. Isliye PIC code sirf relative addressing use karta hai — "current instruction se itne offset pe." Compile karte waqt -fPIC flag isi liye lagta hai. Logic chain yaad rakho: shared code page ke bytes sab process mein same hone chahiye → koi per-process value code mein nahi reh sakti → wo address ek alag writable data table (GOT) mein jaata hai.

GOT aur PLT do tables hain. GOT (Global Offset Table) ek writable data page hai jisme loader real addresses bharta hai, har process ke liye alag. PLT (Procedure Linkage Table) chhote stubs hain jo "lazy binding" karte hain — function ka pehla call hone par hi dynamic linker (ld-linux.so) us function ka address dhoond ke GOT mein likhta hai; baad ke calls seedhe GOT se jump kar jaate hain. Isse startup fast rehta hai kyunki sab functions ek saath resolve nahi karne padte.

Ek practical point: gcc app.o -lfoo compile ho jaana matlab program chalega bhi, ye guarantee nahi. Link time pe sirf symbol exist karta hai ye check hota hai. Run time pe ld-linux ko file physically dhoondhni padti hai (/usr/lib, LD_LIBRARY_PATH, ldconfig cache mein). Isiliye "error while loading shared libraries" tab aata hai jab .so move ho gayi ho. Windows mein same concept hai bas naam alag — .dll, IAT (Import Address Table) GOT jaisa, aur exports explicitly __declspec(dllexport) se mark karne padte hain.

Go deeper — visual, from zero

Test yourself — Build Systems & Toolchain

Connections