RPATH与RUNPATH

原文链接: ckammRPATH and RUNPATH

DT_RPATH通常设置在这样一种可执行程序中,它依赖的库无法在默认位置中被找到。举例来说,Qt Creator自带一个Qt库的副本,而且有一个指向库文件所在目录的rpath。当你构建自己的Qt库而不将其安装到全局位置时,这也是很有用的:qmlviewer之类的二进制文件之所以能够工作,是因为它们包含了一个指向了编译时所用Qt库的rpath。

如果这些文件没有设置rpath,在启动它们之前你需要显式地设置LD_LIBRARY_PATH,或写一个替你做这些事情的脚本。由于LD_LIBRARY_PATH是一个环境变量,它也有自身的一些问题。

很不幸的是:在基于glibc的系统中,动态加载器的搜索路径有些混乱。尤其是在RPATH和RUNPATH二者的区别上。这方面的文档非常匮乏: Google给出的第一个结果 是不正确的,可能只是过时了。ld和ld.so的man页面是不完整的,而且dlopen中对 RUNPATH如何起作用的描述是错的Debain Wiki 一开始说得不错,但是和dlopen页面有同样的错误,而且后面有些自相矛盾。

另一个混乱的来源是:根据你的发行版的不同,“ld”的-rpath参数表现出不同的行为。在一些发行版中生成DT_RPATH,而在另外一些发行版中却同时生成DT_RPATH和DT_RUNPATH。

好的,这并不严重,但问题是什么呢?

简单地说:在你的应用程序中设置DT_RUNPATH(或者DT_RUNPATH和DT_RPATH)并不足以保证它链接到你所指定目录中的库。

一个导致问题的具体例子:

  • Qt Creator链接到它自带的QtGui
  • 它通过dlopen加载了系统提供的KDE插件,该插件(间接)依赖QtDBus
  • 如果Qt Creator的二进制程序设置了RPATH而没有RUNPATH:Qt Creator自带QtDBus被加载
  • 如果Qt Creator的二进制程序设置了RUNPATH:系统提供的QtDBus被加载且导致Qt Creator终止。

该错误将会类似这样“Cannot mix incompatible Qt library (version 473) with this library (version 474)”[不能将当前库(版本474)与不兼容的Qt库(版本473)混用]。因为即使版本间是二进制兼容的,你也不能混用它们。QtDBus可能会依赖QtCore中已经发生变化或者在4.7.4中新增的私有API。

对于所有的链接到自有QtGui库且运行在带有KDE的系统上的应用程序,都可能发生这种问题。但是该问题也并不局限于KDE:任何使用了先前未加载的Qt库的插件,都可能查找到错误的库。

为什么会这种情况?

实验以及对glibc源码(elf/dl-load.c中的_dl_map_object)阅读表明,库的搜索顺序如下:

除非正被加载的对象(object)拥有RUNPATH:
    正被加载对象的RPATH,
        然后是其加载者(loader)的RPATH(除非它有RUNPATH),...,
        直到搜索链的结束,它要么是可执行程序,
        要么是通过dlopen加载的对象
    除非可执行程序拥有RUNPATH:
        该可执行程序的RPATH
LD_LIBRARY_PATH
正被加载对象的RUNPATH
ld.so.cache
默认路径


该过程解释了这种行为:我们依赖于插件加载时检查可执行程序的RPATH。可执行程序的RUNPATH并不被用来查找间接依赖的库。

我们该如何做?

当你发布二进制(程序)时,要么使用RPATH且不使用RUNPATH,要么确保LD_LIBRARY_PATH在运行前被设置。当你在“ld”默认是–enable-new-dtags(因此-rpath会添加RUNPATH)的系统中构建Qt时,要明白一点:在启动使用该版本Qt构建的程序时,你可能必须要先设置LD_LIBRARY_PATH。