链接·七

来源:互联网 发布:财经日历重要数据解读 编辑:程序博客网 时间:2024/05/29 10:03
第7章 链接
关键词:链接器和加载器,静态链接,动态链接,符号解析和解析错误,重定位
链接(linking)就是将不同部分的代码和数据收集和组合成为一个单一文件的过程,这个文件可被加载(或被拷贝)到存储器并执行。链接可以执行于编译时(compile time),也就是源代码被翻译成机器代码时;也可以执行于加载时(load time),也就是在程序被加载器(loader)加载到存储器并执行时;甚至执行于运行时(run time),由应用程序来执行。在早期的计算机系统中,链接是手动执行的。在现代系统中,链接是由叫做链接器(linker)的程序自动执行的。
链接器作用:使分离编译成为可能。只用编译小部件而不用重新编译真个项目的大文件,尤其是大项目出错时候。
链接器:
理解链接器将帮助你构造大型程序
理解链接器将帮助你避免一些危险的编程错误
理解链接器将帮助你理解语言的作用于规则是如何实现的
理解链接器将使你能够开发共享库

7.1 静态链接

静态链接器以一组可重定位目标文件和命令行参数作为输入,生成一个完全链接的可以加载和运行得可执行目标文件作为输出。输入的可重定位目标文件由各种不同的代码和数据节组成。指令在一个节中,初始化的全局变量在另一个节中,而未初始化的变量又在另一个节中。
为了创建可执行文件,链接器必须完成两个主要任务是符号解析和重定位。
符号解析:目标文件定义和引用符号。符号解析的目的是将每个符号引用和一个符号定义联系起来。
重定位:编译器和汇编器生成从地址零开始的代码和数据节。链接器通过把每个符号定义与一个存储器位置联系起来,然后修改所有对这些符号的引用,使得它们指向这个存储器位置,从而重定位这些细节。
链接器的一些基本事实:目标文件纯粹是字节块的集合。这些块中有些包含程序代码,有些则包含程序数据,而其它的则包含指导链接器和加载器的数据结构。链接器将这些块链接起来,确定被链接块的运行时位置,并且修改代码和数据块的各种位置。链接器对目标机器了解甚少,产生目标文件的编译器和汇编器已经完成了大部分工作。

7.2 目标文件

目标文件有三种:可重定位文件、可执行目标文件、共享目标文件。
可重定位文件:包含二进制代码和数据,其形式可以在编译时与其它可重定位目标文件合并起来,创建一个可执行目标文件。
可执行目标文件:包含二进制代码和数据,其形式可以被直接拷贝到存储器并执行。
共享目标文件:一种类型的可重定位目标文件,可以在加载或者运行时,被动态地加载到存储器并链接。
编译器和汇编器生成可重定位目标文件(包括共享目标文件)。链接器声称可执行文件。

7.3 符号和符号表

1 符号
每个可重定位目标模块m都有一个符号表,它包含m所定义和引用的符号的信息,在链接器上下文中,有三种不同的符号。
(1)由m定义并能被其它模块引用的全局符号。全局链接符号对应于非静态的C函数以及被定义为不带C的static属性的全局变量。
(2)由其它模块定义并能被m引用的全局符号。这些符号被称为外部符号(external),对应于定义在其它模块中的C函数核变量。
(3)只被m模块定义和引用的本地符号。有的本地链接器符号对应于带static属性的C函数核全局变量。这些符号在模块m中的任何地方都是可见的,但是不能被其它模块引用。目标文件中对应于模块m的节和相应的源文件也能获得本地符号。
2 static属性的应用
定义为带有C state属性的本地过程变量是不在栈中管理的。
利用static属性隐藏变量和函数名字:C程序员使用static属性在模块内部隐藏变量和函数声明,就像你在Java和c++中使用public和private声明一样。C源代码文件扮演磨快的角色。任何声明带有static属性的全局变量或者函数都是模块私有的。类似地,任何声明为不带static属性的全局变量和函数都是公共的,可以被其它模块访问。尽可能用static属性来保护你的变量和函数是很好的编程习惯。

7.4 符号解析

1 链接器解析符号引用的方法
链接器解析符号引用的方法是将每个引用与它输入的可重定位目标文件的符号表中的一个确定的符号定义联系起来。对那些和引用定义在相同模块中的本地符号的引用,符号解析是非常简单明了的。编译器只允许每个模块中的本地符号只有一个定义。编译器还确保静态本地变量,它们也会有本地链接器符号,拥有唯一的名字。
2 重载方法
C++和Java都允许重载方法,这些方法在源代码中有相同的名字,却有不同的参数列表。那么链接器是如何区别这些不同的重载函数之间的差异呢?C++和Java中能使用重载参数,是因为编译器将每个唯一的方法和参数列表组合编码成一个对链接器来说唯一的名字。这种编码过程叫做毁坏(mangling),而相反的过程叫做恢复(demangling)。幸运的是C++和Java使用兼容的毁坏策略。一个毁坏类的名字是由名字中字符的整数数量,后面跟原始名字组成的。
3 链接器如何解析多处定义的全局符号
函数和已初始化的全局变量是强符号,未初始化的全局变量是弱符号。根据强弱符号的定义,Unix链接器使用下面的规则来处理多处定义的符号:
规则1:不允许有多个强符号。
规则2:如果有一个强符号和多个弱符号,那么选择强符号。
规则3:如果有多个弱符号,那么从这些弱符号中任意选择一个。
三类错误:主函数声明两次,弱符号声明两次,变量定义为不同的类型也会导致及其严重的错误,所以明明时候要保证命名唯一。
4 与静态库连接
迄今为止,我们都是假设链接器读取一组可重定位目标文件并把它们链接起来,成为一个输出的可执行文件。实际上,所有的编译系统都提供一种机制,将所有相关的目标模块打包成为一个单独的文件,称为静态库,它也可以用作链接器的输入。当连接器构造一个输出的可执行文件时,它只拷贝静态库里被应用程序引用的目标模块。

7.5  重定位

重定位由两步组成:重定位节和符号定义、重定位节中的符号引用。
重定位表目:当汇编器生成一个目标模块时,它并不知道数据和代码最终将存放在存储器中的什么位置。它也不知道这个模块引用的任何外部定义的函数或者全局变量的位置。所以,无论何时汇编器遇到最终位置未知的引用,它就会生成一个重定位表目(relocation entry),告诉链接器在将目标文件合并成可执行文件时如何修改这个引用。

7.6 动态链接共享库

静态库和所有的软件一样,需要定期维护和更新。
共享库(shared library)是致力于解决静态库缺陷的一个现代创新产物。共享库是一个目标模块,在运行时,可以加载到任意的存储器地址,并在存储器中和一个程序链接起来。这个过程称为动态链接(dynamic linking),是由一个动态链接器(dynamic linker)的程序来执行的。
共享库也称为共享目标(shared object),在Unix系统中通常用.so后缀来表示。微软的操作系统大量地利用了共享库,它们称为DLL(动态链接库)。
共享库的“共享”在两个方面有所不同。首先,在任何给定的文件系统中,对于一个库只有一个.so文件。所有引用该库的可执行目标文件共享这个.so文件中的代码和数据,而不是像静态库的内容那样背靠背和嵌入到引用它们的可执行的文件中。其次,在存储器中,一个共享库的.text节只有一个副本可以被不同的正在运行的进程共享。

7.7*与位置无关的代码(PIC)

共享库的一个主要目的就是允许多个正在运行的进程共享存储器中相同的代码库,因而节约宝贵的存储器的资源。多个进程共享一个程序的拷贝的一种方法是给每个共享库分配一个事先预备的专用的地址空间组块(chunk),然后要求加载器总是在这个地址加载共享库。这个方法不仅对空间使用效率不高,也难以管理。一种更好的方法是编译库代码,使得不需要链接器修改库代码,就可以在任何地址加载和执行这些代码。这样的代码叫做与位置无关的代码(position-independent code,PIC)。
无论我在们存储器中的何处加载一个目标快(包括共享目标模块),数据段总是分配为紧随在代码段后面。因此代码段中任何指令和数据段中任何变量之间的距离都是一个运行时常量,与代码段和数据段的绝对存储器位置是无关的。

7.8 处理目标文件的工具

Unix系统中有大量可用的工具可以帮助你理解和处理目标文件。特别地,GUN binutils包尤其有帮助,而且可以运行在每个Unix平台上。
AR:创建静态库,插入、删除、列出和提取成员
STRINGS:列出一个目标文件中的所有可打印的字符串
STRIP:从目标文件中删除符号表信息
NM:列出一个目标文件的符号表中定义的符号
SIZE :列出目标文件中节的名字和大小
READELF:显示一个目标文件的完整结构,包括ELF头中编码的所有信息。包含SIZE和NM的功能。
OBJDUMP:所有二进制工具之母。能够显示一个目标文件中所有的信息。。它最有用的功能室反汇编.text中的二进制指令。
Unix系统为操作库贡献还提供了ldd程序:
LDD:列出一个可执行文件在运行时所需要的共享库。

7.9 小结

链接器可以在编译时由静态编译器来完成,也可以在加载时和运行时由动态链接器处理为目标文件的二进制文件,他有三种不同的形式:可重定位的、可执行的和共享的。可重定位的目标文件由静态链接器组合成一个可执行的目标文件,它可以加载到存储器中并执行。共享目标文件(共享库)是在运行时由动态链接器链接和加载的,或者隐含地调用程序被加载和开始执行时,或者根据需要在程序调用dlopen库的函数时。
连机器的两个主要任务是符号解析和重定位。符号解析将目标文件中的每个全局符号都绑定到一个唯一的定义,而重定位确定每个符号的最终存储器地址,并修改对那些目标的引用。
静态链接器是由像GCC这样的编译器调用的,它们将多个可重定位目标文件组合成一个单独的可执行目标文件。多个目标文件可以定义相同的符号,而链接器用来悄悄滴解析这些多处定义的龟儿可能在用户程序中引入的微妙错误。
多个目标文件可以被连接到一个单独的静态库中。链接器用库来解析其它目标模块中的符号引用。许多链接器通过从左到右的顺序扫描来解析符号引用,这是一个因其令人迷惑的链接错误时错误的来源。
加载器将可执行文件的内容映射到存储器,并运行这个程序。链接器还可能生成部分连接的可执行目标文件,这样的文件中有未解析的到定义在共享库中的程序和数据的引用。在加载时,加载器将部分链接的可执行文件映射到存储器,然后调用动态链接器,它通过加载共享库和重定位程序中的引用来完成链接任务。
被编译为位置无关代买的共享库可以加载到任何地方,也可以在运行时被多个进程共享。为了加载、链接和访问共享库的函数和数据,应用程序还可以在运行时使用动态链接器。

参考文献

布赖恩特, O'Hallaron D, et al. 深入理解计算机系统[M]. 中国电力出版社, 2004.
0 0
原创粉丝点击