View on GitHub

Eternal Journeyman

Tutorial: LD_LIBRARY_PATH, soname, and rpath

(This little tutorial is written for Linux, but the Mac OS X version is basically the same. Use otool -L instead of ldd, and replace -soname with -install_name. Also, the keyword for $ORIGIN is not the same.)

Before we start talking about shared objects, let's make one:

$ mkdir /tmp/example
$ cd /tmp/example
$ cat > foo.c
#include <stdio.h>
void greet() { printf("Hello from foo.c\n"); }
$ cat > foo.h
void greet();
$ gcc -shared -fPIC -o foo.so foo.c
$ ls
foo.c   foo.h   foo.so

Great. Now let's try to use it:

$ cat > main.c
#include "foo.h"
int main() { greet(); } 
$ gcc -o main main.c foo.so
$ ls
foo.c   foo.h   foo.so main   main.c

Looks good so far... Does it work?

$ ./main
Hello from foo.c

Sure does. We can use ldd to see that our executable does, in fact, depend on foo.so:

$ ldd main
    linux-vdso.so.1 =>  (0x00007fff06193000)
    foo.so (0x00007fee81382000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fee80fbc000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fee81586000)

Is our executable ready for general use? For example, what happens if we switch directories and try to run it again?

$ cd /
$ /tmp/example/main 
/tmp/example/main: error while loading shared libraries: foo.so: cannot open shared object file: No such file or directory

Ouch. What went wrong? Before we switch directories again, let's see what ldd can tell us:

$ ldd /tmp/example/main
    linux-vdso.so.1 =>  (0x00007fffc17ff000)
    foo.so => not found
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007faaaaba0000)
    /lib64/ld-linux-x86-64.so.2 (0x00007faaaaf67000)

Hmm... the loader knows that our executable depends on something called foo.so, but it doesn't know where to find it. How can we help it?

Option 1: LD_LIBRARY_PATH

There's a special environment variable called LD_LIBRARY_PATH that can be used to tell the loader where to look for .so files. Let's give it a try:

$ export LD_LIBRARY_PATH=/tmp/example
$ /tmp/example/main
Hello from foo.c

Okay, that worked, but it may not be convenient to require our users to have a special environment setup.

Option 2: Hard-code the path in the foo.so soname.

Let's back up a bit. When we built foo.so, we didn't give any thought to the loader. Let's rebuild it with a custom soname setting:

$ gcc -shared -fPIC -o foo.so -Wl,-soname,/tmp/example/foo.so foo.c

After rebuilding our main executable, what does ldd say?

$ gcc -o main main.c foo.so
$ ldd main
    linux-vdso.so.1 =>  (0x00007fff8d987000)
    /tmp/example/foo.so (0x00007f6b85c97000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f6b858d2000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f6b85e9b000)

Now the path to foo.so is hard-coded into our executable. We should't need LD_LIBRARY_PATH any more, right?

$ cd /
$ export LD_LIBRARY_PATH=
$ /tmp/example/main
Hello from foo.c

Looks good! But wait, this means that we can't ever move foo.so out of /tmp/example. In fact, if foo.so is moved, our main executable won't run, no matter what changes we make to LD_LIBRARY_PATH. If you can count on your shared object being installed to a standard location, then maybe that's okay. If not, do you have any other options?

Option 3: Hard-code the library search directory in main's rpath

This time, we'll leave foo.so's soname alone. Instead, we'll tell main where to look for its dependencies:

$ gcc -o main main.c foo.so
$ gcc -o main -Wl,-rpath,/tmp/example main.c foo.so

We can verify that our executable has our custom rpath using the chrpath tool:

$ chrpath main
main: RPATH=/tmp/example

So, can we execute our program even if we switch to some other directory?

$ cd /
$ /tmp/example/main
Hello from foo.c

Nice. But... This solution seems just as fragile as the soname change above (Option 2). Are there any advantages to using rpath to locate our shared object? Yes! If we plan to distribute our main executable alongside our shared object as part of a larger package, we can use a relative path. The special variable $ORIGIN is recognized inside the rpath to enable this feature:

$ gcc -o main -Wl,-rpath,\$ORIGIN/. main.c foo.so
$ chrpath main
main: RPATH=$ORIGIN/.

Note that there are quoting issues to worry about here. I used a \ to prevent the shell from misinterpreting $ORIGIN as an environment variable.

More than one search path can be listed in an rpath. One way to include multiple paths is to simply pass the -rpath setting to the linker more than once:

$ gcc -o main -Wl,-rpath,\$ORIGIN/. -Wl,-rpath,\$ORIGIN/../../lib main.c foo.so
$ chrpath main
main: RPATH=$ORIGIN/.:$ORIGIN/../../lib

This way, if you have a whole directory tree with shared objects and executable programs in subdirectories, you can use relative rpaths to help locate the shared objects. Furthermore, if you copy the whole directory tree and give it to somebody else, it should work on their system, too, no matter where they store it on their hard drive.

One last note: the chrpath utility can also be used to change the rpath in an executable or shared object. That's handy if you've already built the binary and you just want to update the rpath before shipping it. (See the man page for details.)