# 4. 程序的链接

本质:合并相同节

# 4.1 链接的好处

  1. 模块化
    1. 一个程序可以分成很多源程序文件
    2. 可以建立公共函数库
  2. 效率高
    1. 可分开编译
    2. 无需包含共享库所有代码

# 4.2 链接操作的步骤

  1. 符号解析
    1. 程序中有定义和引用的符号 (包括变量和函数等)
    2. 编译器将定义的符号存放在一个符号表 (symbol table) 中
    3. 链接器将每个符号的引用都与一个确定的符号定义建立关联
  2. 重定位
    1. 将多个代码段与数据段分别合并为一个单独的代码段和数据段
    2. 计算每个定义的符号在虚拟地址空间中的绝对地址
    3. 将可执行文件中符号引用处的地址修改为重定位后的地址信息

# 4.3 目标文件

  1. 可重定位目标文件:每个.o 文件代码和数据地址都从 0 开始
  2. 可执行目标文件
  3. 共享库文件 (DLLs)

# 4.4 目标文件格式

目标文件是编译源代码后生成的中间文件,它包含了已编译的机器代码和其他与链接相关的信息。目标文件的格式可以因编译器、操作系统和目标架构的不同而有所变化,以下是几种常见的目标文件格式:

  1. COFF (Common Object File Format): COFF 是一种通用的目标文件格式,广泛用于 UNIX 和 Windows 系统上。它支持存储机器代码、符号表、调试信息和重定位表等。

  2. ELF (Executable and Linkable Format): ELF 是一种在 UNIX 和类 UNIX 系统中广泛使用的目标文件格式。它支持存储机器代码、符号表、调试信息、重定位表和共享库等。ELF 格式具有良好的可扩展性和灵活性,能够适应各种编译和链接需求。

  3. Mach-O (Mach Object): Mach-O 是苹果公司的操作系统 macOS 上使用的目标文件格式。它支持存储机器代码、符号表、调试信息和重定位表等。Mach-O 格式具有多种变体,用于存储可执行文件、共享库和内核扩展等。

  4. PE (Portable Executable): PE 是微软 Windows 操作系统上使用的目标文件格式。它支持存储机器代码、符号表、调试信息、重定位表和资源表等。PE 格式广泛应用于 Windows 可执行文件和动态链接库。

这些目标文件格式都提供了一种标准化的方式来组织和存储编译后的代码和相关信息,以便在链接过程中进行处理。链接器能够根据目标文件的格式来解析其中的数据,并将它们合并到最终的可执行文件或共享库中。目标文件格式的选择通常取决于操作系统和开发工具链的要求,以及所使用的编程语言和目标架构的特定要求。

# 4.5ELF 文件

  1. 链接视图

    • ELF 头:包含文件类型、机器结构等
    • .text 节:编译后的代码部分
    • .data 节:已初始化的全局变量
    • .bss 节:未初始化的全局变量
    • .symtab 节:存放函数和全局变量 (符号表信息),不包括局部变量
  2. 执行视图

# 4.6 可重定位和可执行目标文件区别

可重定位目标文件(Relocatable Object File)和可执行目标文件(Executable Object File)是两种不同类型的目标文件,它们在编译和链接过程中的作用和用途有所不同。

可重定位目标文件是编译器将源代码编译后生成的中间文件,它包含了代码、数据和符号等信息,但还没有进行最终的链接。可重定位目标文件的主要作用是在链接阶段进行符号解析和重定位,将不同的目标文件合并为一个可执行文件或共享对象文件。它可以作为其他目标文件的输入,参与链接过程,解析符号引用,生成最终的可执行文件。

可执行目标文件是链接器将多个可重定位目标文件合并后生成的最终可执行文件。它是可以直接在操作系统上运行的二进制文件,包含了完整的可执行代码、数据和符号表等信息。可执行目标文件可以被加载到内存中,由操作系统的加载器执行,实际运行程序。

总的来说,可重定位目标文件是编译阶段生成的中间文件,用于链接过程中解析符号引用和重定位;而可执行目标文件是链接阶段生成的最终可执行文件,可以被加载和执行。可重定位目标文件与特定平台无关,而可执行目标文件是针对特定平台和操作系统的。

# 4.7 符号和符号解析

  1. 全局符号:非静态函数和非静态全局变量,模块内部的全局符号
  2. 外部符号:extern,外部定义的全局符号
  3. 局部符号 (本地符号): 静态函数和静态变量

# 4.8 全局符号的强弱特性

  1. 函数名和已初始化的全局变量名是强符号
  2. 未初始化的全局变量名是弱符号

# 4.9 链接器符号解析过程

E 将被合并以组成可执行文件的所有目标文件集合
U 当前所有未解析的引用符号的集合
D 当前所有定义符号的集合

开始 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 一定是空的,D 中符号唯一

# 4.10 重定位

  1. 合并相同节:将集合 E 的所有目标模块中相同的节合并成新节
  2. 定义符号重定位:确定新节中所有定义符号在虚拟地址空间中的地址
  3. 引用符号重定位:修改.text 节和.data 节中对每个符号的引用(地址),指向对应的定义符号起始处

# 4.10.1R_386_32 的重定位方式

转移目标地址 = PC + 偏移地址