编译原理-链接程序设计
书中设计的是静态链接器。
链接器的工作内容是把多个可重定位文件正确的合并为可执行文件,不是简单的物理合并。
合并内容如下:
- 合并同类的段
- 为段分配合适的地址空间
- 分析目标文件符号定义的完整性
- 对符号的地址进行解析
- 重定位
地址空间分配
汇编器生成的目标文件,不知道数据段和代码段的虚拟地址, 因此他们的段地址都设置为0。
链接器需要为他们分配端的基址。
链接器按照目标文件的输入顺序扫描文件信息,从每个文件的段表中提取出各个文件的代码段和数据段信息。
链接器将所有文件的同类型端合并,按照代码段、数据段、.bss端的顺序一次决定每个段的起始地址,此时需要考虑段间对其产生的偏移,以及特殊的地址计算方式。
如: a.o,b.o两个目标文件
a.o:数据段大小 0x08字节
代码段大小0x04字节
b.o:数据段大小0x04字节
代码段大小0x21字节
链接器处理后,可执行文件ab:数据段大小 0x0c字节
代码段大小0x6d字节(其中对齐b.o代码段消耗2字节)
符号解析
如果说地址空间分配是为段指定地址的话,符号解析就是为段内的符号指定地址。
一个汇编程序内的符号可以分为两类
- 内部符号:自身定义的符号
- 外部符号:来自其他文件定义的符号,本地文件只是使用该符号。
内部符号在其端内的偏移地址是确定的,当段的起始地址指定完毕后,内部符号的地址按照如下方式计算
符号地址=符号所在的段基地址+符号所在段内偏移
外部符号的地址在本地文件中是无法确定的,但是外部符号也是定义在一个外部文件中,在这个外部文件中,外部符号就是这个外部文件的内部符号了。它的基地址的计算方式也满足上面的式子。使用该外部符号的本地文件需要的也是这个地址。
在重定位文件内,符号表记录了符号的所有信息。
此时对于本地文件中的内部符号,和外部文件的外部符号有不同的标记方式:
- 内部符号直接记录符号的段内偏移
- 外部符号直接标识为“未定义”
那么这个“未定义”在什么时候被修改为正确的地址呢?
当链接器扫描到该外部符号所在的外部文件时,此时这个“未定义”会被替换为正确的符号地址。
最终保证所有目标文件内的符号信息,无论是内部还是外部符号的地址都是正确的。
链接器在扫描重定位目标文件的时候,会动态的维护两个目标集合。
- 一个是记录所有文件定义的全局符号集合
Export
- 一个是记录所有文件使用未定义符号的集合
Import
,该集合内的所有符号都来源于其他文件。
所有目标文件扫描完毕后,所有目标文件符号表内的所有符号都会获得完整、正确的符号地址信息。
重定位
重定位是链接器中最关键的操作。
从本质上来说就是地址修正。
因为目标文件在链接之前无法知道自己所用符号的虚拟地址信息,因此导致依赖这些符号的数据定义或者指令信息缺失。
链接器需要清除的事情:
- 在哪里修改二进制?(重定位地址)
- 用什么信息修改?(重定位符号)
- 按照怎样的方式修改?(重定位类型)
重定位地址
重定位地址在重定位表中没有记录,因为在重定位目标文件内,段地址还没确定下来,它只记录了重定位位置所在的段内偏移。在地址空间分配结束后,可以通过以下公式计算得出:
重定位地址 = 重定位位置所在段基址 + 重定位位置的段内偏移
重定位符号
重定位符号记录着被指令或者数据使用的符号信息。在符号解析完成后,重定位符号地址就已经确定了。
重定位类型
重定位类型决定修改二进制信息的方式
- 绝对地址重定位
- 相对地址重定位