摘自http://gotowqj.iteye.com/blog/1926734css
摘自http://www.360doc.com/content/14/0313/13/12747488_360246417.shtmlhtml
linux 下有动态库和静态库,动态库以.so为扩展名,静态库以.a为扩展名。两者都使用普遍。本文主要讲动态库方面知识。node
这么多so,是的。使用ldd显示的so,并非全部so都是须要使用的,下面举个例子linux
看看,虽然没有用到,可是同样有连接进来,那看看程序启动时候有没有去加载它们呢ios
咱们知道linux连接so有两种途径:显示和隐式。所谓显示就是程序主动调用dlopen打开相关so;这里须要补充的是,若是使用显示连接,上篇文章讨论的那些问题都不存在。首先,dlopen的so使用ldd是查看不到的。其次,使用dlopen打开的so并非在进程启动时候加载映射的,而是当进程运行到调用dlopen代码地方才加载该so,也就是说,若是每一个进程显示连接a.so;可是若是发布该程序时候忘记附带发布该a.so,程序仍然可以正常启动,甚至若是运行逻辑没有触发运行到调用dlopen函数代码地方。该程序还能正常运行,即便没有a.so.c++
既然显示加载这么多优势,那么为何实际生产中不多码农使用它呢, 主要缘由仍是起使用不是很方便,须要开发人员多写很多代码。因此不被大多数码农使用,还有一个重要缘由应该是能提早发现错误,在部署的时候就能发现缺乏哪些so,而不是等到实际上限运行的时候才发现缺东少西。shell
下面举个工做中最常碰到的问题,来引伸出本篇内容吧。ubuntu
写一个最简单的so, tmp.cppbash
1. int test()app
2. {
3. return 20;
4. }
编译=>连接=》运行, 下面main.cpp 内容请参见上一篇文章。
[stevenrao]$ g++ -fPIC -c tmp.cpp
[stevenrao]$ g++ -shared -o libtmp.so tmp.o
[stevenrao]$ mv libtmp.so /tmp/
[stevenrao]$ g++ -o demo -L/tmp -ltmp main.cpp
[stevenrao]$ ./demo
./demo: error while loading shared libraries: libtmp.so: cannot open shared object file: No such file or directory
[stevenrao]$ g++ -fPIC -c tmp.cpp
[stevenrao]$ g++ -shared -o libtmp.so tmp.o
[stevenrao]$ mv libtmp.so /tmp/
[stevenrao]$ g++ -o demo -L/tmp -ltmp main.cpp
[stevenrao]$ ./demo
./demo: error while loading shared libraries: libtmp.so: cannot open shared object file: No such file or directory
[stevenrao]$ ldd demo
linux-vdso.so.1 => (0x00007fff7fdc1000)
libtmp.so => not found
[stevenrao]$ ldd demo
linux-vdso.so.1 => (0x00007fff7fdc1000)
libtmp.so => not found
这个错误是最多见的错误了。运行程序的时候找不到依赖的so。通常人使用方法是修改LD_LIBRARY_PATH这个环境变量
export LD_LIBRARY_PATH=/tmp
[stevenrao]$ ./demo
test
这样就OK了, 不过这样export 只对当前shell有效,当另开一个shell时候,又要从新设置。能够把export LD_LIBRARY_PATH=/tmp 语句写到 ~/.bashrc中,这样就对当前用户有效了,写到/etc/bashrc中就对全部用户有效了。
前面连接时候使用 -L/tmp/ -ltmp 是一种设置相对路径方法,还有一种绝对路径连接方法。
[stevenrao]$ g++ -o demo /tmp/libtmp.so main.cpp
[stevenrao]$ ./demo
test
[stevenrao]$ ldd demo
linux-vdso.so.1 => (0x00007fff083ff000)
/tmp/libtmp.so (0x00007f53ed30f000)
绝对路径虽然申请设置环境变量步骤,可是缺陷也是致命的,这个so必须放在绝对路径下,不能放到其余地方,这样给部署带来很大麻烦。因此应该禁止使用绝对路径连接so。
搜索路径分两种,一种是连接时候的搜索路径,一种是运行时期的搜索路径。像前面提到的 -L/tmp/ 是属于连接时期的搜索路径,即给ld程序提供的编译连接时候寻找动态库路径;而 LD_LIBRARY_PATH则既属于连接期搜索路径,又属于运行时期的搜索路径。
这里须要介绍链-rpath连接选项,它是指定运行时候都使用的搜索路径。聪明的同窗立刻就想到,运行时搜索路径,那它记录在哪儿呢。也像. LD_LIBRARY_PATH那样,每部署一台机器就须要配一下吗。呵呵,不须要..,由于它已经被硬编码到可执行文件内部了。看看下面演示
[stevenrao] $ g++ -o demo -L /tmp/ -ltmp main.cpp
[stevenrao] $ ./demo
./demo: error while loading shared libraries: libtmp.so: cannot open shared object file: No such file or directory
[stevenrao] $ g++ -o demo -Wl,-rpath /tmp/ -L/tmp/ -ltmp main.cpp
[stevenrao] $ ./demo
test
[stevenrao] $ readelf -d demo
Dynamic section at offset 0xc58 contains 26 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libtmp.so]
0x0000000000000001 (NEEDED) Shared library: [libstdc++.so.6]
0x0000000000000001 (NEEDED) Shared library: [libm.so.6]
0x0000000000000001 (NEEDED) Shared library: [libgcc_s.so.1]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000000f (RPATH) Library rpath: [/tmp/]
0x000000000000001d (RUNPATH) Library runpath: [/tmp/]
看看是吧,编译到elf文件内部了,路径和程序深深的耦合到一块儿
继续上一篇《 linux下so动态库一些鲜为人知的秘密(中) 》介绍so搜索路径,还有一个相似于-path,叫LD_RUN_PATH环境变量, 它也是把路径编译进可执行文件内,不一样的是它只设置RPATH。
如何程序在链接时使用了共享库,就必须在运行的时候可以找到共享库的位置。linux的可执行程序在执行的时候默认是先搜索/lib和/usr/lib这两个目录,而后按照/etc/ld.so.conf里面的配置搜索绝对路径。同时,Linux也提供了环境变量LDLIBRARYPATH供用户选择使用,用户能够经过设定它来查找除默认路径以外的其余路径,如查找/work/lib路径,你能够在/etc/rc.d/rc.local或其余系统启动后便可执行到的脚本添加以下语句:LDLIBRARYPATH =/work/lib:$(LDLIBRARYPATH)。而且LDLIBRARYPATH路径优先于系统默认路径以前查找(详细参考《使用LDLIBRARYPATH》)。
不过LDLIBRARYPATH的设定做用是全局的,过多的使用可能会影响到其余应用程序的运行,因此多用在调试。(LDLIBRARYPATH的缺陷和使用准则,能够参考《Why LDLIBRARYPATH is bad》 )。一般状况下推荐仍是使用gcc的-R或-rpath选项来在编译时就指定库的查找路径,而且该库的路径信息保存在可执行文件中,运行时它会直接到该路径查找库,避免了使用LDLIBRARYPATH环境变量查找。
现代链接器在处理动态库时将连接时路径(Link-time path)和运行时路径(Run-time path)分开,用户能够经过-L指定链接时库的路径,经过-R(或-rpath)指定程序运行时库的路径,大大提升了库应用的灵活性。好比咱们作嵌入式移植时#arm-linux-gcc $(CFLAGS) –o target –L/work/lib/zlib/ -llibz-1.2.3 (work/lib/zlib下是交叉编译好的zlib库),将target编译好后咱们只要把zlib库拷贝到开发板的系统默认路径下便可。或者经过-rpath(或-R )、LDLIBRARYPATH指定查找路径。
连接器ld的选项有 -L,-rpath 和 -rpath-link,看了下 man ld,大体是这个意思:
-L: “连接”的时候,去找的目录,也就是全部的 -lFOO 选项里的库,都会先从 -L 指定的目录去找,而后是默认的地方。编译时的-L选项并不影响环境变量LDLIBRARYPATH,-L只是指定了程序编译链接时库的路径,并不影响程序执行时库的路径,系统仍是会到默认路径下查找该程序所须要的库,若是找不到,仍是会报错,相似cannot open shared object file。
-rpath-link:这个也是用于“连接”的时候的,例如你显示指定的须要 FOO.so,可是 FOO.so 自己是须要 BAR.so 的,后者你并无指定,而是 FOO.so 引用到它,这个时候,会先从 -rpath-link 给的路径里找。
-rpath: “运行”的时候,去找的目录。运行的时候,要找 .so 文件,会从这个选项里指定的地方去找。对于交叉编译,交叉编译连接器需已经配置 --with-sysroot 选项才能起做用。也就是说,-rpath指定的路径会被记录在生成的可执行程序中,用于运行时查找须要加载的动态库。-rpath-link 则只用于连接时查找。
直接man ld。The linker uses the following search paths to locate required shared libraries:
1. Any directories specified by -rpath-link options. 2. Any directories specified by -rpath options. The difference between -rpath and -rpath-link is that directories specified by -rpath options are included in the executable and used at runtime, whereas the -rpath-link option is only effective at link time. Searching -rpath in this way is only supported by native linkers and cross linkers which have been configured with the --with-sysroot option. 3. On an ELF system, for native linkers, if the -rpath and -rpath-link options were not used, search the contents of the environment variable "LD_RUN_PATH". 4. On SunOS, if the -rpath option was not used, search any directories specified using -L options. 5. For a native linker, the search the contents of the environment variable "LD_LIBRARY_PATH". 6. For a native ELF linker, the directories in "DT_RUNPATH" or "DT_RPATH" of a shared library are searched for shared libraries needed by it. The "DT_RPATH" entries are ignored if "DT_RUNPATH" entries exist. 7. The default directories, normally /lib and /usr/lib. 8. For a native linker on an ELF system, if the file /etc/ld.so.conf exists, the list of directories found in that file. If the required shared library is not found, the linker will issue a warning and continue with the link.
1 |
gcc -Wl,--start-group foo.o bar.o -Wl,--end-group |
This is important, because otherwise the compiler driver program may silently drop the linker options, resulting in a bad link.
二进制 |
对应源码 |
|
有一个程序 |
a.out |
main.c |
须要加载插件A |
libA.so |
liba.c |
A须要另外一个动态库 |
libB.so |
libB1.c 或 libB2.c |
本文的关注点就是:究竟是哪个libB.so被加载
目录结构:
/home/debao/ttt/a.out /home/debao/ttt/libA.so /home/debao/ttt/libB.so /usr/lib/libB.so
main.c ==> ./a.out
#include <stdio.h> #include <dlfcn.h> typedef int (*funcA)(int, int); int main() { void * plugin = dlopen("./libA.so", RTLD_LAZY); funcA f = (funcA)dlsym(plugin, "funcA"); printf("main: %d\n", f(3,4)); return 0; }
liba.c ==> ./libA.so
#include <stdio.h> int funcB(int, int); int funcA(int a, int b) { printf("hello from funcA\n"); return funcB(a, b); }
libb1.c ==> ./libB.so
#include <stdio.h> int funcB(int a, int b) { printf("Hello from funcB 1\n"); return a*b; }
libb2.c ==> /usr/lib/libB.so
#include <stdio.h> int funcB(int a, int b) { printf("Hello from funcB 2\n"); return a*b; }
$ gcc -shared -fPIC libb2.c -o libB2.so $ sudo mv libB2.so /usr/lib/libB.so $ gcc -shared -fPIC libb.c -o libB.so
$ gcc -shared -fPIC liba.c -o libA.so -L. -lB
顺便看看该elf文件的头部信息:
$ readelf libA.so -d Dynamic section at offset 0xf20 contains 21 entries: Tag Type Name/Value 0x00000001 (NEEDED) Shared library: [libB.so] 0x00000001 (NEEDED) Shared library: [libc.so.6] ...
恩,只有库的文件名信息,而没有路径信息。
$ gcc main.c -ldl $ ./a.out hello from funcA Hello from funcB 2 main: 12
程序:dlopen从当前目录找到libA.so,而后却在/usr/lib/中找到libB.so(没有使用当前目录的libB.so,这是咱们须要的么?)
$ gcc main.c -ldl -Wl,--rpath=. $ ./a.out hello from funcA Hello from funcB 1 main: 12
恩,使用当前目录的libB.so,很理想的东西
但是,因为DT_RPATH没法被环境变量LD_LIBRARY_PATH覆盖,不是不建议被使用,而是建议使用DT_RUNPATH么?
$ gcc main.c -ldl -Wl,--rpath=.,--enable-new-dtags $ ./a.out hello from funcA Hello from funcB 2 main: 12
问题从新出现,使用的系统路径中的libB.so 而不是当前目录下的。
经过下列命令能够查看:
$ readelf -d a.out
为了完整起见,列出前面3次编译的程序的信息:
Dynamic section at offset 0xf20 contains 21 entries: Tag Type Name/Value 0x00000001 (NEEDED) Shared library: [libdl.so.2] 0x00000001 (NEEDED) Shared library: [libc.so.6] 0x0000000c (INIT) 0x8048360 ...
Dynamic section at offset 0xf18 contains 22 entries: Tag Type Name/Value 0x00000001 (NEEDED) Shared library: [libdl.so.2] 0x00000001 (NEEDED) Shared library: [libc.so.6] 0x0000000f (RPATH) Library rpath: [.] 0x0000000c (INIT) 0x8048360 ....
Dynamic section at offset 0xf10 contains 23 entries: Tag Type Name/Value 0x00000001 (NEEDED) Shared library: [libdl.so.2] 0x00000001 (NEEDED) Shared library: [libc.so.6] 0x0000000f (RPATH) Library rpath: [.] 0x0000001d (RUNPATH) Library runpath: [.]
RPATH and RUNPATH给出这个问题的答案:
Unless loading object has RUNPATH: RPATH of the loading object, then the RPATH of its loader (unless it has a RUNPATH), ..., until the end of the chain, which is either the executable or an object loaded by dlopen Unless executable has RUNPATH: RPATH of the executable LD_LIBRARY_PATH RUNPATH of the loading object ld.so.cache default dirs
用它解释第一个程序:
用它解释第二个程序:
用它解释第三个程序:
有意思的就是这个程序了,可执行程序的RUNPATH是一个重要的判断条件,却并不被作为这儿搜索路径!!
本文是在kubuntu 11.10下编写测试的。为了尽量简单,例子也都是认为制造的。并且咱们看到,在使用RPATH的时候是正常的,RUNPATH通常来讲,被推荐使用,但这儿它却不能正常工做。
因此,当使用RUNPATH时,咱们须要明白:某些状况下可能须要设置环境变量 LD_LIBRARY_PATH