Shared Library Tools & Versioning

 

TLPI · Chapter 41 · Part 4

Shared Library Tools & Versioning

Mastering ldd, objdump, readelf, nm — plus real name, soname, and linker name conventions for library versioning.

Key Concepts

ldd objdump readelf nm soname real name linker name major version minor version ELF dynamic linker shared library -fPIC LD_LIBRARY_PATH

4 Key Tools
3 Name Types
2 Version Levels
4 Build Steps

41.4 Recap: Shared Library Build & Runtime Flow

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.

Figure 41-1 · Build Steps: Compile → Link → Program → Symlink

Step 1 · Compile PIC objects
gcc -g -c -fPIC -Wall \
  mod1.c mod2.c mod3.c
Produces mod1.o, mod2.o, mod3.o
Step 2 · Create shared library
gcc -shared -o libfoo.so \
  -Wl,-soname,libbar.so \
  mod1.o mod2.o mod3.o
ELF embeds soname = libbar.so

Step 3 · Link program
gcc -o prog prog.c libfoo.so
prog records dependency: libbar.so
Step 4 · Soname symlink
ln -s libfoo.so libbar.so
libbar.so → libfoo.so (runtime resolution)
At runtime: 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.

41.5 Useful Tools for Working with Shared Libraries

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.

The ldd Command

ldd(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 itself
  • libc.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.

The objdump and readelf Commands

Both 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,....

The nm Command

nm 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, nm prints 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.) that nm cannot 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).

Quick Tool Reference
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'

41.6 Shared Library Versions and Naming Conventions

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.

Major Versions vs. Minor Versions

Two types of version changes exist in shared library evolution:

Minor Version

Compatible changes. Same calling interface, semantically equivalent functions.

  • Bug fixes
  • Performance improvements
  • New functions added (ABI-additive)
  • Existing programs keep working
Major Version

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.

The Three-Name Convention

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.1libfoo.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.solibfoo.so.1

Created with ldconfig or manually

Symlink Chain — Real Name · Soname · Linker Name
libfoo.so
linker name
(compile time)
libfoo.so.1
soname
(runtime resolution)
libfoo.so.1.0.0
real name
(actual file)
When 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.

Managing Symlinks with ldconfig

In 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.cache binary 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

What does 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.
What is the difference between objdump and readelf?
Both examine ELF files but serve different purposes. 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.
How do you find which shared library defines a specific function symbol?
Use 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.
Explain the three names of a shared library and what each is used for.
The real name (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.
What is the difference between a major and minor version of a shared library?
A minor version is a compatible update — same calling interface, same semantics — so existing programs continue to work without relinking. A major version is an incompatible change (changed function signatures, changed struct layouts, or altered semantics) requiring programs that depend on the old interface to keep using the old major version. The soname (major version only) ensures the right major version is loaded at runtime while allowing transparent minor version upgrades.
What does 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.
How can you check which shared libraries a running process is using?
Read /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'.

Leave a Reply

Your email address will not be published. Required fields are marked *