Post

使用patchelf修改rpath

使用patchelf修改rpath

自动解决共享库链接问题

当从软件包获取一个程序时,经常需要手动设置 LD_LIBRARY_PATH 环境变量,以确保程序能够正确加载共享库并正常运行。为了避免每次都进行繁琐的配置,可以通过为程序设置 RPATH 来实现自动查找共享库,从而简化操作。

问题背景

例如,当你从 SourceForge 下载并解压 srecord RPM 包后,你会发现其目录结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
[wanlinwang@computing-server-01 usr]$ ls *
bin:
srec_cat  srec_cmp  srec_info

include:
srecord

lib:
ld-linux-x86-64.so.2  libgcc_s.so.1    libgcrypt.so.20.3.4  libgpg-error.so.0.32.1  libm.so.6       libstdc++.so.6.0.30
libc.so.6             libgcrypt.so.20  libgpg-error.so.0    liblib_srecord.a        libstdc++.so.6

share:
doc  man

直接运行 srec_cat 等命令时,可能会提示共享库未找到。

解决方案

为了让程序能够自动找到其依赖的共享库,而无需用户每次手动设置环境变量,可以通过设置 ELF 文件的 RPATH,使得动态链接器在指定目录(如 lib 目录)中查找共享库。

1. 设置 RPATH

使用 patchelf 工具可以轻松修改 ELF 文件的 RPATH。RPATH 告诉动态链接器去哪里查找共享库,$ORIGIN 代表程序所在目录,因此设置 RPATH 为 $ORIGIN/../lib,表示程序将从其上级目录的 lib 文件夹中查找共享库。

1
[wanlinwang@computing-server-01 bin]$ patchelf --set-rpath '$ORIGIN/../lib' --force-rpath *

2. 验证 RPATH 设置

修改 RPATH 后,可以通过 readelf 查看每个二进制文件的 RPATH 设置,确保其正确:

1
2
3
4
[wanlinwang@computing-server-01 bin]$ readelf -d * | grep -i rpath
 0x000000000000000f (RPATH)              Library rpath: [$ORIGIN/../lib]
 0x000000000000000f (RPATH)              Library rpath: [$ORIGIN/../lib]
 0x000000000000000f (RPATH)              Library rpath: [$ORIGIN/../lib]

3. 验证共享库加载情况

通过 ldd 命令检查共享库是否正确加载,确保程序能够从预期目录加载共享库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[wanlinwang@computing-server-01 bin]$ ldd *
srec_cat:
	linux-vdso.so.1 =>  (0x00007fff02d8d000)
	libgcrypt.so.20 => /nfs/home/wanlinwang/usr/bin/./../lib/libgcrypt.so.20 (0x00007f9b740a0000)
	libstdc++.so.6 => /nfs/home/wanlinwang/usr/bin/./../lib/libstdc++.so.6 (0x00007f9b73e76000)
	libgcc_s.so.1 => /nfs/home/wanlinwang/usr/bin/./../lib/libgcc_s.so.1 (0x00007f9b742d3000)
	libc.so.6 => /nfs/home/wanlinwang/usr/bin/./../lib/libc.so.6 (0x00007f9b73c4e000)
	libgpg-error.so.0 => /nfs/home/wanlinwang/usr/bin/./../lib/libgpg-error.so.0 (0x00007f9b742ac000)
	libm.so.6 => /nfs/home/wanlinwang/usr/bin/./../lib/libm.so.6 (0x00007f9b73b67000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f9b741de000)
srec_cmp:
	linux-vdso.so.1 =>  (0x00007fffa639e000)
	libgcrypt.so.20 => /nfs/home/wanlinwang/usr/bin/./../lib/libgcrypt.so.20 (0x00007f5fd42ca000)
	libstdc++.so.6 => /nfs/home/wanlinwang/usr/bin/./../lib/libstdc++.so.6 (0x00007f5fd4067000)
	libgcc_s.so.1 => /nfs/home/wanlinwang/usr/bin/./../lib/libgcc_s.so.1 (0x00007f5fd4047000)
	libc.so.6 => /nfs/home/wanlinwang/usr/bin/./../lib/libc.so.6 (0x00007f5fd3e1f000)
	libgpg-error.so.0 => /nfs/home/wanlinwang/usr/bin/./../lib/libgpg-error.so.0 (0x00007f5fd3df9000)
	libm.so.6 => /nfs/home/wanlinwang/usr/bin/./../lib/libm.so.6 (0x00007f5fd3d12000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f5fd4291000)
srec_info:
	linux-vdso.so.1 =>  (0x00007ffe78da2000)
	libgcrypt.so.20 => /nfs/home/wanlinwang/usr/bin/./../lib/libgcrypt.so.20 (0x00007f97be3fc000)
	libstdc++.so.6 => /nfs/home/wanlinwang/usr/bin/./../lib/libstdc++.so.6 (0x00007f97be19c000)
	libgcc_s.so.1 => /nfs/home/wanlinwang/usr/bin/./../lib/libgcc_s.so.1 (0x00007f97be17c000)
	libc.so.6 => /nfs/home/wanlinwang/usr/bin/./../lib/libc.so.6 (0x00007f97bdf54000)
	libgpg-error.so.0 => /nfs/home/wanlinwang/usr/bin/./../lib/libgpg-error.so.0 (0x00007f97bdf2e000)
	libm.so.6 => /nfs/home/wanlinwang/usr/bin/./../lib/libm.so.6 (0x00007f97bde47000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f97be3c6000)

此时,所有的共享库都已成功加载,且程序会自动从解压目录下的 lib 文件夹中查找所需的共享库。

共享库查找顺序

当动态链接器查找共享库时,会按照以下顺序进行:

  1. RPATH:如果可执行文件的 RPATH 存在且不为空,链接器将首先检查这些路径。
  2. LD_LIBRARY_PATH:如果设置了环境变量 LD_LIBRARY_PATH,链接器将依次检查其中列出的目录。
  3. RUNPATH:如果可执行文件包含 RUNPATH,链接器将使用这些路径。
  4. 缓存:检查 /etc/ld.so.cache 是否包含所需的库。
  5. 系统默认路径:如果以上都没有找到,链接器将检查 /lib/usr/lib 目录。

总结

通过为程序设置 RPATH,可以避免手动设置 LD_LIBRARY_PATH,并确保共享库能够自动找到。这样做的好处是用户无需额外配置,软件运行时自动加载必要的库,提高了用户体验和软件的可移植性。

参考资料

共享库查找顺序,来自The Linux Programming

When resolving library dependencies, the dynamic linker first inspects each dependency string to see if it contains a slash (/), which can occur if we specified an explicit library pathname when linking the executable. If a slash is found, then the dependency string is interpreted as a pathname (either absolute or relative), and the library is loaded using that pathname. Otherwise, the dynamic linker searches for the shared library using the following rules:

  1. If the executable has any directories listed in its DT_RPATH run-time library path list (rpath) and the executable does not contain a DT_RUNPATH list, then these directories are searched (in the order that they were supplied when linking the program).

  2. If the LD_LIBRARY_PATH environment variable is defined, then each of the colon-separated directories listed in its value is searched in turn. If the executable is a set-user-ID or set-group-ID program, then LD_LIBRARY_PATH is ignored. This is a security measure to prevent users from tricking the dynamic linker into loading a private version of a library with the same name as a library required by the executable.

  3. If the executable has any directories listed in its DT_RUNPATH run-time library path list, then these directories are searched (in the order that they were supplied when linking the program).

  4. The file /etc/ld.so.cache is checked to see if it contains an entry for the library.

  5. The directories /lib and /usr/lib are searched (in that order).

This post is licensed under CC BY 4.0 by the author.

支持创作者

如果本文帮助到你,可以通过以下收款码支持我:

收款码

感谢你的支持!