链接
链接步骤:符号解析,同节合并,确定地址,修改引用。
7.1编译器驱动程序
7.2静态链接
链接器完成连两个主要任务,1. 符号(函数,全局变量,静态变量)解析,2. 重定位。
符号解析:编译器将定义的符号存放在一个符号表(symbol table)中,将每个符号的引用都与一个确定的符号定义建议关联。
重定位:将多个代码段和数据段合并为一个单独的代码段和数据段,计算每个定义的符号在虚拟地址空间的绝对地址,将执行文件中的符号引用处的地址修改为重定位后地址信息。
7.3目标文件
7.4可重定位目标文件
介绍了该文件格式。
7.5符号和符号表
1. 全局符号:本模块定义,其他模块引用(非static函数,非static全局变量)。
2. 外部符号:其他模块定义,本模块引用(其他模块定义的非static函数,非static全局变量)。
3. 局部符号:定义在本模块,本模块到处可以使用的独有符号(static函数,static全局变量)。
有趣的是,使用C静态属性定义的本地过程变量不会在堆栈上进行管理。 相反,编译器为这变量在.data或.bss分配每个定义的空间,并在符号表中使用唯一名称创建本地链接器符号。【COMMON 未初始化的全局变量 .bss 未初始化静态变量,以及初始化为0的全局或静态变量。】
学会利用static对其他模块隐藏变量和函数。
7.6符号解析
开始E、U、D为空,首先扫描main.o,把它加入E, 同时把myfun1加入U,main加入D。接着扫描到 mylib.a,将U中所有符号(本例中为myfunc1)与 mylib.a中所有目标模块(myproc1.o和myproc2.o )依次匹配,发现在myproc1.o中定义了myfunc1 ,故myproc1.o加入E,myfunc1从U转移到D。在 myproc1.o中发现还有未解析符号printf,将其加到 U。不断在mylib.a的各模块上进行迭代以匹配U中的 符号,直到U、D都不再变化。此时U中只有一个未解 析符号printf,而D中有main和myfunc1。因为模块 myproc2.o没有被加入E中,因而它被丢弃。接着,扫描默认的库 文件libc.a,发现其目 标模块printf.o定义了 printf,于是printf也 从U移到D,并将 printf.o加入E,同时 把它定义的所有符号 加入D,而所有未解 析符号加入U。 处理完libc.a时,U一定是空的。
符号解析后的结果是什么?
E中有main.o和swap.o两个模块!D中有所有定义的符号! 在main.o和swap.o的重定位条目中有重定位信息,反映符号引用的位置、绑定的定义符号名、重定位类型
7.6.1链接器如何解析多重定义的全局符号
函数和初始化的全局变量是强符号,未初始化的全局变量是弱符号。
7.6.2与静态库链接
7.6.3链接器如何使用静态库来解析引用
7.7重定位
符号解析完成后,可进行重定位工作,分三步
• 合并相同的节
– 将集合E的所有目标模块中相同的节合并成新节
例如,所有.text节合并作为可执行文件中的.text节
• 对定义符号进行重定位(确定地址)
– 确定新节中所有定义符号在虚拟地址空间中的地址
例如,为函数确定首地址,进而确定每条指令的地址,为变量确
定首地址
– 完成这一步后,每条指令和每个全局或局部变量都可确定地址
• 对引用符号进行重定位(确定地址)
– 修改.text节和.data节中对每个符号的引用(地址)
需要用到在.rel_data和.rel_text节中保存的重定位信息
重定位节和符号定义。
重定位节中的符号引用。
7.7.1重定位条目
7.7.2重定位符号引用
7.8可执行目标文件
7.9加载可执行目标文件
7.10动态链接共享库
动态链接可以按以下两种方式进行:
• 在第一次加载并运行时进行 (load-time linking). – Linux通常由动态链接器(ld-linux.so)自动处理
– 标准C库 (libc.so) 通常按这种方式动态被链接
• 在已经开始运行后进行(run-time linking). – 在Linux中,通过调用 dlopen()等接口来实现
• 分发软件包、构建高性能Web服务器等
优点
在内存中只有一个备份,被所有进程共享,节省内存空间
一个共享库目标文件被所有程序共享链接,节省磁盘空间
共享库升级时,被自动加载到内存和程序动态链接,使用方便
共享库可分模块、独立、用不同编程语言进行开发,效率高
第三方开发的共享库可作为程序插件,使程序功能易于扩展
7.11从应用程序中加载和链接共享库
7.12位置无关代码
(1) 模块内部函数调用或跳转
• 调用或跳转源与目的地都在同一个模块,相对位置固定,只要用相对偏移寻址即可
• 无需动态链接器进行重定位
(2) 模块内部数据引用
• .data节与.text节之间的相对位置确定,任何引用局部符号的指令与该符号之间的距离是一个常数
(3) 模块外数据的引用
• 引用其他模块的全局变量,无法确定相对距离
• 在.data节开始处设置一个指针数组(全局偏移表,GOT),指针可指向一个全局变量
• GOT与引用数据的指令之间相对距离固定
• 编译器为GOT每一项生成一个重定位项(如.rel节…)
• 加载时,动态链接器对GOT中各项进行重定位,填入
两个缺点:多用了四条指令,多了GOT(需要多一个寄存器存储GOT中的符号地址,容易造成寄存器溢出)
(4) 模块间调用、跳转
• 方法一:类似于(3),在GOT中加一个项(指针),用于指向目标函数的首地址(如&ext)
• 动态加载时,填入目标函数的首地址
• 多用三条指令并额外多用一个寄存器(如EBX)
方法二:延迟绑定
不在加载时重定位,而延迟到第一次函数调用时,需要用 GOT和PLT(Procedure linkage Table, 过程链接表)
7.13库打桩机制
7.13.1编译时打桩
7.13.2链接时打桩
7.13.3运行时打桩
7.14处理目标文件的工具
7.15小结