Every dynamically-linked program on Linux starts with ld.so, not main(). The dynamic linker loads all shared libraries, processes relocations, and only then hands control to your program. Understanding ld.so explains "library not found" errors, how LD_PRELOAD works, and why some programs crash at startup.
ld.so is the first thing that runs when you start a dynamically-linked program:
The library search order (in order of priority):
DT_RPATH: embedded in the binary (deprecated, but still works)
LD_LIBRARY_PATH: environment variable (colon-separated list)
DT_RUNPATH: embedded in the binary (preferred over DT_RPATH)
/etc/ld.so.cache: pre-built cache from ldconfig (fast lookup)
/lib and /usr/lib: default paths
Key environment variables:
| Variable | Purpose | Example |
|---|---|---|
| LD_LIBRARY_PATH | Additional library search paths | LD_LIBRARY_PATH=/opt/foo/lib ./myprog |
| LD_PRELOAD | Force-load libraries before all others | LD_PRELOAD=libmalloc.so ./myprog |
| LD_BIND_NOW | Disable lazy binding (resolve all symbols at load) | LD_BIND_NOW=1 ./myprog |
| LD_DEBUG | Print linker debug info | LD_DEBUG=libs ./myprog |
| LD_TRACE_LOADED_OBJECTS | List dependencies and exit (like ldd) | LD_TRACE_LOADED_OBJECTS=1 ./myprog |
# Watch ld.so search for libraries
$ LD_DEBUG=libs ./myprog | head -20
12345: find library=libc.so.6 [0]; searching
12345: search cache=/etc/ld.so.cache
12345: trying file=/lib/x86_64-linux-gnu/libc.so.6
12345: init: /lib/x86_64-linux-gnu/libc.so.6
12345: find library=libfoo.so.1 [0]; searching
12345: search cache=/etc/ld.so.cache
12345: trying file=/usr/lib/x86_64-linux-gnu/libfoo.so.1
12345: init: /usr/lib/x86_64-linux-gnu/libfoo.so.1
# See what ldd actually does
$ ldd /bin/ls
linux-vdso.so.1 (0x00007ffd...)
libselinux.so.1 --- /lib/x86_64-linux-gnu/libselinux.so.1
librt.so.1 --- /lib/x86_64-linux-gnu/librt.so.1
libc.so.6 --- /lib/x86_64-linux-gnu/libc.so.6The LD_DEBUG output shows the exact search sequence — incredibly useful for debugging "library not found" errors.
# Debug library loading LD_DEBUG=libs ls | head -30 # Trace symbol resolution LD_DEBUG=bindings ls | head -20 # Force a library to be loaded first (interposition) LD_PRELOAD=/lib/x86_64-linux-gnu/libc.so.6 ls # Check what a binary needs without running it ldd /bin/ls # Find which package provides a library dpkg -S /lib/x86_64-linux-gnu/libc.so.6
What is the purpose of LD_PRELOAD?
You get "error while loading shared libraries: libfoo.so.3: cannot open shared object file". What are the most likely causes?
You now understand the full dynamic linking pipeline. The final module covers how the OS loader actually runs an ELF program — the kernel-side counterpart to ld.so.
Put the dynamic linker loading steps in the correct order (drag to reorder):