动态库路径问题
软件版本 | 硬件版本 | 更新内容 |
---|---|---|
1. 背景
动态库的使用带来了很多好处,比如大大降低程序所使用磁盘空间,内存空间,这个是曾经系统配置比较低的时代意义重大,还有很多其他的好处,请自行查阅。但是给程序员编译,运行程序都带来一些困惑,这里很通过这片文章将动态库路径问题彻底说明白。
2. 编译时
2.1 链接器的默认搜索路径
编译时使用ld
来连接,链接器是根据连接脚本来工作的,所以它搜索动态库时也是根据链接脚本里SEARCH_DIR
指定的地址来搜索需要连接的库。链接脚本在ubuntu中位于/usr/lib/x86_64-linux-gnu/ldscripts
ld --verbose | grep SEARCH | sed 's/;[ ]/\n/g'
SEARCH_DIR("=/usr/local/lib/x86_64-linux-gnu")
SEARCH_DIR("=/lib/x86_64-linux-gnu")
SEARCH_DIR("=/usr/lib/x86_64-linux-gnu")
SEARCH_DIR("=/usr/lib/x86_64-linux-gnu64")
SEARCH_DIR("=/usr/local/lib64")
SEARCH_DIR("=/lib64")
SEARCH_DIR("=/usr/lib64")
SEARCH_DIR("=/usr/local/lib")
SEARCH_DIR("=/lib")
SEARCH_DIR("=/usr/lib")
SEARCH_DIR("=/usr/x86_64-linux-gnu/lib64")
SEARCH_DIR("=/usr/x86_64-linux-gnu/lib");
2.2 链接时指定的的搜索路径
2.2.1 通过-L
通过gcc
编译时可以通过-L
的增加库的搜索路径,这里引用gcc
帮忙文档的话如下:
-Ldir
Add directory dir to the list of directories to be searched for -l.
这里增加的库搜索路径不是给gcc
使用的,而是给ld
使用的。
2.2.1 通过LD_RUN_PATH
通过设置LD_RUN_PATH
环境变量来添加库搜索路径。
3. 运行时
3.1 运行时搜索库的路径
运行时,库的装载分为显式装载和隐式装载,显示装载是由程序本身通过通过动态库接口dlopen dlsys
来完成的,不存在路径问题,我们这主要讨论隐式的装载,隐式装载是由/lib/x86_64-linux-gnu/ld-2.31.so
完成的。
对于一个ELF
文件,如果存在.interp
段那么就是动态连接的程序,在.interp
存放的就是动态链接器,如果没有.interp
那么就是静态连接的程序。 在我们在linux
下执行一个动态连接的程序时,内核根据不关心些ELF
是否是一个可执行的程序,加载完ELF
文件后,内核会分析ELF
是否存在.interp
段,如果不存在会跳到ELF的e_entry
执行,这就是静态链接程序的执行,如果存在,会将.interp
段的链接器映射到进程地址空间,再跳到动态链接器的e_entry
地址执行,动态链接器会根据.dynamic
段来加载动态库。
那么动态连接器会从那些路径搜索动态库?会从/etc/ld.so.conf
指定的路径的去搜索。我们ubuntu
机器的配置如下
=> cat /etc/ld.so.conf
include /etc/ld.so.conf.d/*.conf
=> ls /etc/ld.so.conf.d/*
/etc/ld.so.conf.d/fakeroot-x86_64-linux-gnu.conf /etc/ld.so.conf.d/x86_64-linux-gnu.conf /etc/ld.so.conf.d/zz_x32-biarch-compat.conf
/etc/ld.so.conf.d/libc.conf /etc/ld.so.conf.d/zz_i386-biarch-compat.conf
=> cat /etc/ld.so.conf.d/x86_64-linux-gnu.conf
# Multiarch support
/usr/local/lib/x86_64-linux-gnu
/lib/x86_64-linux-gnu
/usr/lib/x86_64-linux-gnu
3.2 使用LD_LIBRARY_PATH
使用LD_LIBRARY_PATH
可以增加/lib/x86_64-linux-gnu/ld-2.31.so
的搜索路径。
4. 编译时和运行时动态库路径关系
按目前了解的信息,除了一个特殊情况
基本不存在关系,对于链接时,只要ld
能从搜索库的路径找到库就可以编译通过,它不关系运行时是否可以找到,运行时也一样,只要ld-2.31.so
可以从/etc/ld.so.conf
指定的路径中找到就可以正确运行。
特殊情况
是,在链接是ld
使用rpath
指定的一个库的路径然后时行连接,执行运行时ld-2.31.so
,会从指定的那个路径查找库,
TIP
rpath
指定的路径不会作为ld
搜索路径ld-2.31.so
从指定没有找到,也会从默认的搜索路径中找
这块关系比较复杂,通过一个示例来说明:
源文件 add.c
int add(int a, int b){
return a + b;
}
我们把add.c
通过gcc -o libadd.so -shared -fPIC ./add.c
编译成libadd.so
, 然后放到lib
下。
再编写接口文件 add.h
#ifndef __ADD_H__
#define __ADD_H__
int add(int a, int b);
#endif /* ADD_H */
再编译测试程序 test.c
#ifndef __ADD_H__
#define __ADD_H__
int add(int a, int b);
#endif /* ADD_H */
此时的文件结构如下:
=> ls
add.c add.h lib test.c
- 通过
gcc -o test test.c -ladd -Wl,--rpath=./lib -L ./lib
编译
=> readelf -d ./test
Dynamic section at offset 0x2db0 contains 28 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libadd.so]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000000c (INIT) 0x1000
0x000000000000000d (FINI) 0x1238
0x0000000000000019 (INIT_ARRAY) 0x3da0
0x000000000000001b (INIT_ARRAYSZ) 8 (bytes)
0x000000000000001a (FINI_ARRAY) 0x3da8
0x000000000000001c (FINI_ARRAYSZ) 8 (bytes)
0x000000006ffffef5 (GNU_HASH) 0x3a0
0x0000000000000005 (STRTAB) 0x488
0x0000000000000006 (SYMTAB) 0x3c8
0x000000000000000a (STRSZ) 146 (bytes)
0x000000000000000b (SYMENT) 24 (bytes)
0x0000000000000015 (DEBUG) 0x0
0x0000000000000003 (PLTGOT) 0x3fb0
0x0000000000000002 (PLTRELSZ) 48 (bytes)
0x0000000000000014 (PLTREL) RELA
0x0000000000000017 (JMPREL) 0x610
0x0000000000000007 (RELA) 0x550
0x0000000000000008 (RELASZ) 192 (bytes)
0x0000000000000009 (RELAENT) 24 (bytes)
0x000000000000001e (FLAGS) BIND_NOW
0x000000006ffffffb (FLAGS_1) Flags: NOW PIE
0x000000006ffffffe (VERNEED) 0x530
0x000000006fffffff (VERNEEDNUM) 1
0x000000006ffffff0 (VERSYM) 0x51a
0x000000006ffffff9 (RELACOUNT) 3
0x0000000000000000 (NULL) 0x0
- 通过
gcc -o test test.c -ladd -Wl,--rpath=./lib -L ./lib
编译
=> readelf -d ./test
Dynamic section at offset 0x2da0 contains 29 entries:
Tag Type Name/Value
0x0000000000000001 (NEEDED) Shared library: [libadd.so]
0x0000000000000001 (NEEDED) Shared library: [libc.so.6]
0x000000000000001d (RUNPATH) Library runpath: [./lib]
0x000000000000000c (INIT) 0x1000
0x000000000000000d (FINI) 0x1238
0x0000000000000019 (INIT_ARRAY) 0x3d90
0x000000000000001b (INIT_ARRAYSZ) 8 (bytes)
0x000000000000001a (FINI_ARRAY) 0x3d98
0x000000000000001c (FINI_ARRAYSZ) 8 (bytes)
0x000000006ffffef5 (GNU_HASH) 0x3a0
0x0000000000000005 (STRTAB) 0x488
0x0000000000000006 (SYMTAB) 0x3c8
0x000000000000000a (STRSZ) 152 (bytes)
0x000000000000000b (SYMENT) 24 (bytes)
0x0000000000000015 (DEBUG) 0x0
0x0000000000000003 (PLTGOT) 0x3fb0
0x0000000000000002 (PLTRELSZ) 48 (bytes)
0x0000000000000014 (PLTREL) RELA
0x0000000000000017 (JMPREL) 0x610
0x0000000000000007 (RELA) 0x550
0x0000000000000008 (RELASZ) 192 (bytes)
0x0000000000000009 (RELAENT) 24 (bytes)
0x000000000000001e (FLAGS) BIND_NOW
0x000000006ffffffb (FLAGS_1) Flags: NOW PIE
0x000000006ffffffe (VERNEED) 0x530
0x000000006fffffff (VERNEEDNUM) 1
0x000000006ffffff0 (VERSYM) 0x520
0x000000006ffffff9 (RELACOUNT) 3
0x0000000000000000 (NULL) 0x0
对比两种编译方式readelf -d test
的输出,使用-rpath
在ELF文件中会存在一个 0x000000000000001d (RUNPATH) Library runpath: [./lib]
,也就是增加了一个搜索路径。
TIP
使用rpath
指定路径最好使用绝对路径,这样库文件不会因为程序的执行路径找一到库。
提示
欢迎评论、探讨,如果发现错误请指正。转载请注明出处! 探索者