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 rpath
s 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.)