- RPATH RUNPATH 和 LD_LIBRARY_PATH
Linux 下链接编译和Windows 绝对有很大的区别。比如应用程序如何加载一个so?为什么我当前目录下有so加载不上能?怎么着我的应用程序就加载到别的路径的so了呢?我们从加载讨论起:影响加载的东西:首先是ELF 中的变量 RPATH 和 RUNPATH然后是环境变量 LD_PRELOAD 和 LD_LIBRARY_PATHLD_PRELOAD 属于强制加载,有点
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 <stdio.h> void test_a() { test_b(); } int main() { test_a(); return 0; }
b.c:
#include <stdio.h> 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
失败,解决方案如下:
- 把 lib_b.so 挪到 /lib 或 /usr/lib 下面,但是这个不太靠谱,毕竟是系统的目录
- 使用LD_LIBRARY_PATH 把当前 lib 目录加进来,执行OK
[root@vm153 01]# LD_LIBRARY_PATH=lib ./a
- 重新编译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 <stdio.h> void test_b() { printf("test_b in: %s\n", __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的情况:
其实这三者的关系概括起来没有几点:
- LD_LIBRARY_PATH 是个环境变量 可以用来指定加载so 的路径,并且优先级高于系统默认的。
- RPATH 是 ELF 格式里面的一个数据,他的优先级比 LD_LIBRARY_PATH 还要高
- 但是有一个很二 的 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 |
更多推荐
所有评论(0)