The loading of shared libraries in Linux is somewhat complicated (though not any more than other UNIX systems). Firstly, Linux has gone through tremendous growth; as a result, the method and format of loadable libraries has changed several times, necessitating a change in the library loader:

  • ld.so – for a.out formatted binaries
  • ld-linux.so.1 – for ELF binaries that used libc5
  • ld-linux.so.2 – for ELF binaries that use glibc

Nearly everything today will use ld-linux.so.2. All three use the same support files, configuration files, and so forth. The loaders ld-linux.so.1 and ld.so will not be discussed further.

The search for shared libraries goes in this order:

  • Use the binary’s internally stored RPATH (deprecated).
  • Use the environment variable LD_LIBRARY_PATH (except for suid and sgid binaries).
  • Use the binary’s internally stored RUNPATH.
  • Use the cache file /etc/ld.so.cache.
  • Search /lib then /usr/lib.

If the binary being loaded was linked with the -z nodeflib (that is, no default libraries) option then the last two steps are ignored.

Conveniently, the loader is an actual program which can be executed and with a variety of options. There are only two that will be of assistance when debugging the loading of system libraries. To use a specified path instead of LD_LIBRARY_PATH, use the loader this way:

/lib/ld-linux.so.2 --library-path path binary

…where path is the library path, and binary is the program binary to be loaded and executed. To keep the program binary’s preconfigured RPATH and RUNPATH from being used, use this invocation:

/lib/ld-linux.so.2 --inhibit-rpath binary

The loader also recognizes a lot of different environment variables; only a few are relevant for most purposes:

  • LD_LIBRARY_PATH: a path of directories to search for ELF libraries. Formatted like PATH.
  • LD_PRELOAD: a list of libraries (separated by whitespace) to be loaded before all others. For suid and sgid binaries, only libraries in standard search directories that are also suid will be loaded.
  • LD_BIND_NOW: bind (and load) all necessary libraries at startup instead of on demand. This is useful when using a debugger.
  • LD_TRACE_LOADED_OBJECTS: produces output like ldd(8).
  • LD_DEBUG: output debugging information related to the linker. If set to help will provide help for using this variable. If set to all, will provide full and detailed debugging of all portions of the dynamic loading process and of the dynamic loader.

The variable LD_DEBUG can be quite useful when debugging a missing library. The help for LD_DEBUG looks like this:

# LD_DEBUG=help nm --help
Valid options for the LD_DEBUG environment variable are:
.
libs display library search paths
reloc display relocation processing
files display progress for input file
symbols display symbol table processing
bindings display information about symbol binding
versions display version dependencies
all all previous options combined
statistics display relocation statistics
unused determined unused DSOs
help display this help message and exit
.
To direct the debugging output into a file instead of standard output
a filename can be specified using the LD_DEBUG_OUTPUT environment variable.

To debug a program’s shared libraries, try invoking the program with this environment variable set. As the program executes, each library search and library invocation will be reported. For example:

# LD_DEBUG=libs,versions ls -m
28927: find library=librt.so.1 [0]; searching
28927: search cache=/etc/ld.so.cache
28927: trying file=/lib/librt.so.1
28927:
28927: find library=libselinux.so.1 [0]; searching
28927: search cache=/etc/ld.so.cache
28927: trying file=/lib/libselinux.so.1
28927:
28927: find library=libacl.so.1 [0]; searching
28927: search cache=/etc/ld.so.cache
28927: trying file=/lib/libacl.so.1
28927:
28927: find library=libc.so.6 [0]; searching
28927: search cache=/etc/ld.so.cache
28927: trying file=/lib/libc.so.6
28927:
28927: find library=libpthread.so.0 [0]; searching
28927: search cache=/etc/ld.so.cache
28927: trying file=/lib/libpthread.so.0
28927:
28927: find library=libdl.so.2 [0]; searching
28927: search cache=/etc/ld.so.cache
28927: trying file=/lib/libdl.so.2
28927:
28927: find library=libsepol.so.1 [0]; searching
28927: search cache=/etc/ld.so.cache
28927: trying file=/lib/libsepol.so.1
28927:
28927: find library=libattr.so.1 [0]; searching
28927: search cache=/etc/ld.so.cache
28927: trying file=/lib/libattr.so.1
28927:
28927: checking for version `GLIBC_2.2' in file /lib/librt.so.1 [0] required by file ls [0]
28927: checking for version `ACL_1.0' in file /lib/libacl.so.1 [0] required by file ls [0]
28927: checking for version `GLIBC_2.2.3' in file /lib/libc.so.6 [0] required by file ls [0]
28927: checking for version `GLIBC_2.4' in file /lib/libc.so.6 [0] required by file ls [0]
28927: checking for version `GLIBC_2.3' in file /lib/libc.so.6 [0] required by file ls [0]
28927: checking for version `GLIBC_2.3.4' in file /lib/libc.so.6 [0] required by file ls [0]
28927: checking for version `GLIBC_2.1' in file /lib/libc.so.6 [0] required by file ls [0]
28927: checking for version `GLIBC_2.1.3' in file /lib/libc.so.6 [0] required by file ls [0]
28927: checking for version `GLIBC_2.2' in file /lib/libc.so.6 [0] required by file ls [0]
28927: checking for version `GLIBC_2.0' in file /lib/libc.so.6 [0] required by file ls [0]

[…Snip…]

The output can be quite verbose, but you can get the idea. This output can be most useful when a library is not being loaded by the loader (when you think it should be).

Warning: be aware, too, that the C compiler (gcc) as of version 4.0 requires a shared library for every program by default. If you are trying to create a static program, it will not truly be static until the gcc library is actually built into the binary. This requires a special gcc option for linking in addition to the standard -static option: -static-libgcc. The -static option alone is not enough. This library can be seen by using the -print-libgcc-file-name option:

# gcc -print-libgcc-file-name
/usr/lib/gcc/i386-redhat-linux/4.1.2/libgcc.a

Note, too, that any library required by a program has its own requirements: thus, if the library requires another library, and cannot find a static build version, then that library will be used as a shared library.

The program ldd can be used (as always) to find the libraries that a program requires:

# ldd $(which ls)
linux-gate.so.1 => (0x00ef8000)
librt.so.1 => /lib/librt.so.1 (0x0585b000)
libselinux.so.1 => /lib/libselinux.so.1 (0x004b2000)
libacl.so.1 => /lib/libacl.so.1 (0x05c0c000)
libc.so.6 => /lib/libc.so.6 (0x00101000)
libpthread.so.0 => /lib/libpthread.so.0 (0x00282000)
/lib/ld-linux.so.2 (0x00dc2000)
libdl.so.2 => /lib/libdl.so.2 (0x00de1000)
libsepol.so.1 => /lib/libsepol.so.1 (0x0046a000)
libattr.so.1 => /lib/libattr.so.1 (0x05c05000)

The ldd program will also report if a library is missing.

Update: I forgot to mention these resources for further information about using (and creating) shared libraries in Linux:

In particular, I found Russ Allbery’s description most illuminating.

Advertisements