(一)静态链接器干了什么
以此程序为例,编译器+汇编器会把main.c和swap.c翻译成两个可重定位目标文件(relocatable object file)main.o和swap.o。
链接器ld会将main.o和swap.o以及一些必要的系统目标文件组合起来,创建一个可执行目标文件(executable object file)p。
之后操作系统会使用一个叫做加载器的函数,拷贝可执行文件p中的代码和数据到存储器,然后将控制转移到这个程序的开头。
静态链接器输入一组可重定位目标文件,输出一个可执行目标文件,那么链接器的主要任务究竟是什么呢?
1.符号解析(symbol resolution):将每个符号引用和一个符号定义联系起来;
2.重定位(relocation):编译器和汇编器生成从地址0开始的代码和数据节。链接器通过吧每个符号定义与一个存储器位置联系起来,然后修改所有对这些符号的引用,使得它们指向这个存储器位置,从而重定位这些节。
(二)目标文件
因为链接器的输入输出都是目标文件,所以必须先了解什么是目标文件。
目标文件由三种形式:可重定位目标文件、可执行目标文件、和共享目标文件。其中,共享目标文件是一种特殊的可重定位目标文件,可以在加载或运行时动态地被加载到存储器并链接。
(三)符号解析
那么,symtab中为每一个符号需要保存一些什么信息呢?
链接器解析符号引用的方法是:将每个引用与它输入的可重定位目标文件的符号表中的一个确定的符号定义联系起来。
其中,对于那些和引用定义在相同模块的本地符号的引用,编译器会处理:只允许每个模块中每个本地符号只有一个定义。
那么,对于链接器来说:如何保障符号引用与一个符号定义联系起来?如果存在同一个符号的多个定义怎么办?
首先,符号分为强符号和弱符号。函数和已初始化的全局变量是强符号;未初始化的全局变量是弱符号。
根据强弱符号的定义,Unix链接器使用下面的规则来处理多重定义的符号:
规则1:不允许有多个强符号;
规则2:如果有一个强符号和多个弱符号,那就选择强符号;
规则3:如果有多个弱符号,那就从这些弱符号中任意选择一个。
对于C语言来说,有许多标准函数。如何将标准函数与main.c链接呢?
那么,如何使用静态库?
但是上述的链接过程有一点问题:因为文件输入的顺序非常重要。如果定义一个符号的库出现在引用这个符号的目标文件之前,那么这个符号就不能被解析。
这个问题也没有好的解决方法,只能是将库放到命令行的结尾。同时必须对库文件进行排序。有时候,一个库可能会出现好几次,如:(foo.c libx.a liby.a libx.a)。
(四)重定位
当符号解析完成时,链接器知道它的输入目标模块中的代码节和数据节的确切大小,现在就可以重定位了。在这个步骤中,将合并输入模块,并为每个符号分配运行时地址。
重定位分为两步:
1. 重定位节和符号定义。链接器将所有相同类型的节合并为同一类型的新的聚合节,例如.data节全部合并。然后链接器将运行时存储器地址赋给新的聚合节、输入模块定义的每个节、以及赋给输入模块定义的每个符号。
此时,程序中的每个指令和全局变量都有唯一的运行时存储器地址了。
2. 重定位节中的符号引用。链接器修改代码节和数据街中对每个符号的引用,使得它们指向正确的运行时地址。为了执行这一步,链接器依赖于重定位条目(relocation entry)。
重定位条目:无论何时汇编器遇到对最终位置未知的目标引用,就会生成重定位条目,告诉链接器将目标文件合并成可执行文件时,如何修改这个引用。代码的可重定位条目放到.rel.text中,已初始化数据的可重定位条目放到.rel.data中。
其数据结构如下:offset是需要被修改的引用的节偏移。symbol是被修改的引用应该指向的符号;type告知链接器如何修改新的引用:是R_386_PC32相对引用还是R_386_32绝对引用?
typedef struct { int offset; int symbol:24,4 type:8;5} Elf32_Rel;
(五)可执行文件的执行
(六)动态链接共享库
静态库有两个缺点:
1. 如果静态库更新了,程序员想使用最新版本,则必须重新与静态库链接;
2. 几乎所有的C程序都会使用IO函数,而每一个程序都包含这些函数代码的拷贝,也会浪费空间。
由此,提出了共享库(shared library)的概念。共享库是一个目标模块,在运行时,可以加载到任意的存储器地址,并和一个存储器中的程序链接起来。这个过程称为动态链接,是由一个叫做动态链接库的程序来执行。
相比于静态链接,使用动态链接库时,会首先使用静态链接器完成部分链接,然后再使用动态链接器在运行时链接。
Linux为动态链接器提供了一个简单的接口,允许程序在运行时加载和链接共享库。
(七)与位置无关的代码(PIC)
没有看懂。