The rpath functionality is something that exists only on unix-like OS. It’s
very powerful in order to manage the dynamic loading as you want. The goal here
is not to explain rpath; just use your favorite search engine for that. Here
I will told you how to use rpath in order to provide binaries and libraries
without a dependency on the system paths like /usr/lib and without the
use of LD_LIBRARY_PATH or DYLD_LIBRARY_PATH which can be very dangerous and
even a bit limited on Darwin (see SIP).
It’s simple but it’s a bit more limited that on Darwin. In most of cases you
have your libraries in lib and the main executable in bin. It means that the
main executable must look for your libraries in ../lib.
In order to add a relative path, you must link with -rpath. This attribute
must provides one or more paths where the dynamic libraries must be searched.
For relative path, it’s possible to use a placeholder named $ORIGIN which is
the location of the main executable. Otherwise, you must pass an absolute
location.
You can pass this attribute with the CFLAGS=-Wl for the linker. For example:
CFLAGS='-Wl,-rpath=$ORIGIN/../lib'
Note that the use of
[$]can be a bit problematic because it can be expanded by the shell or by make. It must be escaped by\$ORIGINwith the shell and$$ORIGINwith make.
You can have more than one path just by doing like this:
-Wl,-rpath=$ORIGIN/../lib:/opt/my/lib.
See this example with the build of curl (I use readelf in order to see the
sections in the executable):
$ readelf -d ./usr/bin/curl
Dynamic section at offset 0x28dd0 contains 29 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libcurl.so.4]
0x0000000000000001 (NEEDED) Shared library: [libssl.so.1.1]
0x0000000000000001 (NEEDED) Shared library: [libcrypto.so.1.1]
0x0000000000000001 (NEEDED) Shared library: [libz.so.1]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000001d (RUNPATH) Library runpath: [$ORIGIN/../lib]
[...]]
An rpath value was added by the linker in the RUNPATH section. The same
must be done for the libraries, for example with libcurl.so.4:
$ readelf -d ./usr/lib/libcurl.so.4
Dynamic section at offset 0x68d20 contains 33 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libidn.so.11]
0x0000000000000001 (NEEDED) Shared library: [librtmp.so.1]
0x0000000000000001 (NEEDED) Shared library: [libssl.so.1.1]
0x0000000000000001 (NEEDED) Shared library: [libcrypto.so.1.1]
0x0000000000000001 (NEEDED) Shared library: [liblber-2.4.so.2]
0x0000000000000001 (NEEDED) Shared library: [libldap_r-2.4.so.2]
0x0000000000000001 (NEEDED) Shared library: [libz.so.1]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000000e (SONAME) Library soname: [libcurl.so.4]
0x000000000000001d (RUNPATH) Library runpath: [$ORIGIN/../lib]
[...]]
You can check that the loader is using the right paths with ldd:
$ ldd ./usr/bin/curl
linux-vdso.so.1 (0x00007ffccb787000)
libcurl.so.4 => /home/schroeterm/usr/bin/../lib/libcurl.so.4 (0x00007fb817e5f000)
libssl.so.1.1 => /home/schroeterm/usr/bin/../lib/libssl.so.1.1 (0x00007fb817bea000)
libcrypto.so.1.1 => /home/schroeterm/usr/bin/../lib/libcrypto.so.1.1 (0x00007fb81774e000)
libz.so.1 => /home/schroeterm/usr/bin/../lib/libz.so.1 (0x00007fb817531000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb817148000)
libidn.so.11 => /lib/x86_64-linux-gnu/libidn.so.11 (0x00007fb816f14000)
librtmp.so.1 => /usr/lib/x86_64-linux-gnu/librtmp.so.1 (0x00007fb816cf7000)
liblber-2.4.so.2 => /usr/lib/x86_64-linux-gnu/liblber-2.4.so.2 (0x00007fb816ae8000)
libldap_r-2.4.so.2 => /usr/lib/x86_64-linux-gnu/libldap_r-2.4.so.2 (0x00007fb816897000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fb816691000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fb816474000)
/lib64/ld-linux-x86-64.so.2 (0x000055a978e49000)
libgnutls.so.30 => /usr/lib/x86_64-linux-gnu/libgnutls.so.30 (0x00007fb8160dc000)
libhogweed.so.4 => /usr/lib/x86_64-linux-gnu/libhogweed.so.4 (0x00007fb815ea7000)
libnettle.so.6 => /usr/lib/x86_64-linux-gnu/libnettle.so.6 (0x00007fb815c70000)
libgmp.so.10 => /usr/lib/x86_64-linux-gnu/libgmp.so.10 (0x00007fb8159ed000)
libresolv.so.2 => /lib/x86_64-linux-gnu/libresolv.so.2 (0x00007fb8157d4000)
libsasl2.so.2 => /usr/lib/x86_64-linux-gnu/libsasl2.so.2 (0x00007fb8155b9000)
libp11-kit.so.0 => /usr/lib/x86_64-linux-gnu/libp11-kit.so.0 (0x00007fb815354000)
libtasn1.so.6 => /usr/lib/x86_64-linux-gnu/libtasn1.so.6 (0x00007fb815141000)
libffi.so.6 => /usr/lib/x86_64-linux-gnu/libffi.so.6 (0x00007fb814f38000)
You can see with ldd that a lot of libraries are still pointing on the system
paths. The reason is that I’ve just rebuilt libcurl, libssl, libcrypto and
libz. I’m sure at least for these libraries that it’s using my own versions
and not the versions provided by my system. I can move the bin and lib
directory together without breaking something because I’m using a relative
rpath.
If you are using LD_LIBRARY_PATH, you should consider to rebuild everything
properly with rpath because you can break some libraries otherwise. If I try
ldd on /usr/lib/x86_64-linux-gnu/libgnutls.so.30 (which is a dependency of
my curl, you will see libz too; but it’s not the same):
$ ldd /usr/lib/x86_64-linux-gnu/libgnutls.so.30
linux-vdso.so.1 (0x00007ffc915ec000)
libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f702f95d000)
libp11-kit.so.0 => /usr/lib/x86_64-linux-gnu/libp11-kit.so.0 (0x00007f702f6f8000)
libidn.so.11 => /lib/x86_64-linux-gnu/libidn.so.11 (0x00007f702f4c4000)
libtasn1.so.6 => /usr/lib/x86_64-linux-gnu/libtasn1.so.6 (0x00007f702f2b1000)
libnettle.so.6 => /usr/lib/x86_64-linux-gnu/libnettle.so.6 (0x00007f702f07a000)
libhogweed.so.4 => /usr/lib/x86_64-linux-gnu/libhogweed.so.4 (0x00007f702ee43000)
libgmp.so.10 => /usr/lib/x86_64-linux-gnu/libgmp.so.10 (0x00007f702ebc0000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f702e822000)
libffi.so.6 => /usr/lib/x86_64-linux-gnu/libffi.so.6 (0x00007f702e619000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f702e415000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f702e1f8000)
/lib64/ld-linux-x86-64.so.2 (0x000055e562126000)
It means that ./usr/bin/curl depends of
libz.so.1 => /home/schroeterm/usr/bin/../lib/libz.so.1 (0x00007fb817531000)
and libgnutls.so.30 => /usr/lib/x86_64-linux-gnu/libgnutls.so.30. But
libgnutls depends of
libz.so.1 => /lib/x86_64-linux-gnu/libz.so.1 (0x00007f702f95d000).
You can see two libz files. One in my own tree, and a second one in the system
tree. With rpath, everything is fine because each library uses the right
dependencies.
But if I use LD_LIBRARY_PATH, I break the dependencies:
LD_LIBRARY_PATH=/home/schroeterm/usr/lib ldd /usr/lib/x86_64-linux-gnu/libgnutls.so.30
linux-vdso.so.1 (0x00007ffec8df0000)
libz.so.1 => /home/schroeterm/usr/lib/libz.so.1 (0x00007fb899560000)
libp11-kit.so.0 => /usr/lib/x86_64-linux-gnu/libp11-kit.so.0 (0x00007fb8992b2000)
libidn.so.11 => /lib/x86_64-linux-gnu/libidn.so.11 (0x00007fb89907e000)
libtasn1.so.6 => /usr/lib/x86_64-linux-gnu/libtasn1.so.6 (0x00007fb898e6b000)
libnettle.so.6 => /usr/lib/x86_64-linux-gnu/libnettle.so.6 (0x00007fb898c34000)
libhogweed.so.4 => /usr/lib/x86_64-linux-gnu/libhogweed.so.4 (0x00007fb8989fd000)
libgmp.so.10 => /usr/lib/x86_64-linux-gnu/libgmp.so.10 (0x00007fb89877a000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb8983dc000)
libffi.so.6 => /usr/lib/x86_64-linux-gnu/libffi.so.6 (0x00007fb8981d3000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fb897fcf000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fb897db2000)
/lib64/ld-linux-x86-64.so.2 (0x000055684a461000)
In this case, libgnutls is using my own libz version and it’s not what I
want. Most of time it works fine, but sometimes it breaks your runtime. Then I
repeat, you should never use LD_LIBRARY_PATH (or similar).
Maybe you can not rebuild the libraries, or maybe it’s too difficult to pass
$ORIGIN by command line because it’s expanded by the shell and/or by make.
It’s possible to patch the binaries after the linking by using
patchelf.
Do not use
chrpath, it’s too limited because when the new rpath is longer, it’s not possible to change the value.
To set the rpath with patchelf, it’s very simple:
patchelf --set-rpath '$ORIGIN/../lib' ./usr/bin/curl
In a future article…