rpath vs runpath

Heart Bleed
Obscure System
Published in
6 min readMay 26, 2019

Before we understand the difference between rpath and runpath, we need to understand where they are used. Both rpath and runpath are used to specify directories to search for shared libraries(dynamic libraries). If you are not sure what shared libraries is, I have a story written on Static vs Dynamic libraries.

Shared libraries are libraries which are not bundles along with the executable. They are loaded at the run time. How does the executable know where the libraries are present? Every executable follows a sequence of directories search to search for libraries.

Let’s understand this with a small experiment. We will build a shared library and try linking it with the executable.

$ gcc -c test_shared.c -o test_shared.o
$ gcc --shared test_shared.o -o libtest_shared.so

We can now use the library in sample executable.

$ gcc -o use_shared use_shared.c -L./ -ltest_shared
$ ./use_shared
./use_shared: error while loading shared libraries: libtest_shared.so: cannot open shared object file: No such file or directory

The executable cannot find the library. Let’s debug to to see the paths the library is searched for.

$ LD_DEBUG=libs ./use_shared 
10781: find library=libtest_shared.so [0]; searching
10781: search cache=/etc/ld.so.cache
10781: search path=/usr/lib/x86_64:/usr/lib (system search path)
10781: trying file=/usr/lib/x86_64/libtest_shared.so
10781: trying file=/usr/lib/libtest_shared.so
10781:
./use_shared: error while loading shared libraries: libtest_shared.so: cannot open shared object file: No such file or directory

Now let’s try to use one of the methodology LD_LIBRARY_PATH to specify the path of the library. LD_LIBRARY_PATH specifies the search path for the executable. Let’s see the the debug output

$ LD_DEBUG=libs LD_LIBRARY_PATH=./ ./use_shared 
10853: find library=libtest_shared.so [0]; searching
10853: search path=./x86_64:. (LD_LIBRARY_PATH)
10853: trying file=./x86_64/libtest_shared.so
10853: trying file=./libtest_shared.so
10853:
10853: find library=libc.so.6 [0]; searching
10853: search path=./x86_64:. (LD_LIBRARY_PATH)
10853: trying file=./x86_64/libc.so.6
10853: trying file=./libc.so.6
10853: search cache=/etc/ld.so.cache
10853: trying file=/lib/x86_64-linux-gnu/libc.so.6
10853:
10853:
10853: calling init: /lib/x86_64-linux-gnu/libc.so.6
10853:
10853:
10853: calling init: ./libtest_shared.so
10853:
10853:
10853: initialize program: ./use_shared
10853:
10853:
10853: transferring control: ./use_shared
10853:
Hello World!
10853:
10853: calling fini: ./use_shared [0]
10853:
10853:
10853: calling fini: ./libtest_shared.so [0]
10853:

Now you can see that “.” is added to the search path and we were able to find the library and the program executed successfully. Let’s delete the library and try the same.

$ rm libuse_shared.so
$ LD_DEBUG=libs LD_LIBRARY_PATH=./ ./use_shared
11330: find library=libtest_shared.so [0]; searching
11330: search path=./x86_64:. (LD_LIBRARY_PATH)
11330: trying file=./x86_64/libtest_shared.so
11330: trying file=./libtest_shared.so
11330: search cache=/etc/ld.so.cache
11330: search path=/usr/lib/x86_64:/usr/lib (system search path)
11330: trying file=/usr/lib/x86_64/libtest_shared.so
11330: trying file=/usr/lib/libtest_shared.so
11330:
./use_shared: error while loading shared libraries: libtest_shared.so: cannot open shared object file: No such file or directory

As we can see after it uses the LD_LIBRARY_PATH for search it uses the system path as it cannot find the library in the LD_LIBRARY_PATH paths.

Hope by now you should have a understanding of runtime paths. We used LD_LIBRARY_PATH to specify runtime path. Similarly rpath and runpath are used the specify the runtime paths to find libraries. The difference is the order in which they are searched. We will understand the order by experiments that follow.

rpath

We are using the same test programs as before.

$ gcc -c test_shared.c -o test_shared.o
$ gcc --shared test_shared.o -o libtest_shared.so
$ gcc -o use_shared use_shared.c -L./ -ltest_shared -Wl,-rpath,./
$ readelf -d use_shared | grep PATH
0x0000000f (RPATH) Library rpath: [./]

$ ./use_shared
Hello World!

Looking at the compile step above we include the rpath options to provide the current path. We use the “readelf” to check the path set. The output prints as expected.

Now let’s create a another similar library which displays another message, buts let’s create it another folder named overrided_library inside the current folder.

$ mkdir overrided_library
$ cd overrided_library
$ gcc -c test_shared.c -o test_shared.o
$ gcc --shared test_shared.o -o libtest_shared.so

Now we will be conducting the rest of the experiments in the parent folder.

$ cd ..

Lets execute the already created the use_shared executable but also providing the LD_LIBRARY_PATH to the library in the overrided_library path

$ LD_LIBRARY_PATH=./overrided_library/ ./use_shared
./Hello World!

Let’s see the debug output of the executable load steps

$ LD_DEBUG=libs LD_LIBRARY_PATH=./overriding_library/ ./use_shared 
9168: find library=libtest_shared.so [0]; searching
9168: search path=./tls/i686/sse2/cmov:./tls/i686/sse2:./tls/i686/cmov:./tls/i686:./tls/sse2/cmov:./tls/sse2:./tls/cmov:./tls:./i686/sse2/cmov:./i686/sse2:./i686/cmov:./i686:./sse2/cmov:./sse2:./cmov:. (RPATH from file ./use_shared)
9168: trying file=./tls/i686/sse2/cmov/libtest_shared.so
9168: trying file=./tls/i686/sse2/libtest_shared.so
...
9168: trying file=./sse2/libtest_shared.so
9168: trying file=./cmov/libtest_shared.so
9168: trying file=./libtest_shared.so
9168:
9168:
9168: calling init: /lib/i386-linux-gnu/libc.so.6
9168:
9168:
9168: calling init: ./libtest_shared.so
9168:
9168:
9168: initialize program: ./use_shared
9168:
9168:
9168: transferring control: ./use_shared
9168:
Hello World!
9168:
9168: calling fini: ./use_shared [0]
9168:
9168:
9168: calling fini: ./libtest_shared.so [0]
9168:

The library we search for was available in the folder provided in the rpath. So it does not bother to search in the path specified from the LD_LIBRARY_PATH.

Let’s continue the example by deleting the shared library in the current path(not the one in overrided_library folder)

$ rm libtest_shared.so
$ LD_LIBRARY_PATH=./overrided_library/ ./use_shared
Hello World from overrided library!

There we go, we have the message printed from the library in the overrided_library. Let’s see the debug message for the same

$ LD_DEBUG=libs LD_LIBRARY_PATH=./overrided_library/ ./use_shared 
11512: find library=libtest_shared.so [0]; searching
11512: search path=./tls/i686/sse2/cmov:./tls/i686/sse2:./tls/i686/cmov:./tls/i686:./tls/sse2/cmov:./tls/sse2:./tls/cmov:./tls:./i686/sse2/cmov:./i686/sse2:./i686/cmov:./i686:./sse2/cmov:./sse2:./cmov:. (RPATH from file ./use_shared)
11512: trying file=./tls/i686/sse2/cmov/libtest_shared.so
11512: trying file=./tls/i686/sse2/libtest_shared.so
...
11512: trying file=./cmov/libtest_shared.so
11512: trying file=./libtest_shared.so
11512: search path=./overrided_library/tls/i686/sse2/cmov:./overrided_library/tls/i686/sse2:./overrided_library/tls/i686/cmov:./overrided_library/tls/i686:./overrided_library/tls/sse2/cmov:./overrided_library/tls/sse2:./overrided_library/tls/cmov:./overrided_library/tls:./overrided_library/i686/sse2/cmov:./over/i686/sse2:./overrided_library/i686/cmov:./overrided_library/i686:./overrided_library/sse2/cmov:./overrided_library/sse2:./over/cmov:./overrided_library (LD_LIBRARY_PATH)
11512: trying file=./overrided_library/tls/i686/sse2/cmov/libtest_shared.so
11512: trying file=./overrided_library/tls/i686/sse2/libtest_shared.so
...
11512: trying file=./overrided_library/sse2/libtest_shared.so
11512: trying file=./overrided_library/cmov/libtest_shared.so
11512: trying file=./overrided_library/libtest_shared.so
11512:
11512: calling init: ./overrided_library/libtest_shared.so
11512:
11512:
11512: initialize program: ./use_shared
11512:
11512:
11512: transferring control: ./use_shared
11512:
Hello World from overrided library!
11512:
11512: calling fini: ./use_shared [0]
11512:
11512:
11512: calling fini:./overrided_library/libtest_shared.so [0]
11512:

Here we see that first rpath is searched and since the library was not available so next it searched in the LD_LIBRARY_PATH.

So rpath is first searched and next LD_LIBRARY_PATH.

runpath

For this experiment we are going to use the same programs and folder structure we had for the previous experiment.

Let’s compile the executable with runpath set rather than rpath. For this we need to use “enable-new-dtags” flag.

$ gcc -o use_shared use_shared.c -L./ -ltest_shared -Wl,-rpath,./
$ readelf -d use_shared | grep PATH
0x0000001d (RUNPATH) Library runpath: [./]
$ LD_LIBRARY_PATH=./overrided_library/ ./use_shared
Hello World from overrided library!

Here we see that the message from the library in the overrided_library folder is printed. The reason is runpath comes lower in precedence to the LD_LIBRARY_PATH. Let’s see this with the debug info

$ LD_DEBUG=libs LD_LIBRARY_PATH=./overrided_library/ ./use_shared 
25981: find library=libtest_shared.so [0]; searching
25981: search path=./overrided_library/tls/i686/sse2/cmov:./overrided_library/tls/i686/sse2:./overrided_library/tls/i686/cmov:./overrided_library/tls/i686:./overrided_library/tls/sse2/cmov:./overrided_library/tls/sse2:./overrided_library/tls/cmov:./overrided_library/tls:./overrided_library/i686/sse2/cmov:./overrided_library/i686/sse2:./overrided_library/i686/cmov:./overrided_library/i686:./overrided_library/sse2/cmov:./overrided_library/sse2:./overrided_library/cmov:./overrided_library (LD_LIBRARY_PATH)
25981: trying file=./overrided_library/tls/i686/sse2/cmov/libtest_shared.so
25981: trying file=./overrided_library/tls/i686/sse2/libtest_shared.so
...
25981: trying file=./overrided_library/sse2/libtest_shared.so
25981: trying file=./overrided_library/cmov/libtest_shared.so
25981: trying file=./overrided_library/libtest_shared.so
25981: calling init: ./overrided_library/libtest_shared.so
25981:
25981:
25981: initialize program: ./use_shared
25981:
25981:
25981: transferring control: ./use_shared
25981:
Hello World from overrided library!
25981:
25981: calling fini: ./use_shared [0]
25981:
25981:
25981: calling fini: ./overrided_library/libtest_shared.so [0]
25981:

We can see that the LD_LIBRARY_PATH is searched and the library is found and the runpath is not searched. Now let’s delete the library in the overrided_library folder.

$ rm overrided_library/libtest_shared.so 
$ LD_LIBRARY_PATH=./overrided_library/ ./use_shared
Hello World!

We can see from the above output that the library from the runpath was executed. The reason is it did not find the library in the LD_LIBRARY_PATH. Let’s prove this using the debug info.

$ LD_DEBUG=libs LD_LIBRARY_PATH=./overrided_library/ ./use_shared 
26712: find library=libtest_shared.so [0]; searching
26712: search path=./overrided_library/tls/i686/sse2/cmov:./overrided_library/tls/i686/sse2:./overrided_library/tls/i686/cmov:./overrided_library/tls/i686:./overrided_library/tls/sse2/cmov:./overrided_library/tls/sse2:./overrided_library/tls/cmov:./overrided_library/tls:./overrided_library/i686/sse2/cmov:./overrided_library/i686/sse2:./overrided_library/i686/cmov:./overrided_library/i686:./overrided_library/sse2/cmov:./overrided_library/sse2:./overrided_library/cmov:./overrided_library (LD_LIBRARY_PATH)
26712: trying file=./overrided_library/tls/i686/sse2/cmov/libtest_shared.so
26712: trying file=./overrided_library/tls/i686/sse2/libtest_shared.so
...
26712: trying file=./overrided_library/sse2/libtest_shared.so
26712: trying file=./overrided_library/cmov/libtest_shared.so
26712: trying file=./overrided_library/libtest_shared.so
26712: search path=./tls/i686/sse2/cmov:./tls/i686/sse2:./tls/i686/cmov:./tls/i686:./tls/sse2/cmov:./tls/sse2:./tls/cmov:./tls:./i686/sse2/cmov:./i686/sse2:./i686/cmov:./i686:./sse2/cmov:./sse2:./cmov:. (RUNPATH from file ./use_shared)
26712: trying file=./tls/i686/sse2/cmov/libtest_shared.so
26712: trying file=./tls/i686/sse2/libtest_shared.so
...
26712: trying file=./cmov/libtest_shared.so
26712: trying file=./libtest_shared.so
26712:
26712: calling init: /lib/i386-linux-gnu/libc.so.6
26712:
26712:
26712: calling init: ./libtest_shared.so
26712:
26712:
26712: initialize program: ./use_shared
26712:
26712:
26712: transferring control: ./use_shared
26712:
Hello World!
26712:
26712: calling fini: ./use_shared [0]
26712:
26712:
26712: calling fini: ./libtest_shared.so [0]
26712:

From this we can udestand that runpath has lower precedence than LD_LIBRARY_PATH. The order of precedence for search paths are

  1. rpath
  2. LD_LIBRARY_PATH
  3. runpath

But why do we need two flags here rpath and runpath. Cant we live with one.

Conclusion

Earlier rpath was the only flag that existed. The problem arised that once rpath is set(which is done at the build time) we cannot override it during execution since LD_LIBRARY_PATH has lower precedence. We need to rebuild the executable everytime we need to test it with a different library which was quite annoying. That is when runpath was introduced where we could override the libraries with LD_LIBRARY_PATH which makes it easy to test accross different libraries without rebuilding it everytime.

--

--