静态链接主要的过程如下

  1. 空间和地址分配
  2. 符号解析和重定位
  3. COMMON 块
  4. 静态库链接
  5. 链接过程控制

案例代码

  1. // a.c
  2. extern int shared;
  3. int main()
  4. {
  5. int a = 100;
  6. swap(&a, &shared);
  7. }
  8. // b.c
  9. int shared = 1;
  10. void swap(int *a, int *b)
  11. {
  12. *a ^= *b ^= *a ^= *b;
  13. }
  14. // 将上述两个文件进行编译会得到两个 .o 文件
  15. gcc -c a.c b.c

空间和地址分配

链接器作用:将多个目标文件加工后合并成一个输出文件
问题:多个输入文件中的空间是如何分配到?

按序叠加

  • 将后面的目标文件叠加到前面的目标文件中
  • 存在的问题是会有很多零散的段,此时会比较浪费空间,因为每一个段都有地址和空间对齐要求
  • 实际并不使用这种方法

    相似段合并

  • 同一个类型的段放在一起

  • 使用相似段合并到链接器一般都是使用两步链接的方法,即链接过程分成两步
    • 空间和地址分配:扫描所有目标文件,获取每个段都长度,属性和位置,并且将输入的目标文件的符号表中的所有符号引用收集起来,统一放在一个全局符号表中
    • 符号解析和重定位:使用第一步收集的信息,读取文件中段的数据、重定位信息,并且进行符号解析和重定位,调整代码中的地址等

下面使用链接器命令来链接两个目标文件

  1. // -e main 表示将main函数作为程序的入口,-o ab 表示链接输出文件名为ab,默认为a.out
  2. ld a.o b.o -e main -o ab

查看合并前后的虚拟地址
image.png
我们只看 .text 段,也可以看出来是进行段合并
image.png
问题:从上面可以看到,在合并后的可执行文件 ab 中,虚拟地址不是从 0 开始,实际开始地址是 00000000004000e8 那么为什么不是从0开始呢?
todo 这个问题在可执行文件的装载与进程中解决

符号地址的确定

  • 经过前一步的虚拟地址分配,已经可以知道每一个段的大小和起始地址
  • 段中每一个符号的相对位置都是确定的
  • 计算符号的地址就是段基址+偏移量

链接器的重定位

首先反汇编 a.o
image.png
再看链接后的 ab 可执行文件
image.png

问题:链接器是如何知道哪些指令的地址是需要被调整的呢?

在 ELF 文件中,是有一个重定位表的结构专门用来保存这些与重定位相关的信息,在ELF 文件中重定位表是一个单独的段,叫做重定位段

查看重定位表
image.png