链接器
1. 背景
对于经常使用 IDE 的开发者,通常点击一个按钮就万事大吉了,这虽然极大简化了过程,但是对于我们C语言这些相对底层的开发者来说非常非常不友好,屏蔽了大量细节,不了解内部细节是非常可怕的,因此,非常有必要探究其原理。
2. 知识储备
- 将C代码生成可执行目标文件需要经过4个步骤
- 其中需要编译工具链来翻译我们写的代码
- 编译工具链其实也就只是程序,只不过是用来将我们人能够读懂的代码翻译成机器码的程序
- 预处理:使用预处理器将头文件、宏定义,原封不动的直接展开在当前文件中,并生成 .i 文件
- 编译:使用编译器将 .i 文件翻译成汇编语言文件 .s
- 汇编:使用汇编器将 .s 翻译成一个可重定位目标文件(relocatable obj file).o
- 链接器:闪亮登场!本文重点研究下链接器 ld,因为链接器非常重要,需要经常使用,将一系列 .o 文件组合起来,创建出可执行目标文件(executable object file)
3. 什么是链接器(Linker)
借用深入理解计算机系统一书中的解释:以一组可重定位目标文件和命令行参数作为输入,生成一个完全链接的、可以加载和运行的可执行目标文件作为输出。输入的可重定位目标文件由各种不同的代码和数据节(section)组成,每一个节都是一个连续的字节序列。指令在一个节中,初始化了的全局变量在另一个节中,而未初始化的变量又在另外一个节中。
节其实就是一个区域
有点类似 Linux 下的归档工具 tar,将多个文件集合起来,但这仅仅是 Linker 的作用之一,还有个作用就是重定位!
4. 可重定位目标文件里到底是啥?
上面这副图就是目标文件中装的东西
- .text:指令(汇编指令,C函数翻译后得来,局部变量其实就是指令的操作数)放在 text 段
- .data:初始化的全局变量与静态变量放着 data 段
- .bss:未初始化或初始化为0的全局变量与静态变量
- .rodata:使用 const 关键字修饰的变量
- .symtab:符号表!它存放程序中定义、引用的函数、全局符号的信息。每一个 .o 都有一个符号表,记录着自己定义和全局变量符号、自己定义的函数符号与引用别人的符号(变量与函数),就是因为这张表的存在,链接器才能够将大量的 .o 文件链接起来生成可执行文件。
- 其他不深究
5. 符号和符号表
每一个可重定位目标模块m都有一个符号表,它包含m定义和引用的符号的信息。在链接器的上下文中,有三种不同的符号:
-
由模块m定义应能够被其他模块引用的全局符号
-
由其他模块定义并被模块m引用的全局符号。 这些符号称为外部符号, 对应于在其他模块中定义的非静态C函数和全局变最
-
只被模块m定义和引用的局部符号。 它们对应于带 static属性的C函数和全局变 量。 这些符号在模块m中任何位置都可见, 但是不能被其他模块引用。
6. 符号决议
符号表给 Linker 提供了两种信息,一个是当前目标文件可以提供给其他目标文件使用的符号,另一个是其他文件需要提供给当前目标文件使用的符号。有了这些信息,链接器就能够进行符号决议了。
对于那些引用定义在相同模块(同一目标文件)中的局部符号的引用,符号解析是非常简单明了的。编译器只允许每个模块中的每个局部符号只有一个定义。静态局部变量也会有本地链接器符号, 编译器还要确保它们拥有唯一的名字。
全局符号的引用解析就棘手很多了,当编译器遇到一个不是在当前模块中定义的符号时,会假设该符号是在其他某个符号中定义的,生成一个链接器符号表条目,并把它交给链接器处理。如果链接器在它的任何输入模块中都找不到这个被引用的符号的定义,就输出一条错误信息,并终止。
7. 链接静态库
未完持续。。。。。。
8. 重定位
未完持续。。。。。。