csapp 第七章 链接

二木 王者

正如其名 链接就是将各种代码和数据片段收集组合成一个整体的过程

链接器的一个重要的用处就是进行分离编译。这样我们就不用将一个大型的项目写成一个非常大的源文件,我们可以将它分解许多小的部分。也就是说一个可以项目拆分,让不同的人负责不同的模块,进行分工。另一方面维护起来也更加方便,只需对相应模块进行调试和修改就行了。

学习链接器我们可以干什么?

  • 帮助我们理解构建大型文件中出现的一些错误。
  • 帮助我们避免一些危险的编译错误。
  • 帮助我们理解语音的作用域规则是如何实现的。比如,全局变量与局部变量的区别。
  • 帮助我们理解一些重要的系统概念。比如,虚拟内存,分页,内存映射。
  • 让我们更好的利用共享库

静态链接

静态链接器以一组可重定位目标文件和命令行参数作为输入,生成一个完全的链接的,可以加载和运行的执行目标文件作为输出 。简单来说就是,把我们的程序由不可执行变成了可执行文件。在此过程中链接器需要完成的任务:

  • 符号解释 将我们的数据,比如全局变量,静态变量等,属于static声明的数据,用链接器自己的符号来关联起来

    符号 就是我们平常声明的那些东西比如int a,char arr[5],等等

    符号表 将符号整合在一起就形成了符号表。

  • 重定位 编译器与汇编器生成从0开始的地址,链接器将先前关联起来的符号,再与内存关联起来,就重定位了这些数据,然后再修改所有对这些符号的引用,使得它们指向内存的这个位置。

总得来说,链接器就是将我们程序的各种块连接起来,然后再确定这些块的运行位置,并且定位这些块。

目标文件

目标文件可以分为三种:

  • 可重定位目标文件。 二进制代码和数据,在编译的时候可以与其他可重定位目标文件合并在一起,形成一个可执行目标文件。
  • 可执行目标文件。包含二进制代码和数据,可以直接复制到内存中执行。
  • 共享目标文件。特殊的可重定位目标文件,可以在加载或者运行时被动态的加载内存并链接。(也就是我们常说的动态链接库那些)

可重定位目标文件

​ 一个可重定位文件的组成如图所示:

image-20221229165138290

elf头:描述了该文件的系统字的大小与字节顺序,还有帮助链接器语法分析和解释目标文件的信息。包括, elf头的大小,目标文件的类型(上面提到的三种),机器类型(X86-64),字节头部表的文件偏移,节头部表中条目的大小和数量。

.text :已经编译程序的机器代码。

.rodata :只读数据,如在printf语句中的格式串和开关语句的跳转表。

.data :已经初始化的全局和静态变量。而局部变量保存在栈中,没出现在.data和.bss区中。

.bss :未初始化的全局和静态变量,和所有被初始为0的全局或者静态变量。在目标文件中这个节不占实际的空间,仅占位符。

(将目标文件格式区分为已初始化和未初始化变量是为了空间效率,为初始化变量不需要占用任何实际的磁盘空间。运行时,在内存中分配这些变量,初始值为0)

.symtab 一个 符号表。存放着程序中定义和引用的函数和全局变量的信息。(每个可重定位目标文件在.symtab中都有一张符号表)

rel.text : .text节中位置的列表,当链接器把这个目标文件和其他文件组合在一起时,需要修改这些位置。一般的,任何调用外部函数或者引用全局变量的指令都需要需改。(后面会讲,更好理解一些)

rel.data : 被模块引用或定义的所有全局变量的重定位信息。一般的,任何已被初始化的全局变量,如果它的初始值是一个全局变量地址或者外部定义的地址,都需要被修改。(后面会讲,更好理解一些)

debug :一个调试符号表。

.line : 原始c源程序的行号和.text节中机器指令之间的映射。

.strtab : 一个字符表,其中内容包括.symtab和.debug节的符号表,以及节头部中的节名字。

符号和符号表

符号有三种

image-20221229203235962

符号表

由汇编器构造,使用编译器输入到汇编语言.s文件中的符号

符号解析

链接器解析符号引用的方法是将每个引用与它输入的可重定位目标文件的符号表中的一个确定的符号定义关联起来。

当多个模块定义同名的全局符号时,Linux系统采用的方法:将全局符号,分为强和弱,一般函数和已初始化的全局变量是强符号,未初始化的全局变量是弱符号。再由此定义,使用以下规则:

  • 不允许有多个同名的强符号。比如,一个项目中只能有一个main函数。
  • 如果有一个强符号和多个弱符号同名,那么选择强符号来使用。
  • 如果有多个弱符号同名,则在其中随机选择一个。(因为都没有初始化,所以都是一样的)——————》(书中有很经典的例子P510)
静态库链接(P511)

编译系统将所有相关的目标模块打包成一个单独的文件,这个文件我们称其为静态库,它可以作为链接器的输入,当链接器构造一个可执行文件时,它只需复制静态库里面被程序引用的目标块。(我们使用的printf,scanf这些标准函数利用的也是静态库)

重定位

重定位由两步组成:

  • 重定位节和符号定义。使得程序中的每条指令和全局变量都有唯一的运行时内存地址。
  • 重定位节中的符号引用。链接器修改代码节和数据节中对每个符号的引用,使得其指向正确的运行时地址。

重定位条目

当汇编器生成一个目标模块时,它并不知道数据和代码最终将放在内存中的什么位置。也不知道这个模块引用的任何外部定义的函数或者全局变量的为,因而,当汇编器遇到对最终 未知的目标引用就会生成一个重定位条目,用来告诉链接器在将目标文件合成可执行文件时如何修改这个引用。(相当于汇编器不明确数据后面是怎么分配的,就把那些数据放在重定位条目里面,给链接器一个信息,让链接器处理)

  • 代码重定位条目放在.rel.text中,而已初始化了的放在.rel.data中。

image-20221230145928033

(书p517)

可执行目标文件

一个比较典型的ELF可执行文件:

image-20221230150344932

可执行文件与可重定位目标文件有些许差别:

  • ELF头描述文件的总体格式,包括程序的入口点(oep).
  • .init节,定义了一个函数_init。程序初始化代码时会调用它。

动态链接共享库

动态链接库是一个目标模块,在运行或加载时,可以加载到任意的内存地址,并和一个在内存中的程序链接起来,这个过程就是动态链接。Linux中通常为.so后缀,在微软的操作系统中为DLL.

 评论