RPATH and RUNPATH

The DT_RPATH tag is commonly set in executables that depend on libraries that can't be found in the default locations. For instance Qt Creator comes with its own copy of the Qt libraries and has an rpath to the directory that contains them. It's also useful when you build your own version of Qt without installing it globally: Running binaries like qmlviewer will 'just work' because they contain an rpath to the Qt libraries they were compiled with.

If these files didn't have an rpath set, you'd need to explicitly set LD_LIBRARY_PATH before starting them - or write a script that does that for you. And since LD_LIBRARY_PATH is an environment variable that comes with its own set of problems.

Unforunately, there's some confusion about the dynamic loader's search paths on glibc based systems. In particular on the differences between RPATH and RUNPATH. Documentation on the subject is lacking: the first google hit is incorrect, probably outdated. The man pages on ld and ld.so are incomplete and the one on dlopen is wrong about how RUNPATH applies. The debian wiki starts out well, but has the same error as the dlopen page and later contradicts itself.

An additional source of confusion is that, depending on your distribution, the -rpath argument in 'ld' behaves differently. For some it generates a DT_RPATH and for others DT_RPATH and DT_RUNPATH.

Ok, that's not great, but what's the problem?

In short: Setting DT_RUNPATH (or DT_RUNPATH and DT_RPATH) in your application is not enough to guarantee that it will prefer to link to libraries in the directory you specified.

A concrete example where this has caused problems:

  • Qt Creator links to the QtGui it shipped
  • that dlopens the system's KDE plugin, which (indirectly) depends on QtDBus
  • if the Qt Creator binary has a RPATH and no RUNPATH: the QtDBus that came with Creator is loaded
  • if the Qt Creator binary has a RUNPATH: the system's QtDBus is loaded and Creator aborts

The error would be something like "Cannot mix incompatible Qt library (version 473) with this library (version 474)". Because even though the versions are binary compatible, you cannot mix them. Some code in QtDBus may rely on private API in QtCore that may have changed or have been added in 4.7.4.

This can happen for all applications that link to their own QtGui and are run on a system with KDE. But the problem is not limited to KDE either: any plugin that pulls in previously unloaded Qt libraries can look up the wrong ones.

Why does this happen?

Some experiments and reading the glibc source code (check _dl_map_object in elf/dl-load.c) indicate that this is the library search order:

Unless loading object has RUNPATH:
RPATH of the loading object,
then the RPATH of its loader (unless it has a RUNPATH), ...,
until the end of the chain, which is either the executable
or an object loaded by dlopen
Unless executable has RUNPATH:
RPATH of the executable
LD_LIBRARY_PATH
RUNPATH of the loading object
ld.so.cache
default dirs

That explains the behavior: We were depending on plugin loads checking the RPATH of the executable. The executable's RUNPATH is not used for finding indirect library dependencies.

What should I do?

When you ship binaries, either use RPATH and not RUNPATH or ensure LD_LIBRARY_PATH is set before they are run. When you build Qt on a system where 'ld' defaults to --enable-new-dtags (and thus -rpath adds a RUNPATH), be aware that you might have to set LD_LIBRARY_PATH before starting applications built against that Qt version.


Blog Topics:

Comments