Shared Library Tools & Versioning
Mastering ldd, objdump, readelf, nm — plus real name, soname, and linker name conventions for library versioning.
Key Concepts
The previous part established how a shared library is created, embedded with a soname, and how the dynamic linker resolves it at runtime. The four-step process is shown below for reference before we dive into tools.
gcc -g -c -fPIC -Wall \
mod1.c mod2.c mod3.c
gcc -shared -o libfoo.so \
-Wl,-soname,libbar.so \
mod1.o mod2.o mod3.o
gcc -o prog prog.c libfoo.so
ln -s libfoo.so libbar.so
LD_LIBRARY_PATH=. ./prog → dynamic loader finds libbar.so symlink → loads libfoo.so into virtual memory💡 Why the soname indirection?
The program records libbar.so (the soname), not libfoo.so (the real filename). This allows the physical library file to be swapped out (e.g., upgraded from libfoo.so.1 to libfoo.so.1.2) without relinking every binary — only the symlink changes.
Linux provides several command-line tools that let you inspect shared libraries, executables, and compiled object files. These tools are essential for debugging dependency problems, verifying symbol availability, and understanding ELF structure.
ldd Commandldd(1) stands for list dynamic dependencies. It displays all shared libraries that a program (or another shared library) requires at runtime.
$ ldd prog
libdemo.so.1 => /usr/lib/libdemo.so.1 (0x40019000)
libc.so.6 => /lib/tls/libc.so.6 (0x4017b000)
/lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)
For each library dependency, ldd resolves it using the same search conventions as the dynamic linker and prints:
library-name => resolves-to-path
For most ELF executables, ldd always shows at minimum:
ld-linux.so.2— the dynamic linker / loader itselflibc.so.6— the standard C library
⚠️ Architecture Note
The C library name varies by architecture. On IA-64 and Alpha, it is named libc.so.6.1 rather than libc.so.6.
⚠️ Security Caution with ldd
Never run ldd on untrusted binaries. Internally, ldd may invoke the binary with a special environment variable that causes the dynamic linker to print dependencies. On some systems, this can execute the binary’s initialization code. Use objdump -p binary | grep NEEDED as a safer alternative.
objdump and readelf CommandsBoth commands extract and display structural information from ELF files (executables, shared libraries, compiled object files). Their capabilities overlap but output format differs.
| Command | Primary Use | Example |
|---|---|---|
| objdump | Disassemble machine code, display ELF section headers, segment info | objdump -d prog |
| objdump -p | Display program headers (safer NEEDED-library check than ldd) | objdump -p prog | grep NEEDED |
| readelf | Display ELF header, section headers, symbol tables in structured format | readelf -d prog |
| readelf -d | Show dynamic section (dependencies, soname, RPATH) | readelf -d libfoo.so | grep SONAME |
✅ Practical Tip
To confirm the soname embedded in a shared library you just built, run: readelf -d libfoo.so | grep SONAME. You should see the soname you passed via -Wl,-soname,....
nm Commandnm lists the symbols (functions, global variables) defined in an object file, shared library, or executable. A particularly useful application is finding which library defines a specific symbol when you have multiple candidate libraries.
# Find which library defines crypt()
$ nm -A /usr/lib/lib*.so 2>/dev/null | grep ' crypt$'
/usr/lib/libcrypt.so:00007080 W crypt
Key flags:
-A— Prefix every output line with the library filename. Without this,nmprints the filename once, then all its symbols — making grep output useless for identifying the source library.2>/dev/null— Suppresses error messages for non-ELF files (scripts, symlinks, etc.) thatnmcannot process.
Symbol Type Letters
In nm output, the letter before the symbol name indicates its type. Common ones: T = text (function, defined), W = weak symbol, U = undefined (imported from another library), D = initialized data, B = BSS (uninitialised data).
| Tool | What it answers | Typical usage |
|---|---|---|
| ldd | What shared libraries does this binary need? | ldd ./prog |
| objdump -d | What is the disassembled machine code? | objdump -d ./prog | less |
| objdump -p | What libraries does this ELF depend on? (safe alternative to ldd) | objdump -p ./prog | grep NEEDED |
| readelf -d | What is the soname / RPATH / RUNPATH embedded in this ELF? | readelf -d ./libfoo.so | grep -E 'SONAME|RPATH' |
| readelf -s | What symbols are exported or imported? | readelf -s ./libfoo.so |
| nm -A | Which library defines a specific symbol? | nm -A /usr/lib/lib*.so 2>/dev/null | grep ' func_name$' |
| /proc/PID/maps | Which shared libraries is a running process currently using? | cat /proc/1234/maps | grep '\.so' |
Shared libraries evolve over time. Linux uses a structured naming convention to allow multiple versions of the same library to coexist on a system, ensuring backward compatibility for existing programs while allowing new programs to use updated versions.
Two types of version changes exist in shared library evolution:
Compatible changes. Same calling interface, semantically equivalent functions.
- Bug fixes
- Performance improvements
- New functions added (ABI-additive)
- Existing programs keep working
Incompatible changes. Existing binaries may break.
- Function signatures changed
- Structs/types changed size/layout
- Semantics fundamentally altered
- Old programs must keep the old major version
The key challenge: programs linked against the old major version must still be runnable while new programs can use the new major version. The three-name convention solves this.
Every shared library typically has three names associated with it:
Real Name
The actual filename on disk. Contains both major and minor version numbers.
libfoo.so.1.0.0
Format: libNAME.so.MAJOR.MINOR.PATCH
Soname
Embedded inside the ELF file. Contains only the major version. A symlink with this name points to the real name.
libfoo.so.1 → libfoo.so.1.0.0
Set at build time with -Wl,-soname,libfoo.so.1
Linker Name
Used by the linker (-lfoo) at compile time. No version number. A symlink pointing to the soname or real name.
libfoo.so → libfoo.so.1
Created with ldconfig or manually
(compile time)
(runtime resolution)
(actual file)
libfoo.so.1.1.0 is installed, only the libfoo.so.1 symlink is updated → all programs dynamically linked against major version 1 automatically get the new minor version.ldconfigIn production, soname symlinks are managed automatically by ldconfig(8), not created manually. Running ldconfig after installing a library:
- Scans standard library directories (
/lib,/usr/lib, directories listed in/etc/ld.so.conf) - Creates/updates soname symlinks to point at the latest real name for each major version
- Rebuilds the
/etc/ld.so.cachebinary cache used by the dynamic linker for fast lookups
# After installing a new library, run:
$ sudo ldconfig
# Verify the soname symlink was created:
$ ls -la /usr/lib/libfoo.so*
lrwxrwxrwx libfoo.so -> libfoo.so.1
lrwxrwxrwx libfoo.so.1 -> libfoo.so.1.0.0
-rwxr-xr-x libfoo.so.1.0.0
✅ Developer Workflow
During development you create symlinks manually (as shown in Figure 41-1). In deployment/packaging (RPM, DEB), the package post-install script calls ldconfig to set everything up correctly.
🎯 Interview Questions — Shared Library Tools & Versioning
ldd do and what is a security concern with it?ldd lists the dynamic library dependencies of a binary by resolving each dependency using the same rules as the dynamic linker. The security risk is that on some systems, ldd internally invokes the binary with a special environment variable, which can trigger execution of the binary’s initialization code — potentially harmful if the binary is untrusted. Use objdump -p binary | grep NEEDED as a safer alternative.objdump and readelf?objdump is broader — it can disassemble machine code and display ELF headers in various formats. readelf is focused purely on ELF structure (headers, sections, segments, symbols, dynamic entries) and provides more ELF-centric output. For checking a soname or RPATH, readelf -d is typically more convenient.nm -A /usr/lib/lib*.so 2>/dev/null | grep ' function_name$'. The -A flag prefixes each line with the library filename, making it possible to grep and see exactly which library owns the symbol.libfoo.so.1.0.0) is the actual file on disk containing the library code. The soname (libfoo.so.1) is embedded in the ELF file and contains only the major version; a symlink with the soname points to the real name and is updated by ldconfig when a new minor version arrives. The linker name (libfoo.so) is a version-less symlink used at compile time with -lfoo; it points to the soname or real name.ldconfig do?ldconfig scans standard library directories and /etc/ld.so.conf, creates or updates soname symlinks to point at the newest real name for each major version, and regenerates /etc/ld.so.cache — a binary database used by the dynamic linker to resolve library names quickly without scanning directories at runtime. It must be run after installing or removing shared libraries./proc/PID/maps, the Linux-specific per-process memory map file. Each line shows a memory region; lines ending in .so (or similar) are mapped shared library segments. For example: cat /proc/$(pgrep prog)/maps | grep '\.so'.