DT_RPATH (ld) & @rpath (dyld)
Mac and Linux have two similarly named concepts that both deal with
dynamic loading, that behave quite differently: @rpath
(under Mac OS
X’s dyld) and DT_RPATH
(or just rpath, under Linux' ld.)
Having done development (and more importantly, deployment) on both of these platforms, I’ve experienced first-hand how those concepts can get a little jumbled in your mind, so here’s a brief overview.
DT_RPATH
DT_RPATH, or more commonly just rpath, is a property set on an ELF
file1. It points to a list of directories that the dynamic linker
will consider when loading a shared library. DT_RPATH is set at
link-time with the -rpath
option to ld
. If you invoke ld
through
gcc
(or another compiler, like g++
), then you can use the -Wl
option to pass arguments through to ld
. You use commas to separate
arguments passed to -Wl
.
1 2 3 4 5 6 |
|
The snippet above also shows one of the three special variables2 you
can include in an rpath, $ORIGIN. $ORIGIN gets replaced at runtime with
the directory in which our executable lives. DT_RPATH is transitive,
meaning it applies to any dependencies of our dependencies (unlike
DT_RUNPATH, but I won’t talk about that here.) If our executable links
with libfoo, and libfoo depends on libbar, libfoo will include our rpath
in its search for libbar. (EDIT 2014-05-25: You might need
-Wl,-z,origin
for GCC to allow $ORIGIN to be expanded.)
$ORIGIN is also commonly expanded by bash or zsh, so we use single
quotes around our -Wl,-rpath,$ORIGIN/lib
option to prevent that from
happening. To make sure that $ORIGIN didn’t get expanded, you can run
readelf -d my_executable | grep -i rpath
to see the value of your
rpath, making sure it starts with $ORIGIN
.
To specify multiple paths, separate them by a colon, like
-Wl,-rpath,$ORIGIN/lib:$ORIGIN/lib/amd64
.
As you might be able to tell, rpath is great for creating self-contained
applications. You still have to be careful, as any libraries that are
missing from your rpath will still be (silently) searched for in the
system directories. I highly recommend asking users for ldd
output if
you’re trying to debug something with your dependencies.
Many people use LD_LIBRARY_PATH to achieve a similar effect. LD_LIBRARY_PATH is not set at link-time, but rather as an environment variable when your application is run. This is for example what Valve’s steam-runtime does to guarantee that your dynamically linked libraries will be picked from the Steam runtime libraries rather than the system libraries.
The benefit of using LD_LIBRARY_PATH is that it can be set for
applications you cannot edit, but the downside is that it also applies
to any applications launched by the application in question. Say that
you have an application that launches dbus-send
or aplay
– since
they’re system applications, you’d want them to pick their dependencies
from the system, not your LD_LIBRARY_PATH.
Interaction between LD_LIBRARY_PATH and your application’s rpath is well-defined: Your rpath is searched first, and anything it can’t find there it’ll look for in LD_LIBRARY_PATH. Finally, if searches the system directories3.
@rpath
While @rpath is named similarly to its Linux cousin, it behaves a bit differently. When you dynamically link to a library on Mac OS X, the linker stores the “install name” of the library inside your executable. The install name is something that comes from the dylib you’re linking against, and by default it is the absolute path of the linked file. You can change the install name by modifying the dylib after linking4.
After your application has been linked, you can change what the application thinks the install name is for one of its dependent libraries5.
Your application can set its own rpath at link-time using the same
-Wl,-rpath,@executable_path
magic, but note that instead of $ORIGIN,
you use @executable_path or @loader_path. @executable_path behaves
like $ORIGIN, @loader_path is the directory of whatever object is doing the
loading, which could be a dylib that your application has loaded. For
details, read this excellent article by Wincent Colaiuta and
this blog post by Mike Ash.
This rpath does not do anything by default. To make it take effect,
the install name for the shared library has to start with @rpath/
–
and the dynamic linker will then substitute each of the possible values
for @rpath
in order. This means that you’ll typically change the
install name of the dylib (if it’s a dylib you built yourself) or change
the install name inside the application.
Under Mac OS X, you have the DYLD_LIBRARY_PATH environment variable – and this behaves just like it does on Linux. When DYLD_LIBRARY_PATH is set, it is checked before the install name (and therefore, @rpath) is consulted.
Conclusion
Hopefully this helps you understand some nuances of dynamic linking on Mac OS X versus Linux. In my next blog post, I hope to show how you can use DT_RPATH on Linux to link with the Steam runtime when distributing your game outside of Steam.
-
This also applies to .so’s - when one of your dynamically loaded libraries load another dynamic library, their rpath is searched first (if any), then your main application’s rpath is searched.↩
-
The other two variables are
$LIB
and$PLATFORM
, and they deal with finding architecture-specific binaries.↩ -
The truth is a little more complicated, see the ld.so manpage for more info. (http://man7.org/linux/man-pages/man8/ld.so.8.html)↩
-
See the man page for install_name_tool (
install_name_tool -id @rpath/my.dylib my.dylib
)↩ -
See the man page for install_name_tool (
install_name_tool -change old.dylib @rpath/new.dylib my_application
)↩