两个链接,写的都非常的详细、易理解:
链接(1)-- 偏重原理的解释:
a. 基础部分
- 源代码经过编译器的编译以及链接器的链接过程才会生成可执行文件,仅经过编译生成的文件被称为目标文件(.o文件)。编译过程会将源代码使用的高级程序语言转换为机器语言,链接过程会解析未定义的符号引用,将目标文件中的占位符替换为符号的地址,有时也涉及到重定位工作。因此目标文件和最终可执行文件的内容结构非常类似,所以目标文件和可执行文件都以ELF格式进行存储,除此之外,动态链接库(.so)和静态链接库(.a)也按照ELF文件的格式存储。
- ELF文件的代码段(.code或.text),存放编译后的机器指令;数据段存放全局变量和静态局部变量(其他局部变量储存在函数的堆栈中),常见的数据段有.data存放初始化的变量,.rodata存放只读数据,.comment存放注释信息,.bss存放未初始化的变量,仅作为预留位置,不占空间。
- 在ELF文件中占据空间的section只有.text,.data,.rodata,.comment四个。
b. 动态链接
- 静态链接是在链接过程中,将目标文件和需要的静态链接库文件放到一个输出文件中形成最终的可执行文件,这样会导致空间的极大浪费,而且更新也比较麻烦。为了解决这个问题,提出了动态链接。
- 动态链接的基本思路就是将目标文件和链接库分开,只有在程序要运行时,才将目标文件依赖的链接库装载到进程空间,进行链接。ELF动态链接文件.so。
- 动态链接在程序装载时进行操作,对性能有一定的影响,引出了动态链接的优化方法。
- 优化一
- 装载时重定位
- 在链接时,对所有绝对地址的引用不做重定位,将其推迟到装载完成时再完成。当模块装载地址确定,再根据偏移值对所有的绝对地址引用进行重定位,更新重定位表。该方法解决了动态链接中的绝对地址引用问题,但带来了指令部分无法在多个进程间共享的问题。
- 地址无关代码技术(PIC)
- 该技术解决上述问题,将指令和其中需要修改的部分分开,指令部分不随装载地址的改变而改变,其中需要修改的部分放在数据部分,这样指令部分可以保持不变,数据部分可以在每个进程中拥有一个副本,互不干扰。
- 分析模块中各种类型的地址引用方式,把共享对象模块中地址引用按照是否跨模块分为两类:模块内部引用和模块外部引用;按照不同的引用方式又可以分为指令引用和数据访问。两两组合可以组合为四种类型。
- GOT(Global Offset Table,全局偏移表)
- 对于模块外部引用的情况,包含数据引用和函数调用,ELF在数据段中建立一个指向这些变量或函数的指针数据,就称为GOT。当指令访问变量时,程序根据当前PC值和偏移找到GOT中相应的位置,储存着目标变量的地址,进行间接引用。当指令调用函数时,程序也会去找GOT中的相应位置,储存目标函数的地址,进行间接跳转。
- 装载时重定位
- 优化二
- 延迟绑定
- 在动态链接时,程序模块之间存在大量的函数引用,解决函数引用的符号查找以及重定位需要耗费大量的时间,并且因为有些函数自始至终可能不会被用到,也做了一些无用功。因此可以等函数第一次被调用到时,再进行绑定。那么不被调用的函数就不需要完成绑定操作了。延迟绑定就需要用到PLT项。
- PLT(Procedure Linkage Table,程序链接表)
- 动态链接器使用_dl_runtime_reolve()函数完成地址绑定工作。程序调用外部模块的某个函数时,不直接通过GOT进行跳转,而是通过PLT项进行跳转,每个外部函数在PLT中都有对应的项。
- 当程序调用一个外部函数时,首先会跳转到它的PLT项的地址,如果该函数是第一次被调用,那么就会调用_dl_runtime_reolve()函数进行地址绑定,并将函数的真实地址填入GOT表中。当该函数之后再次被调用时,跳转到PLT项后,就可以直接进行调用。(PLT的具体实现参照链接)
- 实际上,ELF将GOT拆分成了两个表:.got和.got.plt,前者保存全局变量引用的地址,后者保存函数引用的地址。
- 延迟绑定
链接(2)-- 通过GDB的实际操作分析GOT和PLT:
- 是以上程序进行外部函数调用时,使用PLT项进行跳转的实例。