Linux shared libraries: link time vs load time, SONAME, RPATH, and naming
- 1. Link time vs load time
- 2.
DT_NEEDED, SONAME, RPATH/RUNPATH resolution, and$ORIGIN - 3. Linux shared library naming (real name, SONAME, linker name)
- 4. Why “exact file match” at link/load time vs why naming convention is enough
1. Link time vs load time
Link time (ld / g++ at build) |
Load time (ld.so at run) |
|
|---|---|---|
| What happens | Resolves symbols, records DT_NEEDED entries (dependency names), may embed RPATH/RUNPATH. |
Loads .so files into the process, resolves NEEDED to actual files on disk. |
| What is stored | Usually SONAME strings (e.g. libfoo.so.3), not full paths — plus optional RPATH/RUNPATH. |
Uses RPATH/RUNPATH, LD_LIBRARY_PATH, ld.so.cache, default dirs to find files. |
| Why they differ | The linker picks one file per -l / -L order. |
The loader may find a different path with the same SONAME — behavior is still OK if ABI matches. |
So “linked against X” and “loaded from path Y” are related by SONAME and ABI, not by identical file paths.
RPATH vs RUNPATH quick note:
RUNPATH(newer,DT_RUNPATH) is generally preferred in modern toolchains and is intended to be overridable byLD_LIBRARY_PATH.RPATH(legacy,DT_RPATH) can be consulted earlier and can affect transitive lookup behavior differently in some dependency chains.
2. DT_NEEDED, SONAME, RPATH/RUNPATH resolution, and $ORIGIN
DT_NEEDED (the “NEEDED” section)
- Each dependency is recorded as a string, almost always the SONAME (e.g.
libgrpc.so.10,libz.so.1). - It is not a full path like
/usr/lib/x86_64-linux-gnu/libz.so.1.
SONAME
- Embedded in the shared library and used as the key for loading.
- Same SONAME ⇒ same ABI family (by project policy): old binaries should keep working with newer minors that keep that SONAME.
RPATH / RUNPATH
- Embedded in the executable or
.so: extra directories for the loader to search forNEEDEDlibraries. $ORIGINexpands to the directory of the current binary or.so, so you can use paths relative to the installed file, e.g.$ORIGIN/../lib.- In CMake,
$ORIGINis often written as\$ORIGINso CMake does not treat it as a variable. DT_RPATHandDT_RUNPATHdiffer mainly in precedence and transitive lookup behavior;RUNPATHis newer and generally preferred for override flexibility.
Typical resolution order (glibc ld.so, simplified; nuanced)
- If loading object has no RUNPATH: consult its RPATH chain (loading object RPATH, then loader’s RPATH up the chain, with RUNPATH boundaries).
LD_LIBRARY_PATH(unless ignored by secure-execution mode such as setuid/setgid).- RUNPATH of the loading object.
/etc/ld.so.cache.- Default directories (
/lib,/usr/lib, …).
Unless loading object has RUNPATH:
RPATH of the loading object,
then the RPATH of its loader (unless it has a RUNPATH), ...,
until the end of the chain, which is either the executable
or an object loaded by dlopen
Unless executable has RUNPATH:
RPATH of the executable
LD_LIBRARY_PATH
RUNPATH of the loading object
ld.so.cache
default dirs
In modern Linux practice, RPATH is generally considered legacy/deprecated in favor of RUNPATH.
Recommended approach: use RUNPATH for bundled defaults, and use LD_LIBRARY_PATH when you need to override which concrete library build is loaded at runtime.
So RUNPATH is typically after LD_LIBRARY_PATH, while legacy RPATH can be consulted earlier when RUNPATH is absent on the loading path.
Reference: Stack Overflow discussion: use RPATH but not RUNPATH?
3. Linux shared library naming (real name, SONAME, linker name)
Three related names:
| Role | Example | Meaning |
|---|---|---|
| Real name | libfoo.so.3.2.1 |
Actual file on disk: major.minor.patch (or similar). |
| SONAME | libfoo.so.3 |
ABI / major line: recorded in DT_NEEDED, must stay stable for compatible upgrades. |
| Linker name | libfoo.so |
Symlink for compile-time -lfoo; points at current dev/SONAME target. |
Version digits (typical convention):
- First number (SONAME major, e.g.
3) — incompatible ABI change ⇒ new SONAME (e.g.libfoo.so.4). Old programs linked tolibfoo.so.3do not uselibfoo.so.4automatically. - Minor / patch (e.g.
2,1) — within the same SONAME: backward compatible changes (new symbols allowed in minor; patch = bugfix). SONAME often stayslibfoo.so.3.
4. Why “exact file match” at link/load time vs why naming convention is enough
Same process, same SONAME → one loaded library
DT_NEEDEDonly namesliba.so.3, not3.1vs3.2.- The loader maps one
liba.so.3into the process; all dependents share it. - Example: if two dependent libraries were linked at build time against
a.3.1anda.3.2, at runtime one process still usually loads only oneliba.so.3implementation. So at least one side may not get the exact patch/minor file it saw at link time. This is exactly why the SONAME convention exists:a.3.1anda.3.2(same SONAME familyliba.so.3) are expected to be interchangeable for existing ABI usage. - Convention: any installed
liba.so.3.x.ywith SONAMEliba.so.3must be ABI-compatible for symbols that older clients use; newer minor usually works for older binaries; older minor may miss new symbols required by something built against a newer minor (edge case).