Skip to content
On this page

动态库路径问题


软件版本硬件版本更新内容

1. 背景

动态库的使用带来了很多好处,比如大大降低程序所使用磁盘空间,内存空间,这个是曾经系统配置比较低的时代意义重大,还有很多其他的好处,请自行查阅。但是给程序员编译,运行程序都带来一些困惑,这里很通过这片文章将动态库路径问题彻底说明白。

2. 编译时

2.1 链接器的默认搜索路径

编译时使用ld来连接,链接器是根据连接脚本来工作的,所以它搜索动态库时也是根据链接脚本里SEARCH_DIR指定的地址来搜索需要连接的库。链接脚本在ubuntu中位于/usr/lib/x86_64-linux-gnu/ldscripts

c
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机器的配置如下

cmd
=> 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

  1. rpath 指定的路径不会作为ld搜索路径
  2. ld-2.31.so从指定没有找到,也会从默认的搜索路径中找

这块关系比较复杂,通过一个示例来说明:

源文件 add.c

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

c
#ifndef __ADD_H__
#define __ADD_H__

int add(int a, int b);

#endif /* ADD_H */

再编译测试程序 test.c

c
#ifndef __ADD_H__
#define __ADD_H__

int add(int a, int b);

#endif /* ADD_H */

此时的文件结构如下:

c
=> ls
add.c  add.h  lib  test.c

  1. 通过gcc -o test test.c -ladd -Wl,--rpath=./lib -L ./lib 编译
c
=> 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

  1. 通过gcc -o test test.c -ladd -Wl,--rpath=./lib -L ./lib 编译
c
=> 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指定路径最好使用绝对路径,这样库文件不会因为程序的执行路径找一到库。


提示

欢迎评论、探讨,如果发现错误请指正。转载请注明出处! 探索者


Released under the MIT License.