csapp3e 第七章 链接

本文详细介绍了链接的步骤,包括符号解析和重定位。符号解析涉及编译器如何处理全局、外部和局部符号,以及如何解决多重定义的全局符号问题。重定位则涉及合并节、确定符号地址以及修改引用地址。文章还讨论了动态链接共享库的优势,以及位置无关代码在模块间调用和数据引用中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

链接

       链接步骤:符号解析,同节合并,确定地址,修改引用。

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小结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值