Linux so 文件

6 篇文章 0 订阅

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环境下有些差异,具体加载顺序总结如下:

  1. ELF可执行文件中动态段DT_RPATH指定;gcc加入链接参数“-Wl,-rpath”指定动态库搜索路径;
  2. 环境变量LD_LIBRARY_PATH指定路径;
  3. /etc/ld.so.cache中缓存的动态库路径。可以通过修改配置文件/etc/ld.so.conf
    增删路径(修改后需要运行ldconfig命令);
  4. 默认的 /lib/;
  5. 默认的 /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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值