链接(linking) 是将各种代码和数据部分收集起来并组合成为一个单一文件的过程,这个文件可被加载(或被拷贝)到存储器并执行。链接可以执行于编译时(compile time) ,也就是在源代码被翻译成机器代码时:也可以执行于加载时(load time) ,也就是在程序被加载器(loader) 加载到存储器并执行时:甚至执行于运行时(run time) ,由应用程序来执行。在早期
的计算机系统中,链接是手动执行的。在现代系统中,链接是由叫做链接器(linker) 的程序自动执行的。
一个运行Linux 的x86 系统,使用标准的ELF 目标文件格式。
7.1 编译器驱动程序大多数编译系统提供编译驱动程序,它代表用户在需要时调用语言预处理
器、编译器、汇编器和链接器。比如,要用GNU 编译系统构造示例程序,我们就要通过在外壳中输人下列命令行来调用GCC 驱动程序:
将C 源程序main.c 翻译成一个ASCII 码的中间文件main.i:
cpp [other arguments] main.c /tmp/main.i接下来,驱动程序运行C 编译器(ccl) ,它将main.i 翻译成一个ASCII 汇编语言文件main.s
ccl /tmp/main.i main.c -02 [other arguments] -0 /tmp/main.s驱动程序经过相同的过程生成swap.o.最后,它运行链接器程序ld,将main和swap以及一些必要的系统目标文件组合起来,创建一个可执行目标文件.
ld -0 p [system object files .d argsJ /tmp/main.o /tmp/swap.o外壳调用操作系统中一个叫做加载器的函数,它拷贝可执行文件p 中的代码和数据到存储器,然后将控制转移到这个程序的开头。
7.2 静态链接像Unix ld 程序这样的静态链接器(staticlinker) 以一组可重定位目标文件和命令行参数作为输人,生成一个完全链接的可以加载和运行的可执行目标文件作为输出。
为了构造可执行文件,链接器必须完成两个主要任务:
①符号解析(symbol resolution) 。目标文件定义和引用符号。符号解析的目的是将每个符号引用刚好和一个符号定义联系起来。 ②重定位(relocation) 。编译器和汇编器生成从地址。开始的代码和数据节。 7.3 目标文件目标文件有三种形式:
·可重定位目标文件。包含二进制代码和数据,其形式可以在编译时与其他可重定位目标文件合并起来,创建一个可执行目标文件。 ·可执行目标文件。包含二进制代码和数据,其形式可以被直接拷贝到存储器并执行。 ·共享目标文件。一种特殊类型的可重定位目标文件,可以在加载或者运行时被动态地加载到存储器并链接。链接器生成可执行目标文件。从技术上来说,一个目标模块(object module) 就是一个字节序列,而一个目标文件(object)的就是一个存放在磁盘文件中的目标模块。
7.4 可重定位目标文件ELF 头(ELF header) 以一个16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。包括ELF 头的大小、目标文件的类型
(如可重定位、可执行或者是共享的〉、机器类型〈如IA32) 、节头部在(section header table) 的文件偏移,以及节头部表中的条目大小和数量。
一个典型的ELF 可重定位目标文件包含下面几个节:
. text: 已编译程序的机器代码。 .rodata: 只读数据,比如printf 语句中的格式串开关语句的跳转表 .data: 已初始化的全局C 变量 .bss: 未初始化的全局C 变量。在目标文件中这个节不占据实际的空间,它仅仅是一个占位符。 .symtab: 一个符号哀,它存放在程序中定义和引用典型的ELF 可重定位目标文件的函数和全局变量的信息。 . rel . text :一个.text 节中位置的列表,当链接器把这个目标文件和其他文件结合时,需要修改这些位置。 .rel.data: 被模块引用或定义的任何全局变量的重定位信息。 .debug: 一个调试符号表,其条目是程序中定义的局部变量和类型定义, 程序中定义和引用的全局变量,以及原始的C 源文件。 .line: 原始C 源程序中的行号和.text 节中机器指令之间的映射。 .strtab: 一个字符串表,其内容包括.symtab 和.debug 节中的符号表,以及节头部中的节名字。 7.5 符号和符号表##每个可重定位目标模块m 都有一个符号表,它包含m 所定义和引用的符号的信息。在链接器的上下文中,有三种不同的符号:
·由m 定义并能被其他模块引用的全局符号。全局链接器符号对应于非静态的C以及被定义为不带C static 属性的全局变量。 ·由其他模块定义并被模块m 引用的全局符号。这些符号称为外部符号,对应于定义在其他模块中的C 函数和变量。 ·只被模块m 定义和引用的本地符号。有的本地链接器符号对应于带static 属性的C 函数和全局变量。这些符号在模块m 中随处可见,但是不能被其他模块引用。目标文件中对应于模块m 的节和相应的源文件的名字也能获得本地符号。