Shared Library Search Paths

It's becoming more and more common these days to link everything against shared libraries, and in fact many software packages (Tcl and Cyrus SASL come to mind) basically just don't work properly static. This means that one has to more frequently deal with the issues involved in finding the appropriate libraries at runtime.

Here's a brief primer on the way that this works on Solaris and Linux. The search paths for libraries come from three sources: the environment variable LD_LIBRARY_PATH (if set), any rpath encoded in the binary (more on this later), and the system default search paths. They're searched in this order, and the first matching library found is used.

LD_LIBRARY_PATH is broken and should not be used if at all possible. It's broken because it overrides the search paths for all binaries that you run using it, not just the one that you care about, and it doesn't add easily to other competing settings of LD_LIBRARY_PATH. It has a tendency to cause odd breakage, and it's best to only use it with commercial applications like Oracle where there's no other choice (and then to set it only in a wrapper around a particular application, and never in your general shell environment).

Now, more about the other two mechanisms in detail.

System default paths

Far and away the best way of handling shared libraries is to add every directory into which you install shared libraries to the system default paths. This doesn't work if you install a variety of conflicting libraries, but that's a rare case. If you're just installing software into /usr/local/lib, for example, then just add /usr/local/lib to your system default search paths.

On Linux, you do this by adding those directories to /etc/ld.so.conf and then running ldconfig. On Solaris, you do this by using the crle command (see the man page for more details).

This doesn't always work, though. The main case where this doesn't work is when you're installing shared libraries into a shared network file system for use throughout your cluster or enterprise. Then, you probably don't want to add that network file system to the default system search path, since that search path is used for every binary on the system, including ones integral to the operation of the system. If the network file system goes down, and the default search path includes it, the system will become unusable.

That leads to the next approach.

Encoding rpath in applications

ELF binaries (used by Solaris and Linux) can contain in the binary supplemental paths for shared libraries. (Shared libraries can also contain their own supplemental paths for finding other shared libraries, but that's not as commonly used.) This path is searched before the system default paths (but is overridden by LD_LIBRARY_PATH, if set).

This path must be encoded at compile time and after that doesn't change. The advantage of this approach over the LD_LIBRARY_PATH approach, apart from the other problems caused by LD_LIBRARY_PATH, is that the work is born by the person building the software rather than the person running it. That means the work of configuring library paths only has to be done once, by a fairly clued person, rather than by every user who may or may not understand the issues.

There are two basic ways of telling a compiler to encode a search path in the binary (called an rpath):

Either can be used, so normally I use LD_RUN_PATH since it's less intrusive. However, note that if there are any -R or -Wl,-rpath flags on the command line, the LD_RUN_PATH setting will typically be ignored. This means that, if the normal build process of the software adds -R flags, you'll need to get it to add your -R flags as well (unless it helpfully includes a -R flag for the installation directory, which it sometimes does). If the software package uses Autoconf, generally the easiest way to do this is to set LDFLAGS to "-R /usr/local/lib" (or whatever directory) before running configure, which will then stick it into the Makefile. Failing that, I'll sometimes do something like:

    make CC="gcc -R /usr/local/lib"

to build the software, which generally does the right thing. (The same applies to -Wl,-rpath flags if you need to use it instead.

Note that software that uses libtool to link its libraries and to link against libraries will more frequently do the right thing and encode rpath properly when it builds the final binaries, but not always. Thankfully libtool also understands the -R and -Wl,-rpath flags and does the right thing when it's provided.

Checking the binaries

To verify that a binary is doing the right thing, use the command ldd on the binary. This will list all of the libraries and either where they came from (if found) or something like "(not found)" if the library wasn't found. On Mac OS X, use otool -L.

Solaris has the very useful -s option to ldd that will additionally show the full library search path, so you can confirm that you encoded the right rpath in the binary.

A partial equivalent under Linux is:

    readelf -d <binary> | grep RPATH

but note that this only shows the rpath for a particular binary (executable or library). If a shared library depends on other shared libraries, those shared libraries may be searched for using the rpath of the shared library that is loading them. To make sure that the search path is correct on Linux, you may need to use the above readelf command on the binary and all libraries other than system libraries that it uses. readelf comes with binutils, so if you're compiling software you will probably already have it installed.

Changing the rpath

On Linux, a utility called patchelf is available that can modify or remove the rpath or add one if one was not already present. This utility replaces the older chrpath utility, which appears to no longer be maintained.

patchelf won't compile on Solaris out of the box. For it, you may still need chrpath. The maintainer's FTP site used to be at ftp://ftp.hungry.com/pub/hungry/chrpath/ but has been unreachable for some time. At this point, the best source is probably the Debian package.

Mac OS X comes with a utility named install_name_tool that can make similar modifications to the rpath encoded in a binary. However, it can't change the rpath to one that's longer than the original unless the binary was built with the linker flag -headerpad_max_install_names.

Other platforms

I'm afraid I don't have as much helpful information about Tru64, HP-UX, AIX, or IRIX. HP-UX and AIX don't use ELF, and therefore have a completely different linking mechanism. Tru64 and IRIX seem to often encode library locations at build time based on where the library was found and therefore don't need either of these mechanisms, but I don't know exactly how that works. HP-UX and AIX may do something similar.

Note that another option is to just build your software against the static libraries. This is what we used to do at Stanford on all platforms other than Solaris and Linux (and the BSDs, although I don't normally build on BSD systems), since the handling of shared library versioning and dependencies is considerably less robust on other systems.

Last spun 2022-02-06 from thread modified 2020-04-11