Dependency Doctor
error while loading shared libraries names one library and stops. This tool shows
the whole dependency list the loader will walk, the search paths baked into the binary, and —
if you paste your ldd output — which entries actually resolved on the host.
How the loader actually searches
When you execute a dynamically linked binary the kernel reads PT_INTERP, starts
that dynamic loader, and hands it the file. The loader then resolves each
DT_NEEDED entry by walking a fixed search order. Getting this order wrong is the
single most common reason a library is "installed but not found":
-
DT_RPATH— only if the binary has noDT_RUNPATH. It sits beforeLD_LIBRARY_PATH, which is why a stale RPATH can shadow the library you just exported. LD_LIBRARY_PATH— the environment override, ignored for setuid binaries.DT_RUNPATH— the modern replacement; deliberately searched after the environment./etc/ld.so.cache— the compiled index maintained byldconfig./lib,/usr/liband their multiarch equivalents.
Every path in that list is a directory search for a filename, not a version match.
The loader does not know what version you meant — it knows what filename the binary recorded
in DT_NEEDED. That is why installing OpenSSL 3 on a distribution expecting
OpenSSL 1.1 produces "missing libssl.so.1.1": the file genuinely does not exist
under that name, even though "openssl is installed".
Why ldd is useful but not safe
ldd is a shell script that runs the loader in a special mode; on some systems it
executes the binary's dependencies. Never run ldd against a binary you do not
trust — the documented alternative is objdump -p file | grep NEEDED, which only
reads the file. This tool does the same thing structurally: it parses
DT_NEEDED out of the dynamic section and never executes anything.
Combining both gives the useful answer. The binary tells you what it wants; the
ldd output tells you what the host resolved. Pasting the loader output here maps
one onto the other, so a library that is not found is highlighted next to its
position in the NEEDED list, along with the search paths that were in effect.
Static binaries and glibc are not the same thing
A statically linked binary has no PT_INTERP and no DT_NEEDED: there
is nothing to resolve and no loader involved. A binary linked against glibc's static
libc.a is still not portable to a musl system in the other direction — it simply
does not need musl at all, which mostly works, but calls like getpwnam() that
depend on NSS modules will fail silently at runtime. If the goal is "one binary that runs
everywhere", the answer is usually musl-gcc -static or shipping the loader and
libraries alongside, not a glibc static build.