《深入理解计算机系统》笔记——链接

链接可以执行于编译、加载、运行时,编译时属于静态链接,加载与运行时属于动态链接。链接由链接器自动执行的,它使得分离编译成为可能。

编译器驱动程序:
main.c 经过预处理器 cpp 得到 main.i (ASCII码的中间文件)
main.i 经过编译器 ccl 得到 main.s (ASCII码汇编语言文件)
main.s 经过汇编器 as 得到 main.o (可重定位目标文件)
main.o 经过静态链接器 ld 得到 a.out (可执行目标文件)

目标文件有三种:可重定位目标文件,可执行目标文件,可共享目标文件。

为了构造可执行文件,链接器必须完成两个主要任务:
符号解析,将每个符号引用刚好和一个符号定义联系起来;
重定位,编译器和汇编器生成从地址0开始的代码节和数据节,链接器通过把每个符号定义和一个存储器位置联系起来,并修改所有对这些符号的引用,使得它们指向这个存储器位置,从而重定位这些节。

当编译器遇到一个不是在本模中定义的符号(变量或函数名)时,它会假设该符号是在其他某个模块中定义的,生成一个链接器符号表条目,并把它交给链接器处理。每个可重定位目标模块都有一个符号表,它包含该模块中定义和引用的符号的信息,可以通过READELF工具显示出来。符号表是由汇编器构造的,使用编译器输出到汇编语言.s文件中的符号。

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

在编译时,编译器向汇编器输出每一个全局符号,或者是强,或者是弱。函数和已初始化的全局变量是强符号,未初始化的全局变量是弱符号。根据强弱符号的定义,链接器使用3个规则来处理多重定义的符号。1)不允许有多个强符号,2)强符号和弱符号同时出现时先强符号,3)多个弱符号时任意选择一个弱符号。

静态库以一种称为存档的特殊文件格式存放在磁盘中。存档文件是一组连接起来的可重定位目标文件的集合,有一个头部用来描述每一个成员目标文件的大小和位置。存档文件以后缀.a标识。

在符号解析的阶段,链接器从左到右按照它们在编译器驱动程序命令行上出现的相同顺序来扫描可重定位目标文件和存档文件。在这个过程中维护3个集合,可重定位目标的集合,未解析的符号集合,前面输入文件中已定义的符号集合,在读的过程中不断修改后面两个集合。(驱动程序自动将命令行所有的.c文件翻译为.o文件。)

关于库的一般准则是将它们放到命令行的结尾。如果库不是独立的,那么它们必须排序。如果满足需要,可以在命令上重复库。

在完成了符号解析一步后,链接器就知道它的输入目标模块中代码节和数据节的确切大小。现在可以开始重定位了,在这个步骤中,将合并输入模块,并为每个符号分配运行时地址。

重定位由两步组成:
重定位节和符号定义,将所有相同类型的节合并为同一类型的新的聚合节,使得每个指令和全局变量都有唯一的运行时存储器地址;
重定位节中的符号引用,修改代码节和数据节中对每个符号的引用,使得它们指向正确的运行时地址,这一步依赖于重定位条目。

当汇编器遇到最终位置未知的目标引用时,它就会生成一个重定位条目,告诉链接器合成可执行文件时如何修改这个引用。

可执行目标文件的段头部表描述了将连续的文件节映射到运行时存储器段的映射关系。可以通过OBJDUMP工具显示出来。

静态库和所有的软件一样,需要定期维护和更新。共享库是一个目标模块,在运行时,它可以被加载到任意的存储器地址,并和一个在存储器中的程序链接起来,即动态链接。

动态链接不拷贝动态库任何代码和数据节到可执行目标文件,只是拷贝了一些重定位和符号表信息,它们在运行时可以解析对动态库中代码和数据的引用。

当加载器加载可执行目标文件时,会注意到.interp节,这个节包含了动态链接器的路径名,然后加载和运行这个动态链接器,并将控制传给动态链接器,然后动态链接器完成重定位代码和数据的任务,最后动态链接器把控制传给应用程序。

一个共享库的.text节的一个副本可以被不同的正在运行的程序共享。

下面是对GCC常用参数的整理。

#**********************************************************************
-std=standard 	#Specify the standard to which the code should conform. 

-E 				#预处理后即停止,不进行编译,预处理后的代码送往标准输出
-S              #编译后即停止,不进行汇编,可以使用 -o 选项选择其他名字
-c              #编译或汇编源文件,但是不作连接,编译器输出对应于源文件的目标文件,可以使用 -o 选项选择其他名字

-o 				#指定输出文件名。该选项不在乎 GCC 产生什么输出,无论是可执行文件,目标文件,汇编文件还是预处理后的 C 代码

-MM 			#和 -M 选项类似,但是输出结果仅涉及用户头文件

-llibrary		#连接名为 library 的库文件,连接器在标准搜索目录中寻找这个库文件,库文件的真正名字是 liblibrary.a
				#搜索目录除了一些系统标准目录外,还包括用户以 -L 选项指定的路径
-static			#在支持动态连接的系统上,阻止连接共享库
-shared			#生成一个共享目标文件,他可以和其他目标文件连接产生可执行文件
-fPIC			#如果支持这种目标机,编译器就输出位置无关目标码,适用于动态连接,即使分支需要大范围转移

-Idir			#在头文件的搜索路径列表中添加 dir 目录
-Ldir			#在 -l 选项的搜索路径列表中添加 dir 目录

-w              #禁止所有警告信息
-Wall			#生成所有警告信息

-g 				#以操作系统的本地格式产生调试信息
-rdynamic		#把所有符号(而不仅仅只是程序已使用到的外部符号,但不包括静态符号,比如被static修饰的函数)
				#都添加到动态符号表(即.dynsym表)里,以便那些通过dlopen()或backtrace()
				#(这一系列函数使用.dynsym表内符号)这样的函数使用。

-O0				#不优化				
-O1				#优化,对于大函数,优化编译占用稍微多的时间和相当大的内存 
-O2  			#多优化一些,除了涉及空间和速度交换的优化选项,执行几乎所有的优化工作
-O3				#优化的更多,除了打开 -O2 所做的一切,它还打开了 -finline-functions 选项
				#-finline-functions,把所有简单的函数集成进调用者,编译器探索式地决定哪些函数足够简单,值得这种集成

-m32			#产生32位代码
#**********************************************************************

相关代码:https://files.cnblogs.com/files/shuaihanhungry/link.zip
参考:《深入理解计算机系统》。

原文地址:https://www.cnblogs.com/shuaihanhungry/p/5808369.html