- RPATH RUNPATH 和 LD_LIBRARY_PATH

2019-07-13 08:34发布

Linux 下链接编译和Windows 绝对有很大的区别。 比如应用程序如何加载一个so?为什么我当前目录下有so 加载不上能?怎么着我的应用程序就加载到别的路径的so了呢? 我们从加载讨论起: 影响加载的东西: 首先是ELF 中的变量 RPATH RUNPATH 然后是环境变量 LD_PRELOAD LD_LIBRARY_PATH LD_PRELOAD 属于强制加载,有点Injection的感觉,这里暂时不讨论,LD_PRELOAD 似乎总是最强势的……   我们从一个应用程序 a 链接一个 lib_b.so 开始 下面是 a b 的代码以及编译链接过程 a.c: 复制代码 #include void test_a() { test_b(); } int main() { test_a(); return 0; } 复制代码  b.c: #include void test_b() { } 编译: [root@vm153 01]# gcc b.c -shared -fPIC -o lib/lib_b.so [root@vm153 01]# gcc -o a a.c -Llib -l_b 因为lib_b.so 在 lib 目录下 所以需要 -Llib 运行结果如下: [root@vm153 01]# ./a ./a: error while loading shared libraries: lib_b.so: cannot open shared object file: No such file or directory 失败,解决方案如下:
  1. 把 lib_b.so 挪到 /lib 或 /usr/lib 下面,但是这个不太靠谱,毕竟是系统的目录
  2. 使用LD_LIBRARY_PATH 把当前 lib 目录加进来,执行OK
    [root@vm153 01]# LD_LIBRARY_PATH=lib ./a
  3. 重新编译a 加入RPATH,执行 OK,注意用readelf 可以查看RPATH (原来-Wl,-rpath= 的作用就是在目标文件里加入了RPATH)
    [root@vm153 01]# gcc -o a a.c -Llib -l_b -Wl,-rpath=lib [root@vm153 01]# ./a [root@vm153 01]# readelf -d a | grep PATH 0x0000000f (RPATH) Library rpath: [lib] [root@vm153 01]#
  那么LD_LIBRARY_PATH RPATH 同时提供的情况下,结果会如何呢? 我们要做一个简单的实验,我们尝试提供两个lib_b.so 并且在test_b代码中给出打印。 b1.c 和 b2.c 代码一致 如下: #include void test_b() { printf("test_b in: %s ", __FILE__); } 编译过程如下: [root@localhost 02]# gcc b1.c -shared -fPIC -o lib1/lib_b.so [root@localhost 02]# gcc b2.c -shared -fPIC -o lib2/lib_b.so [root@localhost 02]# gcc -o a a.c -Llib1 -l_b -Wl,-rpath=lib1 显然 a 的 RPATH 已经被设置成了 lib1 , 然后我们使用 LD_LIBRARY_PATH 设置为lib2 来看看 a究竟加载那个 lib_b [root@localhost 02]# LD_LIBRARY_PATH=lib2 ./a test_b in: b1.c [root@localhost 02]# 很明显是RPATH 起了作用。其实没有必要加入打印,用ldd 一样可以验证具体加载了哪个 so [root@localhost 02]# LD_LIBRARY_PATH=lib2 ldd a linux-vdso.so.1 => (0x00007fffc51c8000) lib_b.so => lib1/lib_b.so (0x00007f5b6d268000) libc.so.6 => /lib64/libc.so.6 (0x00007f5b6cebe000) /lib64/ld-linux-x86-64.so.2 (0x00007f5b6d46a000) [root@localhost 02]#   加上RUNPATH 吧! 再复杂一点的情况, RUNPATH 我们用的不太多,但是有的系统上 -Wl,-rpath 会一起加上RUNPATH,重新编译a 使用--enable-new-dtags 加入RUNPATH 复制代码 [root@localhost 02]# gcc -o a a.c -Llib1 -l_b -Wl,-rpath=lib1,--enable-new-dtags [root@localhost 02]# readelf -d a | grep PATH 0x000000000000000f (RPATH) Library rpath: [lib1] 0x000000000000001d (RUNPATH) Library runpath: [lib1] [root@localhost 02]# LD_LIBRARY_PATH=lib2 ldd a linux-vdso.so.1 => (0x00007fffa4591000) lib_b.so => lib2/lib_b.so (0x00007f8779855000) libc.so.6 => /lib64/libc.so.6 (0x00007f87794ab000) /lib64/ld-linux-x86-64.so.2 (0x00007f8779a57000) [root@localhost 02]# 复制代码 恩~~ 这个有点困惑了。 其实逻辑是这样的:加载器首先看有没有 RUNPATH, 如果有那么就尝试从LD_LIBRARY_PATH 加载,如果还是失败再从RUNPATH加载。当然最后如果还是不行,会尝试 /lib 和 /usr/lib。 这个RUNPATH 真的有点二,设置了一个值,但是不会马上启用,而且还干掉了 RPATH 成全了LD_LIBRARY_PATH……   如何创建只有RUNPATH 的 ELF 文件? 按照上面的推论,当RPATH 和 RUNPATH 同时存在的话,那么RPATH 实际就没有用了(这点逻辑应该能理解吧),可是ld 链接出来的要么没有 RPATH 和 RUNPATH,要么只有RPATH, 要么两个都有(我想可能是为了兼容一下老版本的loader 吧)。 简单的方案,利用第三方工具 patchelf (http://nixos.org/patchelf.html)。先编译出一个没有RPATH RUNPATH的 ELF 文件,然后 用 patchelf --set-rpath 来添加,这样就只会添加 RNPATH。当然这个工具还有好些个其他选项……   小结一下一个ELF 文件自身加载 so的情况: 其实这三者的关系概括起来没有几点:
  1. LD_LIBRARY_PATH 是个环境变量 可以用来指定加载so 的路径,并且优先级高于系统默认的。
  2. RPATH 是 ELF 格式里面的一个数据,他的优先级比 LD_LIBRARY_PATH 还要高
  3. 但是有一个很二 的 RUNPATH,也是 ELF 格式中的,如果他出现了 RPATH 就躲起来了,LD_LIBRARY_PATH 又成了首选
整理出了一张表,供参考 ELF 中 RPATH ELF 中 RUNPATH LD_LIBRARY_PATH 变量 尝试加载目录的顺序 未设置 未设置 未设置 /lib => /usr/lib  未设置 未设置 设置 ${LD_LIBRARY_PATH} => /lib => /usr/lib 设置 未设置 设置 ${RPATH} => /lib => /usr/lib 设置 未设置 设置 ${RPATH} => ${LD_LIBRARY_PATH} => /lib => /usr/lib 设置 或 未设置 设置  设置  ${LD_LIBRARY_PATH} => ${RUN_PATH} =>  /lib => /usr/lib 设置 或 未设置 设置  设置  ${RUN_PATH} =>  /lib => /usr/lib