文章目录
  1. 动态链接的实现
    1. 动态链接器自举
    2. 装载共享对象
    3. 重定位与初始化
  2. 显式运行时链接

动态链接的实现

动态链接器自举

动态链接器是一个特殊的共享对象!首先,动态链接器本身不能依赖于其他任何共享对象;其次,共享对象本身所需要的全局&静态变量&函数地址的重定位工作由它自身完成。为了完成第二个目标,动态链接器必须使用一段非常精巧的代码,这段启动代码便称为自举

自举代码在动态链接器入口处,自举代码首先找到自身的GOT,而GOT的第一项为.dynamic段,自举代码便可以通过.dynamic段的信息获得动态链接器本身的重定位表与符号表等,从而得到动态链接器本身的重定位入口,将重定位表中所有项重定位。从这一步开始,动态链接器的代码中才能使用自己的全局&静态变量以及调用函数。

装载共享对象

动态链接器完成自举之后,动态链接器将可执行文件与自身的符号表合并到一个符号表中——全局符号表。然后链接器根据.dynamic段的内容开始寻找可执行文件所依赖的共享对象,如果依赖的共享对象还依赖其他共享对象,同样将其装载到进程的地址空间。具体的装载算法可使用深度优先或广度优先。

在装载共享对象时存在一个全局符号介入的问题,当一个符号需要被加入到全局符号表中时,如果相同的符号名已经存在,则后加入的符号会被忽略。

由于编译器在编译时将模块内部的函数当作模块外部函数处理,因此,若希望加快函数调用速度,可以对函数加上static修饰,将函数作为编译单元私有函数,使用第一种地址引用方式。

重定位与初始化

有了全局符号表,链接器遍历可执行文件与每个共享对象的重定位表,将GOT&PLT中每个需要重定位的位置进行修正。重定位的操作与静态链接的重定位操作相同。重定位之后,如果共享对象有.init段,则动态链接器会执行.init段中的代码;相应地,共享对象还会有.finit段,当进程退出时会执行.finit段中的代码。接下来,动态链接器将控制权交给可执行文件。

动态链接器作为共享对象,也可以作为可执行文件运行。动态链接器是静态链接的。

显式运行时链接

显式运行时链接是一种比动态链接更加灵活的模块加载方式,它让程序在运行时自己控制加载指定的模块,并在不需要某模块时将其卸载。此时的模块称为动态装载库,它与共享对象并无差别。

动态链接由动态链接器完成,而显式运行时链接需要由程序来完成,因此动态链接器提供了一系列API来支持程序完成动态装载库的装载。

  • dlopen:打开一个动态库,并将其加载到进程的地址空间;
  • dlsym:找到动态装载库的符号,并返回符号的值;
  • dlerror:每次调用dlopen、dlsym、dlclose之后,都需要调用dlerror来判断上一次调用是否成功;
  • dlclose:卸载加载的模块,事实上,操作系统会为每个模块维护一个计数器,每有一个进程调用dlopen加载该模块,该模块对应的计数器加1;每有一个进程调用dlclose卸载该模块,该模块对应的计数器减1;只有当模块对应的计数器为0时才会真的被卸载,动态链接器首先执行.finit段中的代码,然后从进程的全局符号表中删除该模块中的符号,取消进程地址空间与模块的映射关系,最后关闭模块文件。