链接笔记

# 链接笔记

编译链接是经常会听到的,但是编译器和链接器具体做了什么,又是如何完成的这些问题总是不太理解。所以有了这个文档,关于链接过程的解析。

## 链接的作用
程序从编写的一个个文本文件到最后的可执行文件,需要经过预处理,编译,汇编和链接四个过程。预处理的过程中将源代码文件中不必要的东西去除,比如注释和多余的空格换行。预处理后的文件经过编译会得到汇编代码,汇编代码在经汇编器之后得到的就是二进制的文件了,此时的文件被称为可重定位的目标文件。每个源代码文件都会对应与一个可重定位的目标文件,链接器将这些可重定位的目标文件收集起来,最后输出一个单一的可执行文件。这个可执行文件,就可以被载入到存储器中去运行了。

## 静态链接
像上一节中所描述的链接过程,Linux上通常使用ld程序来完成的,这样的程序被称作是静态链接器。静态链接器以一组可重定位的目标文明和命令行参数作为输入,生成一个完全链接的可以加载和运行的可执行目标文件作为输出。输入的可重定位目标文件由不同的代码和数据端(section)组成。代码指令在一个段中,初始化的全局变量在一个段中,而未初始化的变量有点另一个段中。
为了构造可执行文件,连接器必须完成的连个主要任务:
1. 符号解析。目标文件中定义和引用的符号,符号解析的过程将每个符号引用刚好和一个符号定义联系起来。
2. 重定位。编译器和汇编器生成的代码都是从地址0开始的。连接器通过把每个符号定义与一个存储器的位置联系起来,然后修改所有对这些符号的引用,使得指向这个存储器的地址。

### 符号解析
方法是将每个符号引用和输入的可重定位目标文件的符号表中的一个确定的符号定义联系起来。模块内部的本地符号,链接器并不关心,链接器只关心全局的符号引用和定义。
链接器为符号引用选择具体的定义时可能会遇到多重定义的符号。此时必须遵守如下的规则:
1. 不允许有多个强符号,否则出错;
2. 有一个强符号和多个弱符号时,选择强符号;
3. 只有多个弱符号定义时,从弱符号中任意选择一个。

所谓的强符号是指函数和已初始化的全局变量,未初始化的全局变量是弱符号。事实上,C语言中,也可以使用weak显示的定义一个弱符号变量。

#### 静态库
以C语言中为例,C 语言中有很多经常使用到的系统函数,比如printf,scanf等。如果没有静态库,那么用户需要使用这些库函数时候,要么编译器能自带这些函数的源代码,要么将这些函数放在一个单独的可重定为目标模块中(如libc.o),需要使用的时候将这个模块一同链接进来。
对上述第一种方法,C语言中这样的库函数数量相当多,这种处理方式会使得编译器的实现过于复杂,因为任何对这些函数的增加,删除和修改,都不得不更新一次编译器,否则就不能使用最新的库函数。同时这样子使得编译器需要维护的部分不再这是编译器本身。而第二中方法,则会产生空间上的浪费,因为每个程序都需要把整个 libc.o 文件连接到自己的可执行文件中,这使得即使很小的程序也不得不带有一个巨大的libc.o文件。如果把每个模块单独编译成一个可重定位的目标模块,那么在链接时候需要显示指定使用到的模块,如果使用的模块多了之后,编译时在命令行加入的参数就会数量超多,这使得链接的过程就复杂起来了。
为了解决上面方法的问题,就产生了静态库的概念。所谓的静态库,实际上就是将不同模块编译得到的可重定位目标文件(.o)文件打包起来的一个文件。这是对上述两个方法的一种折中处理。这个库,不由编译器维护,而是在每个操作系统上独立提供。同时不同的模块编译成不同的目标模块,在链接时由链接器选择将特定需要的模块链接到最后的可执行文件中,也避免了将所有文件链接进来产生的空间上的浪费。最后,这些目标模块被打包在一起之后,也就避免了在命令行中输入过多参数的问题,只需要把静态库作为参数放进来即可。

### 重定位
链接器完成了符号解析时,也就把代码中所有的符号引用和确定的一个符号定义联系起来了。这时候,链接器也就知道它的输入目标模块中的代码段和数据段的确切大小。这时候就可以开始重定位了。重定位的过程会合并所有的输入模块,并为每个符号分配运行时的地址。重定位的过程可以分为如下的两步:
1. 重定位段和符号定义。这一步中,将所有相同类型的段合并到同一类型的新的聚合段中。如所有的.data合并到.data段中。然后链接器把运行时存储器的地址赋给新的聚合段,赋给输入模块定义的每个段,以及赋给输入模块定义的每个符号。
2. 重定位段中的符号引用。链接器修改每个符号的地址,使他们指向正确的运行时地址。

## 动态链接和运行时加载
静态链接是在生成可执行文件之前完成链接的操作,使得程序能正常运行。这样依然会有问题,当库函数更新时候,如果程序需要使用新的库函数则必须要重新执行链接的操作。而且,虽然静态库的使用使得磁盘占用减小了,但是不同的程序依然要把使用到的库函数打包到自己程序中,造成比较多的浪费。为了应对这些问题,就需要使用到动态链接的技术。
所谓的动态链接,是指在程序被加载之后,在运行之前完成链接操作。完成这个链接操作的是动态链接器。这个过程需要使用到共享库,类UNIX系统中这样的库以.so结尾,Windows中则被称为DLL文件。动态链接的基本思路是当创建可执行文件时,静态执行一些链接,在加载程序时动态完成与共享库链接的过程。
这使得,动态库中的内容并不需要被真正拷贝到可执行文件中,只要把一些重定位和符号表的信息告诉链接器,使得在运行时能解析对动态库中函数的调用即可。
动态链接器完成的主要工作包括:
1. 重定位libc.so的文本和数据到某个存储段。
2. 重定位用户.so的文本和数据到另一个存储器段。
3. 重定位可执行文件中所有由libc.so和用户.so定义的符号的引用。
最后,动态链接器会把控制传递给应用程序。此时,共享库的位置就固定了,并且程序执行的过程中也就不会改变了。

共享库的使用不一定需要在动态链接的过程中使用,甚至可以在程序运行时显示的打开某个共享库,来加载其中的函数。

posted @ 2016-04-07 16:11  柜员_徐无咎  阅读(226)  评论(0)    收藏  举报