一文搞懂系列——动态库的加载方式及应用场景

引文

        我们在工作中经常会遇到动态库链接的问题,因为正常的方式并不能满足我们的场景。常见的问题可以总结如下:

  • 系统路径默认路径、usr/lib、/lib 目录,不会集成第三方动态库。
  • 同名动态库可能在多个路径中存在。

        针对不同的场景,根据链接器的加载逻辑,进行相应的处理。

程序的加载流程

        根据专栏《程序员的自我修养》中的【程序员的自我修养02】初识ELF文件格式-CSDN博客可知,可执行文件的运行流程简述如下:

  1. 操作系统加载ELF的文件头。以检查文件格式、操作权限等属性。
  2. 根据文件头中的段表地址,定位到各个段内容。将各个段映射到虚拟地址中。
  3. 查找依赖的动态库并加载
  4. 进入文件头中的Entry point address。执行业务代码。

        其中链接器查找依赖的动态库并加载,这个流程实质很复杂,后续我会在《程序员的自我修养》专栏中详细介绍。本文我们只关心链接器如何去找动态库

问:链接器如何知道可执行程序依赖哪些动态库呢?

答:ELF文件中有一个段.dynamic。这个段里面保存了动态链接器所需要的基本信息,比如依赖哪些动态库动态链接符号表的位置动态链接重定位表的位置共享对象初始化代码的地址等。我们可以通过readelf -d main命令查看该段内容,如下:

Dynamic section at offset 0xda8 contains 29 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [liba.so]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x000000000000000f (RPATH)              Library rpath: [/home/yihua/]
 0x000000000000000c (INIT)               0x5b8
 0x000000000000000d (FINI)               0x794
 0x0000000000000019 (INIT_ARRAY)         0x200d98
 0x000000000000001b (INIT_ARRAYSZ)       8 (bytes)
 0x000000000000001a (FINI_ARRAY)         0x200da0
 0x000000000000001c (FINI_ARRAYSZ)       8 (bytes)
 0x000000006ffffef5 (GNU_HASH)           0x298
 0x0000000000000005 (STRTAB)             0x3f0
 0x0000000000000006 (SYMTAB)             0x2d0
 0x000000000000000a (STRSZ)              182 (bytes)
 0x000000000000000b (SYMENT)             24 (bytes)
 0x0000000000000015 (DEBUG)              0x0
 0x0000000000000003 (PLTGOT)             0x200fb8
 0x0000000000000002 (PLTRELSZ)           24 (bytes)
 0x0000000000000014 (PLTREL)             RELA
 0x0000000000000017 (JMPREL)             0x5a0
 0x0000000000000007 (RELA)               0x4e0
 0x0000000000000008 (RELASZ)             192 (bytes)
 0x0000000000000009 (RELAENT)            24 (bytes)
 0x0000000000000018 (BIND_NOW)
 0x000000006ffffffb (FLAGS_1)            Flags: NOW PIE
 0x000000006ffffffe (VERNEED)            0x4c0
 0x000000006fffffff (VERNEEDNUM)         1
 0x000000006ffffff0 (VERSYM)             0x4a6
 0x000000006ffffff9 (RELACOUNT)          3
 0x0000000000000000 (NULL)               0x0

        如上可知:main 可执行程序依赖 两个动态库 liba.solibc.so.6。其中黄色字体0x000000000000000f (RPATH)              Library rpath: [/home/yihua/]表示链接器查找动态库的路径,其优先级最高。需要我们关注。其它的参数暂不考虑。

        查看依赖的动态库还可以通过ldd main命令或objdump -x main | grep NEEDED命令。

知道可执行程序依赖哪些动态库口,动态链接器就需要去找这些动态库,查找的方式主要有以下四种。

  • 程序指定路径:rpath
  • 环境变量:LD_LIBRARY_PATH
  • 动态链接器配置文件:/etc/ld.so.conf
  • 系统默认路径:/lib 、 /usr/lib

        其动态链接器加载的顺序分别是rpath --> LD_LIBRARY_PATH -->  /etc/ld.so.conf  --> /lib 、 /usr/lib。

知道四个方式后,我们尝试了解如何使用。本文的示例代码如下:

//a.c
#include<stdio.h>
int a()
{
        printf("i'm liba.a\n");
}
// main.c
#include<stdlib.h>
#include<stdio.h>
extern int a();
int main()
{
        a();
        return 0;
}

编译:

C
yihua@ubuntu:~/test/dynamic$ gcc -FPIC -shared -o liba.so a.c
yihua@ubuntu:~/test/dynamic$ gcc main.c -o main -L. -la

集成:分别将 main 和 liba.so 放入到bin 和lib目录下。

Shell
yihua@ubuntu:~/test/dynamic$ tree
.
├── a.c
├── bin
│   └── main
├── lib
│   └── liba.so
└── main.c

运行:

Shell
yihua@ubuntu:~/test/dynamic$ ./bin/main
./bin/main: error while loading shared libraries: liba.so: cannot open shared object file: No such file or directory

如上错误,是因为动态链接器没有找到liba.so导致的。可通过以下四种方式修复。

-Wl,-rpath

        该方式是通过在编译阶段,修改main 可执行程序中的dynamic段达到目的。可查看当前main的dynamic段内容:

        是没有RPATH参数的。可通过如下编译命令:

yihua@ubuntu:~/test/dynamic$ gcc -FPIC -shared -o liba.so a.c
yihua@ubuntu:~/test/dynamic$ gcc main.c -o main -L. -la -Wl,-rpath=/home/yihua/test/dynamic/lib

再查看dynamic段内容:

运行:

拓展:

        -Wl,-rpath是编译阶段修改可执行程序的rpath参数,但是往往我们在工程中是不太确认最终的集成路径的。可以在集成时,采用chrpath命令,修改可执行程序的rpath参数。如下:

yihua@ubuntu:~/test/dynamic$ chrpath -r ./lib/ bin/main    //修改rpath
bin/main: RPATH=/home/yihua/
bin/main: new RPATH: ./lib/
yihua@ubuntu:~/test/dynamic$ chrpath -l bin/main   //查看rpath
bin/main: RPATH=./lib/

完结撒花~~~

LD_LIBRARY_PATH

        环境变量LD_LIBRARY_PATH是我们最最常用的方式,大部分情况下,我们使用该方式即可。使用方式如下:

LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/yihua//test/dynamic/lib。编译如下:

yihua@ubuntu:~/test/dynamic$ gcc -FPIC -shared -o liba.so a.c
yihua@ubuntu:~/test/dynamic$ gcc main.c -o main -L. -la

集成:分别将 main 和 liba.so 放入到bin 和lib目录下。

运行:

完结撒花~~~

/etc/ld.so.conf

        该配置文件是系统动态链接器加载的配置文件。我们可以重新创建一个窗口(目的是关闭上述的LD_LIBRARY_PATH环境变量)。修改/etc/ld.so.conf文件,如下:

运行:

        发现依然没有找到动态库,这是为什么呢?我们可以通过strace ./bin/main命令查看程序的加载流程。输出如下:

yihua@ubuntu:~/test/dynamic$ strace ./bin/main
execve("./bin/main", ["./bin/main"], 0x7ffdfd030ae0 /* 26 vars */) = 0
brk(NULL)                               = 0x5643c65c3000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=104673, ...}) = 0
mmap(NULL, 104673, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7ff91b2ed000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/tls/haswell/x86_64/liba.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/lib/x86_64-linux-gnu/tls/haswell/x86_64", 0x7fffcd77f4e0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/tls/haswell/liba.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/lib/x86_64-linux-gnu/tls/haswell", 0x7fffcd77f4e0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/tls/x86_64/liba.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/lib/x86_64-linux-gnu/tls/x86_64", 0x7fffcd77f4e0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/tls/liba.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/lib/x86_64-linux-gnu/tls", 0x7fffcd77f4e0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/haswell/x86_64/liba.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/lib/x86_64-linux-gnu/haswell/x86_64", 0x7fffcd77f4e0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/haswell/liba.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/lib/x86_64-linux-gnu/haswell", 0x7fffcd77f4e0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/x86_64/liba.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/lib/x86_64-linux-gnu/x86_64", 0x7fffcd77f4e0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/liba.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/lib/x86_64-linux-gnu", {st_mode=S_IFDIR|0755, st_size=16384, ...}) = 0
openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/tls/haswell/x86_64/liba.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/usr/lib/x86_64-linux-gnu/tls/haswell/x86_64", 0x7fffcd77f4e0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/tls/haswell/liba.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/usr/lib/x86_64-linux-gnu/tls/haswell", 0x7fffcd77f4e0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/tls/x86_64/liba.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/usr/lib/x86_64-linux-gnu/tls/x86_64", 0x7fffcd77f4e0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/tls/liba.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/usr/lib/x86_64-linux-gnu/tls", 0x7fffcd77f4e0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/haswell/x86_64/liba.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/usr/lib/x86_64-linux-gnu/haswell/x86_64", 0x7fffcd77f4e0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/haswell/liba.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/usr/lib/x86_64-linux-gnu/haswell", 0x7fffcd77f4e0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/x86_64/liba.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/usr/lib/x86_64-linux-gnu/x86_64", 0x7fffcd77f4e0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/x86_64-linux-gnu/liba.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/usr/lib/x86_64-linux-gnu", {st_mode=S_IFDIR|0755, st_size=81920, ...}) = 0
openat(AT_FDCWD, "/lib/tls/haswell/x86_64/liba.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/lib/tls/haswell/x86_64", 0x7fffcd77f4e0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/tls/haswell/liba.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/lib/tls/haswell", 0x7fffcd77f4e0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/tls/x86_64/liba.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/lib/tls/x86_64", 0x7fffcd77f4e0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/tls/liba.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/lib/tls", 0x7fffcd77f4e0)        = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/haswell/x86_64/liba.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/lib/haswell/x86_64", 0x7fffcd77f4e0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/haswell/liba.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/lib/haswell", 0x7fffcd77f4e0)    = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/x86_64/liba.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/lib/x86_64", 0x7fffcd77f4e0)     = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/liba.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/lib", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
openat(AT_FDCWD, "/usr/lib/tls/haswell/x86_64/liba.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/usr/lib/tls/haswell/x86_64", 0x7fffcd77f4e0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/tls/haswell/liba.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/usr/lib/tls/haswell", 0x7fffcd77f4e0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/tls/x86_64/liba.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/usr/lib/tls/x86_64", 0x7fffcd77f4e0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/tls/liba.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/usr/lib/tls", 0x7fffcd77f4e0)    = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/haswell/x86_64/liba.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/usr/lib/haswell/x86_64", 0x7fffcd77f4e0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/haswell/liba.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/usr/lib/haswell", 0x7fffcd77f4e0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/x86_64/liba.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/usr/lib/x86_64", 0x7fffcd77f4e0) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/liba.so", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
stat("/usr/lib", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
writev(2, [{iov_base="./bin/main", iov_len=10}, {iov_base=": ", iov_len=2}, {iov_base="error while loading shared libra"..., iov_len=36}, {iov_base=": ", iov_len=2}, {iov_base="liba.so", iov_len=7}, {iov_base=": ", iov_len=2}, {iov_base="cannot open shared object file", iov_len=30}, {iov_base=": ", iov_len=2}, {iov_base="No such file or directory", iov_len=25}, {iov_base="\n", iov_len=1}], 10./bin/main: error while loading shared libraries: liba.so: cannot open shared object file: No such file or directory
) = 117
exit_group(127)                         = ?
+++ exited with 127 +++

        我们从输出结果,可以知道,动态链接器只加载/etc/ld.so.cache配置文件,并没有加载/etc/ld.so.conf配置文件。因此,我们需要通过ldconfig命令更新ld.so.cache文件内容。

如下:

完结撒花~~~

系统默认路径

        系统默认路径即系统存放动态库的地方,一般为/lib、/usr/lib。我们只需要将动态库放到对应的路径下即可。

        重新打开一个窗口,执行如下命令运行:

        完结撒花~~~

总结

        综上所述,我们知道了动态链接库寻找动态库的四种方式,其中:

        系统默认路径和/etc/ld.so.conf需要系统权限,大部分情况是不能进行修改的。若有相应权限,可以优先使用该方式。

        环境变量LD_LIBRARY_PATH可以解决我们大部分场景,比如引文中的第一个场景。但是它并不解决动态库重名的问题。比如引文中的第二个场景。这时,我们就可以采用-Wl,-rpath 编译选项或chrpath修改可执行程序的rpath参数。

        希望本文能够对您有所帮助,还请三连表示支持哦~~~

 若我的内容对您有所帮助,还请关注我的公众号。不定期分享干活,剖析案例,也可以一起讨论分享。
我的宗旨:
踩完您工作中的所有坑并分享给您,让你的工作无bug,人生尽是坦途

  • 31
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

谢艺华

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值