Skip to content

102.3 Manage shared libraries

Linking

When we write a program, we use libraries. Linking has two forms:

  • Static linking is when the library code is copied directly into the executable at compile time. The program is larger, but has no external dependencies - it can run on its own without needing any libraries installed separately. Static library files end in .a (e.g. libpthread.a).

  • Dynamic linking is when the program just records which libraries it needs. The actual library code is loaded at runtime. Programs are smaller, and libraries can be updated centrally - one security fix in a shared library improves every program that uses it, without recompiling any of them. Dynamic library files end in .so (e.g. libpthread.so.0).

Dynamic linking is also called shared libraries because all programs share the same installed copy of the library.

Static linking:
  [your code] + [library code] --> one big executable (no dependencies)

Dynamic linking:
  [your code] + [reference: "I need libX.so"] --> small executable
       |
       v  (at runtime)
  System finds and loads libX.so from /lib64/ or /usr/lib/

Linux shared library names follow a pattern: libNAME.so.VERSION (e.g. libpthread.so.0). On Windows, the equivalent is a DLL (Dynamic Linked Library).


What libraries do I need

Libraries related to system utilities are installed in /lib and /lib64 (for 32-bit and 64-bit libraries). Libraries installed by other software go in /usr/lib and /usr/lib64. The PDF also lists /lib32 and /usr/local/lib as common locations.

ldd

The ldd command shows which shared libraries a program depends on, or tells you if it's statically linked:

$ ldd /sbin/ldconfig
    not a dynamic executable

$ ldd /bin/ls
    linux-vdso.so.1 (0x00007ffdd53eb000)
    libselinux.so.1 => /lib64/libselinux.so.1 (0x00007f5cbc7b0000)
    libcap.so.2 => /lib64/libcap.so.2 (0x00007f5cbc7a6000)
    libc.so.6 => /lib64/libc.so.6 (0x00007f5cbc5a5000)
    libpcre2-8.so.0 => /lib64/libpcre2-8.so.0 (0x00007f5cbc50f000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f5cbc813000)

ldconfig is statically linked (it has to be - it's the tool that sets up dynamic linking, so it can't depend on dynamic linking itself). ls depends on six shared libraries.

The -u option shows unused direct dependencies - libraries that were linked but aren't actually needed:

$ ldd -u /usr/bin/git
Unused direct dependencies:
  /lib/x86_64-linux-gnu/libz.so.1
  /lib/x86_64-linux-gnu/libpthread.so.0

A program asks for libudev.so.1. Your system has libudev.so.1.4.0. How does this work? Symbolic links:

$ ls -la /lib/i386-linux-gnu/libudev.so.1
lrwxrwxrwx 1 root root 16 ... libudev.so.1 -> libudev.so.1.4.0

The generic name (libudev.so.1) is a symlink pointing to the actual versioned file (libudev.so.1.4.0). Programs link against the generic name, and the symlink silently redirects to whatever version is actually installed. When the library is upgraded to 1.5.0, only the symlink needs to change - every program using it automatically gets the new version.

Dynamic library configs and cache

The configuration file for shared library paths is /etc/ld.so.conf. On most systems it just includes everything from a subdirectory:

$ cat /etc/ld.so.conf
include ld.so.conf.d/*.conf

$ cat /etc/ld.so.conf.d/llvm13-x86_64.conf
/usr/lib64/llvm13/lib

The ldconfig command reads these config files, creates the necessary symlinks, and builds a cache file (/etc/ld.so.cache) so programs can find libraries quickly without scanning directories every time.

Edit /etc/ld.so.conf or add a .conf file to /etc/ld.so.conf.d/
    |
    v
Run: sudo ldconfig          (rebuilds the cache)
    |
    v
/etc/ld.so.cache updated    (programs can now find the new library paths)

Useful ldconfig options:

Option What it does
ldconfig -v Rebuild cache and show every library and symlink it processes. Useful to verify your changes took effect
ldconfig -p Print what's currently in the cache without rebuilding. Quick way to check if a specific library is available
$ ldconfig -p | head
1373 libs found in cache `/etc/ld.so.cache'
    libzstd.so.1 (libc6,x86-64) => /lib64/libzstd.so.1
    libz.so.1 (libc6,x86-64) => /lib64/libz.so.1

Where OS finds dynamic libraries

When a program needs a shared library, the system searches in this order:

  1. LD_LIBRARY_PATH environment variable (checked first - overrides everything)
  2. The program's own PATH
  3. /etc/ld.so.conf (which may include files from /etc/ld.so.conf.d/)
  4. /lib/, /lib64/, /usr/lib/, /usr/lib64/

LD_LIBRARY_PATH is a colon-separated list of directories, searched before the system cache. It's the library equivalent of PATH for executables. Use cases:

  • Running old software that needs an old version of a library
  • Testing a library you're developing without installing it system-wide
  • Running software from /opt that ships its own libraries
export LD_LIBRARY_PATH=/usr/lib/myoldlibs:/home/jadi/lpic/libs/

After this, any command you run will search those two directories first, before falling back to the system libraries.

To remove it:

unset LD_LIBRARY_PATH

To make it permanent, add the export line to /etc/bash.bashrc (system-wide) or ~/.bashrc (your user only).


Loading dynamically

The dynamic linker (ld-linux) is the program that actually loads shared libraries at runtime. You can find it with:

$ locate ld-linux
/usr/lib64/ld-linux-x86-64.so.2

You can use it to run any program directly:

$ /usr/lib64/ld-linux-x86-64.so.2 /usr/bin/ls
Desktop  Documents  Downloads  Music  Pictures

Why you don't normally need to do this: every Linux ELF executable has a header that already says "run me using ld-linux":

$ readelf -Wl /usr/bin/ls
  ...
  INTERP  ...
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]

The kernel reads that header automatically and invokes the dynamic linker for you.

One practical trick: you can run any Linux executable using ld-linux even if its executable permission bit is not set. The file doesn't need chmod +x if you invoke the dynamic linker directly.


Summary

I have a Linux system where programs share libraries instead of each one carrying its own copy. A shared library is a file named something like libpthread.so.0, stored in /lib/, /lib64/, /usr/lib/, or /usr/lib64/. Programs reference the generic name, and a symlink redirects to whatever specific version is installed.

I can check what libraries a program needs with ldd /path/to/program. If it says "not a dynamic executable," the program is statically linked and carries everything it needs inside itself. The library search paths are configured in /etc/ld.so.conf (and the .conf files inside /etc/ld.so.conf.d/), and after changing them I must run ldconfig to rebuild the cache at /etc/ld.so.cache.

If I need to temporarily override the system library paths, I set the LD_LIBRARY_PATH environment variable, which is searched before anything else. This is useful for testing or running software that ships its own version of a library. The whole system only has four essential items to remember: ldd to check dependencies, ldconfig to rebuild the cache, /etc/ld.so.conf for configuration, and LD_LIBRARY_PATH for temporary overrides.