请稍侯

动态链接深入详解

03 December 2020

动态链接深入详解

动态链接:为了解决内存及磁盘空间的浪费和更新困难的问题,把程序的链接过程推迟到运行时再进行,就是动态链接的基本思想。让程序在运行时可以动态地选择加载各种程序模块,实现程序功能的扩展,另一方面也可加强程序的兼容的。

动态链接运行时地址空间分布:对于动态链接程序来说,除了可执行文件本身之外,还有它所依赖的共享目标文件。其共享对象的最终装载地址在编译时是不确定的,而是在装载时,装载器根据当前地址空间的空闲情况动态分配一块足够大小的虚拟地址空间给相应的共享对象。

编译一个共享对象,-shared表示生生共享对象,-fPIC表示产生地址无关代码;

gcc -fPIC -shared -o hello.so hello.c 

装载时重定位:我们知道静态链接是链接时重定位,但动态链接时装载进重定位,在Windows中,这种装载时重定位又叫基址重置。

模块中的地址引用方式:可分类模块内引用和模块外引用;也可分为指令引用和数据访问。所以就可以分为以下四种情况:

  • 模块内部的函数调用、跳转等;
  • 模块内部的数据访问,如模块中定义的全局变量、静态变量;
  • 模块外部的函数调用、跳转等;
  • 模块外部的数据访问,如模块中定义的全局变量;

GOT(Global Offset Table)全局偏移表:在模块间的数据访问中,因为其他模块的全局变量是与模块装载地址有关的,ELF的做法是在数据段里面建立一个指向这些变量的指针函数,也叫全局偏移表,当代码需要引用该全局变量时,可以通过GOT中相对应的项间接引用。

使用objdump来查看 GOT的位置objdump -h pic.so;查看动态链接时重定位项:objdump -R pic.so

区分一个DSO是否为PIC

readelf -d foo.so | grep TEXTREL

查看可执行文件所需要的动态链接器的路径:

readelf -l a.out | grep interpreter

使用readelf查盾.dynamic段的内容:readelf -d Lib.so

查看程序或共享库的依赖:ldd program1

共享模块的全局变量:ELF共享库在编译时,默认都把定义在模块内部的全局变量当作定义在其他模块的全局变量,这样就通过GOT来实现变量的访问。即共享模块被装载时,如果某个全局变量在可执行文件中有副本,那么动态链接器就会把GOT中相应地址指向该副本,这样该变量在运行时实际上最终就只有一个实例。

数据段地址无关性:对于共享对象来说,如果数据段中有绝对地址引用,那编译器和链接器就会产生一个重定位表,并且这个表中包含了相对寻址类型的重定位入口。

查看ELF文件动态符号表及它的哈希表:readelf -sD Lib.so

-Xlinker -rpath ./表示链接器在当前路径寻找共享对象

gcc main.c b1.so b2.so -o main -Xlinker -rpath ./ 

延迟绑定(Procedure Linage Table ,PLT):基本思想时当函数第一次被用到时才进行绑定(符号查找、重定位等),如果没有用到则不进行绑定。

动态链接相关的结构:.interp段、.dynamic段、动态符号表、动态链接重定位表、进程堆栈里的辅助信息数组。

动态链接的步骤:动态链接器自举,装载共享对象,最后是重定位与初始化。

Linux动态链接器的实现:它本身是一个共享对象,是个非常特殊的共享对象,还是个可执行的程序。

dlopen():用来打开一个动态库; dlsym():用来查找所需要的符号; dlerror():用来判断上一次调用是否成功; dlclose():用来将一个已经加载的模块卸载;