Linux程序加载动态链接库的搜索路径
Linux与Windows都支持动态链接库,Windows下是dll文件,Linux下是so文件,
如果程序执行时没有找到需要的动态链接库,Windows下会出现提示框,Linux下会提示:
error while loading shared libraries: xxx.so: cannot open shared object file: No such file or directory
这都是因为按照一定的规则,可执行程序没有找到某个库文件
Windows下可执行文件运行时会在当前目录和系统目录以及PATH环境变量的目录列表中搜索dll文件,而Linux环境下有些差异,具体加载顺序总结如下:
- ELF可执行文件中动态段DT_RPATH指定;gcc加入链接参数“-Wl,-rpath”指定动态库搜索路径;
- 环境变量LD_LIBRARY_PATH指定路径;
- /etc/ld.so.cache中缓存的动态库路径。可以通过修改配置文件/etc/ld.so.conf
增删路径(修改后需要运行ldconfig命令); - 默认的 /lib/;
- 默认的 /usr/lib/
详细的:
-
4和5比较好理解,只需将so文件放入/lib或/usr/lib下,可执行文件就能找到它;
-
2有点类似Windows下PATH环境变量的作用:
设定LD_LIBRARY_PATH:export LD_LIBRARY_PATH=./
不好的地方在于,在当前环境上下文中,所有的可执行文件运行时都会先搜索一边当前目录
取消LD_LIBRARY_PATH值的设定:unset LD_LIBRARY_PATH -
3是通过修改ld的配置来设定可执行文件搜索动态链接库的路径,同样是一种全局效果
-
如果能确定动态链接库文件与可执行文件的相对路径,使用1是最合适的,它将搜索行为编译到了可执行文件中
举例:
// foo.c
int add(int a, int b){ return a + b; }
#编译为动态链接库
#gcc foo.c -shared -o libfoo.so// main.c
extern add(int a, int b);
int main(){ return add(1, 2); }
#编译为可执行文件,并指定需要动态链接libfoo.so
#gcc main.c -L./ -lfoo -Wl,-rpath=./
#说明:-L./ 指定编译时动态链接库的路径
-lfoo 指定需要链接libfoo.so这个库
-Wl,rpath=./ 最关键的,将搜索路径编译到可执行程序中
另外:关于查看程序依赖的动态链接库,Windows下可以使用Dependency Walker,Linux下可以使用ldd命令,如:ldd ./a.out
补充命令
编译时使用-Wl,–as-needed,可以时可执行文件只依赖用到的so文件
关于Linux程序执行的符号查找
与Windows平台不同,程序执行链接时允许有相同名称的符号存在,如链接的so中定义了func1,自己的main.cpp中也可以定义一个,编译过了,链接就不会报错,实际运行时执行的是main.cpp中定义的func1。
使用nm工具可以查看可执行文件和so文件内的符号列表,只要定义了(非static)就会导出该符号。
如果main.cpp中只是引用func1,那么导出时的符号类型是U,如果定义了func1,其符号类型就是T
一共主要有以下几种类型:
1)U,未定义符号
表示这个符号没有在本文件中定义,需要解析别的文件从而找出对应符号的定义。
例如,当前文件调用另一个文件中定义的函数或者全局变量,这个被调用的函数或全局变量在当前文件中就是未定义的。(但是,在定义它的文件中,如果是函数则对应的类型是T,而如果是全局变量则其符号类型为C)。
2)A,绝对符号
表示该符号的值是绝对的,在以后的链接过程中,不允许进行改变。这种类型的符号常常出现在中断向量表中,例如用符号来表示各个中断向量函数在中断向量表中的位置。
3)T,定义在__TEXT段__text区(代码区)中的符号
表示该符号位于代码区中,其值表示该符号在整个文件当中的所处的位置。
有点奇怪的是符号“__mh_execute_header”竟然类型也为T,算作在代码区定义的符号。
4)D,定义在__DATA段__data区中的符号
表明该符号位于初始化数据区中,其值表示该符号在整个文件当中的所处的位置。
5)B,定义在__DATA段__bss区中的符号
表明该符号位于非初始化数据区中,其值表示该符号在bss段中的偏移。
6)C,所谓的普通(Common)符号,定义在__DATA段__common区中的符号
普通符号是定义在一个未初始化数据段内的符号。该符号没有包含于一个普通的区中,只有在链接过程中才进行分配,符号的值表示该符号需要的字节数。例如在一个C文件中,定义int
test,并且该符号在别的地方会被引用,则该符号类型即为C,否则其类型为B。7)I,间接符号
说明这个符号是仅仅是对另一个符号的间接引用。
8)S,其它符号
定义在除前所述其它地方的符号,例如出现在__TEXT段__const区中的符号。
实验1:最普通的so创建与使用
lib1.cpp 中定义 func1(){}
main.cpp 中声明 func1(); 并使用。
gcc -fPIC -shared lib1.cpp -o liblib1.so
gcc main.cpp -L./ -Wl,-rpath=./ -llib1
实验2:main中重复定义了so中的函数
lib1.cpp 中定义 func1(){}
main.cpp 中也定义了 func1(); 并使用。
gcc -fPIC -shared lib1.cpp -o liblib1.so
gcc main.cpp -L./ -Wl,-rpath=./ -llib1
结果:没有报错(Windows环境会报重复定义),main.cpp 实际调用执行的是main.cpp中定义的func1,即它将so中的定义覆盖了,并且这时即使删除掉lib1.so文件,main程序仍然能执行。
实验3:main中重复定义了so中的函数,并且main还链接了另外一个使用func1的so库
lib1.cpp 中定义 func1(){}
lib2.cpp 中声明了 func1();并使用
main.cpp 中也定义了 func1(); 并使用。
gcc -fPIC -shared lib1.cpp -o liblib1.so
gcc -fPIC -shared -L./ -Wl,-rpath=./ -llib1 lib2.cpp -o liblib2.so
gcc main.cpp -L./ -Wl,-rpath=./ -llib1 -llib2
结论:main.cpp 和 lib2.cpp 中实际执行的都是 main.cpp 中定义的 func1
实验4:lib1中定义func1函数,lib2和main都使用lib1
lib1.cpp 中定义 func1(){}
lib2.cpp 中声明了 func1();并使用
main.cpp 中声明了 func1();并使用
gcc -fPIC -shared lib1.cpp -o liblib1.so
gcc -fPIC -shared -L./ -Wl,-rpath=./ -llib1 lib2.cpp -o liblib2.so
gcc main.cpp -L./ -Wl,-rpath=./ -llib2 -llib1
结论:都使用的是lib1中的唯一一个定义,但需要注意,在最后编译main时,-llib2 要在 -llib1 之前
实验5 copy 实验3,不同的是在lib1.cpp中增加了对func1的调用
结论:关键意外:lib1.cpp中对func1的调用并不是执行的lib1.cpp中的func1,而是main.cpp中的func1