两步链接

  • 空间与地址分配

扫描所有的输⼊⽬标⽂件,并且获得它们的各个段的⻓度、属性和位置,并且将输⼊⽬标⽂件中的符号表中所有的符号定义和符号引⽤收集起来,统⼀放到⼀个全局符号表。这⼀步中,链接器将能够获得所有输⼊⽬标⽂件的段⻓度,并且将它们合并,计算出输出⽂件中各个段合并后的⻓度与位置,并建⽴映射关系

  • 符号解析与重定位

使⽤上⾯第⼀步中收集到的所有信息,读取输⼊⽂件中段的数据、重定位信息,并且进⾏符号解析与重定位、调整代码中的地址等。事实上第⼆步是链接过程的核⼼,特别是重定位过程

符号地址的确定:在第一步完成后,链接器开始计算各个符号的 虚拟地址。因为各个符号在段内的 相对位置是固定 的,而各个段的地址已经在第一步确定,所以只需加上一个 偏移量 就能够确定 符号地址

空间与地址分配

对于链接器来说,整个链接过程中,它就是将⼏个输⼊⽬标⽂件加⼯后合并成⼀个输出⽂件

链接器为目标文件分配空间与地址,有两种含义

  • 输出的可执行文件的空间(磁盘空间)

  • 装载后的虚拟地址中的虚拟地址空间

分配策略

  • 按序叠加

1

将各个段依次合并,非常简单,但由于每个段都有自己的 地址和空间对齐要求,所以这可能会造成很大的 内存浪费(太零散了)

  • 相似段合并

1

不同目标文件 中相似的段合并在一起

符号解析与重定位

  • 重定位

    在第一步完成后,我们能够确定 符号的虚拟地址 了,但是 符号还并没有解析,简单来说,就是我知道了这个符号的位置,但是他实际的内容还不知道(相当于我们有了指针,但是指针指向的内容还没有确定,所以依旧不能工作)

    这时候,我们就先用 临时地址,比如 全零占位,然后再经过一些 修正过程 填充这些 临时地址,这个就是 重定位

  • 重定位表

    链接器如何知道哪些指令需要进行修正呢?这些指令的哪些部分需要进行修正呢?

    在 ELF 文件中,有一个叫 重定位表 的结构专门用来存储 与重定位相关的信息,通常是 一个或多个

    比如 .text 段有重定位的地方,那么就有一个叫 .rel.text 的段保存了 .text 的重定位表

    每个要被重定位的地⽅叫⼀个重定位⼊⼝,重定位表的结构一般包含了 重定位入口偏移量符号信息

  • 符号解析

    • 符号类型是 GLOBAL 表示符号定义在当前代码段外

    • 符号类型是 LOCAL 表示符号定义在当前代码段内

    • 符号类型是 UND 表示当前目标文件中没有关于他们的 重定位项,所以在链接器扫描完 所有输入的目标文件之后,所有这些未定义的符号都应该在 全局符号表 中找到,否则链接器就 报符号未定义错误

为什么缺少符号的定义会导致链接错误?

重定位过程也伴随着符号的解析过程,每个⽬标⽂件都可能定义⼀些符号,也可能引⽤到定义在其他⽬标⽂件的符号。重定位的过程中,每个重定位的⼊⼝都是对⼀个符号的引⽤,那么当链接器须要对某个符号的引⽤进⾏重定位时,它就要确定这个符号的⽬标地址。这时候链接器就会去查找由所有输⼊⽬标⽂件的符号表组成的全局符号表,找到相应的符号后进⾏重定位。

  • 指令修正方式

不同的处理器指令对于地址的格式和⽅式都不⼀样

我们有时能够通过 指令修正方式 确定 修正地址处的大小

x86平台下可以分为以下两种:

  1. 绝对寻址 修正后的地址为该符号的实际地址

  2. 相对寻址 修正后的地址为符号距离被修正位置的地址差

静态库链接

⼀个静态库可以简单地看成⼀组⽬标⽂件的集合,即很多⽬标⽂件经过压缩打包后形成的⼀个⽂件

可以使用 ar -t 来查看一个静态库包含的目标文件

1

还可以通过 objdump -t 配合 grep 查找某个符号对应的目标文件了

在通过 ar -x 将某个静态库解压成原来的目标文件

综合以上手段,你就能够自己进行链接了

这是开玩笑的,因为这些静态库的体量一般都很大,然后链接的过程很复杂,不仅仅只是几个目标文件,哪怕是一个 helloworld 的程序也是一样的

 posted on 2025-05-29 12:02  Dylaris  阅读(35)  评论(0)    收藏  举报