动态链接

来源:互联网 发布:画原型的软件 编辑:程序博客网 时间:2024/05/01 17:13

一、静态链接缺陷

静态链接需要把数据和代码都连接到自己的可执行文件中,运行时系统中有时会存在多个库文件副本从而容易造成内存和磁盘的空间浪费、其次静态链接模块更新困难。为了解决这两个问题需要把程序的模块分隔开,生成相互独立的文件从而不在将他们链接在一起。对目标文件的链接等到运行时在进行,这就是动态链接。

二、动态链接的基本实现

Linux下的动态链接文件为.so,Windows下动态链接文件为.dll

1、当程序被装载时,系统的动态链接器会将程序所需要的所有动态链接库装载到进程的地址空间,并且将程序中所有未决议的符号绑定到相应的动态链接库中,并进行重定位工作。

2、动态库编译

gcc -fPIC -shared -o lib.so lib.c  即得到lib.so动态库(-fPIC 产生地址无关代码)

编译.o目标文件时需要使用 -fPIC -shared参数,如:

gcc -fPIC -c lib.c 编出的.o文件

gcc -shared -o lib.so lib.o lib1.o 编出的lib.so文件才可用于动态库编译。

共享库都是通过使用附加选项 -fpic 或 -fPIC 进行编译,从目标代码产生位置无关的代码

(Position Independent Code,PIC),使用 -shared选项将目标代码放进共享目标库中。

gcc -o p1 p1.c ./lib.so   p1.c中调用lib.so库中函数

gcc -o p2 p2.c ./lib.so   p2.c中调用lib.so库中函数

gcc -o p1 p1.c -L./ -l0 -l1 

使用export查看设置环境变量

​export LD_LIBRARY_PATH=/workteam/liupengyf1/data/work/deal/dynamic

export LD_LIBRARY_PATH=$(pwd)

3、动态链接执行步骤:加载所有有关系的动态库至内存(动态链接器)->链接(符号解析、地址重定位)->程序开始运行​。

链接时如果是静态符号,链接器会按照静态规则将其链接到可执行文件中,如果是动态符号,那么链接器会将其标记为一个动态链接的符号,不对他进行地址重定位,把这个过程留到装载时在进行,那么链接器是如何知道符号是动态符号还是静态符号的呢?这实际上就是链接的时候​需要指定动态库的原因,链接器通过动态库中符号的解析知道哪些符号是动态符号。

4、装载时的重定位

共享对象在编译时不能假设​自己在进程的虚拟地址空间中的位置,而可执行文件基本可以确定自己在进程虚拟地址空间中的位置。因为可执行文件往往是第一个被加载的文件,Linux下一般为0x08040000,Windows下为0x0040000。由于动态共享对象在链接时没有把对绝对地址的引用进行重定位所以系统在装载程序的时候需要对程序的指令和数据中的绝对地址引用进行重定位。装载时重定位是解决动态模块中有绝对地址引用的方法之一。

静态链接使用的是链接时重定位,动态链接使用的是装载时重定位,Linux支持装载时重定位的方法,前面生成共享对象时使用了“-share”“-fPIC”两个参数,如果只使用“-share”生成的共享对象就使用装载时重定位的方法。

5、地址无关代码

但是装载时重定位有很大的一个缺陷即指令部分无法在多个进程间共享,装载时重定位需要修改指令所以没有办法做到一份指令被多个进程共享。数据部分每个进程都有自己的副本所以可以在装载是进行重定位。前面提到的问题一种解决办法就是把指令中需要修改的部分提取出来存放到数据段中,这样指令部分在重定位是不需要变动可以在多进程间共享,数据部分每个进程有自己的副本,这种技术就叫做地址无关代码。

按照引用的方式不同可以分为:

(1)模块内部的函数访问

此种类型的访问时相对地址的访问不需要重定位。​

(2)模块内部的数据访问

此种类型的访问时相对地址的访问不需要重定位。​​

(3)模块外部的函数访问

(4)模块外部的数据访问

编译时使用参数-fPIC或者-fpic可以生成地址无关代码。​这两个参数的功能一样,唯一的区别就是-fpic产生的代码更小,执行更快。地址无关代码与硬件相关,不同的硬件有着不同的实现,-fpic在某些硬件平台上有限制,例如全局符号的数量和代码的长度等,而-fPIC没有这种限制,所以一般使用-fPIC。

判断共享对象文件是否有使用PIC命令:readelf -d foo.so | grep TEXTREL​,如果没有任何输出就没有使用。

​6、延迟绑定

动态链接使用的是装载时重定位所以程序的性能会受到影响,为了解决这个问题ELF采用一种叫延迟绑定的技术,基本思想就是函数第一次使用时才进行绑定(符号查找,重定位等),如果没有用到则不进行重定位。这种技术大大加快了程序启动速度。

7、动态链接器

Linux下,动态链接器实际是一个共享对象,操作系统通过普通共享对象的方式将其映射到进程的地址空间中,操作系统在加载完动态链接器之后将控制权交给动态链接器,动态链接器会开始对可执行文件进行动态链接工作,之后将控制权重新交给操作系统。动态链接器的位置既不是有系统决定也不是由环境变量决定而是由ELF文件自己决定,有一个专门的段叫做“.interp”。使用objdump命令可以查看:

objdump -s a.out

readelf -l a.out | grep interpreter

".dynamic"段中保存了依赖哪些共享对象及动态链接符号表的位置等,使用readelf -d 命令可以查看".dynamic"段内容。

readelf -d a.out

".dynsym"表示动态库的导入导出符号段,只保存动态链接相关的符号。​

​三、动态库的运行时加载

运行时加载是指通过调用系统函数dlopen(),dlsym(),dlerror(),dlclose()等一系列函数程序自己在运行时指定加载某个共享对象,并且在你不需要时将其卸载。

此种方式使用共享库时由于没有符号的导入所以在链接时不需要指定使用的共享库。​

四、链接版本

如果系统中存在两个相同链接名的动态库和静态库,如:libc.a和libc.so.2.0.0那么编译程序时-lc会链接哪个呢?

ld使用“-static”参数时会链接libc.a,使用“-Bdynamic”(默认情况)会链接libc.so.2.0.0

五、共享库的系统路径

Linux一般都遵循FHS标准:

(1)、/lib存放系统最关键最基础的共享库,如动态链接器,C语言的运行库等。

(2)、/usr/lib​存放非系统运行时所需要的关键共享库。

(3)、/usr/local/lib存放一些跟操作系统本身并不十分相关的库​

六、环境变量

Linux中使用LD_LIBRARY_PATH​环境变量可以临时改变某个应用程序查找共享库的路径,不会影响系统中的其他程序。LD_LIBRARY_PATH是由若干个路径组成的环境变量,每个路径之间由冒号隔开,默认情况为空。

为某个进程设置LD_LIBRARY_PATH之后,进程启动之后会首先查找由LD_LIBRARY_PATH指定的目录。

动态链接器加载和查找共享库的顺序依次是:

(1)由环境变量LD_LIBRARY_PATH指定的路径

(2)由路径缓存文件/etc/ld.so.cache指定的路径。​

(3)默认共享目录,先/usr/lib​然后/lib

ld.so.conf是一个文本配置文件,他可能包含其他配置文件,这些配置文件包含着路径信息。链接器查找共享库时都应该去遍历这些目录,但是为了提高效率和速度Linux将所有这些路径下的共享库的符号链接集中存放到ld.so.cache文件里面,这样动态链接器查找文件时只需要在文件ld.so.cache中查找。

0 0