rpath问题

rpath指的是有时代码会在链接二进制文件时对特定的库路径进行硬编码(使用 -rpath 或 -R 标志)。

通常,动态链接器和加载器(ld.so)可以解决可执行文件对共享库的依赖关系并加载所需的内容。但是当使用 -rpath 或 -R 时,位置信息将会硬编码到二进制文件中并在执行开始时由 ld.so 检查。

由于 Linux 动态链接器通常比硬编码路径更智能,Fedora 通常不允许使用 rpath。

检查rpath问题

在rpmdevtools中包含一个名为 check-rpaths 的工具。在配置文件中添加宏%__arch_install_post~/.rpmmacros来检查rpath问题:

%__arch_install_post          \
/usr/lib/rpm/check-rpaths     \
/usr/lib/rpm/check-buildroot

当 check-rpaths 运行时,可能会出现以下输出:

ERROR   0001: file '/usr/bin/xapian-tcpsrv' contains a standard rpath '/usr/lib64' in [/usr/lib64]

这表示二进制文件中包含了 rpath,因此需要移除。通过修改相关配置文件或构建过程中的参数,确保移除所有被 check-rpaths 标记的 rpath。移除 rpath 可以提高二进制文件的可移植性和安全性,避免因为库文件位置变化而导致程序无法正常执行的问题。

内部库

  • 当程序安装内部库时,这些库通常不会安装到系统路径中,而是安装在程序包内部。
  • 这些内部库仅供程序包中的程序使用,用于提取出对可执行文件常见的代码(例如,共享通用代码)。
  • 这些库并不打算供程序包外的其他程序使用。因此,当程序包内的程序需要使用这些内部库时,使用 rpath 来找到这些库是可以接受的。

例如:

# Internal libraries for myapp are present in:
%{_libdir}/myapp/
%{_libdir}/myapp/libmyapp.so.0.3.4
%{_libdir}/myapp/libmyapp.so

# myapp has an rpath to %{_libdir}/myapp/
readelf -d /usr/bin/myapp | grep RPATH
 0x0000000f (RPATH)                      Library rpath: [/usr/lib/myapp]

非内部库

  • 当外部程序需要链接到库时,最好使用替代 rpath 的方法,或者直接将库移动到系统的标准库目录(例如 %{_libdir})中。
  • 这样,动态链接器可以在不必使用 rpath 的情况下找到这些库。
  • 移动库到标准库目录可以使得其他程序可以轻松地链接到这些库,而不必每个程序都设置自己的 rpath,这样更加方便和可维护。

替代 rpath 的方法

通常,使用 rpath 是因为二进制文件在非标准位置(标准位置为 /lib、/usr/lib、/lib64、/usr/lib64)查找库。如果将库存储在非标准位置(例如 /usr/lib/foo/),应该在 /etc/ld.so.conf.d/ 中包含一个自定义配置文件。

例如,如果将 libfoo 的 32 位库放在 /usr/lib/foo 中,则需要在 /etc/ld.so.donf.d/ 中包含一个名为“foo32.conf”的文件,内容为:

/usr/lib/foo

确保同时制作了这个文件的 64 位版本(例如 foo64.conf),除非该软件包被禁用于 64 位架构。

删除rpath

  • 如果应用程序使用 configure,尝试传递 –disable-rpath 标志进行配置。
  • 如果应用程序使用 libtool 的本地副本, 在 %configure 之后将以下行添加到规范中:

    %configure
    sed -i 's|^hardcode_libdir_flag_spec=.*|hardcode_libdir_flag_spec=""|g' libtool
    sed -i 's|^runpath_var=LD_RUN_PATH|runpath_var=DIE_RPATH_DIE|g' libtool
    
  • 有时可以修改源代码或者 Makefile 文件来删除 -rpath 或 -R 标志,但是这个方法可能不太容易或者不太明智。
  • 作为最后的手段,Fedora 有一个名为 chrpath 的软件包。安装此软件包后可以在包含 rpath 的文件上运行。例如在前面的示例中运行:chrpath --delete,如果最终使用了这个方法,请确保记得添加 BuildRequires: chrpath

    chrpath --delete $RPM_BUILD_ROOT%{_bindir}/xapian-tcpsrv