链接和共享库

来源:互联网 发布:龙虎榜数据 同花顺 编辑:程序博客网 时间:2024/05/17 23:57

链接:

链接就是将不同部分的代码和数据集合成一个单一文件的过程,这个文件可以被加载(或者被拷贝)到存储器并执行。链接可执行于编译时(compile time) 也就是源代码被翻译成机器代码时;也可以执行于加载时(load time),也就是程序被加载器加载到存储器并执行时;甚至执行于运行时(run time),由应用程序来执行。


为了构造可执行文件,连接器必须完成两个主要任务:

符号解析。目标文件定义和引用符号。符号解析的目的是将每个符号引用刚好和一个符号链接联系起来。

重定位(relocation) 。编译器和汇编器生成从零开始的代码和数据节。连接器通过把每个符号定义与一个存储位置联系起来,然后修改所有对这些符号的引用,使得他们指向这个存储位置,从而从定位这些细节。


要记住关于连接器的一些基本事实:目标文件纯粹是字节快的集合。这些快中,有些包含程序代码,有些包含程序数据,而其他则包含知道链接器和加载器的数据结构。连接器将这些块链接起来,确定被连接块的运行时位置,并且修改代码和数据块的位置。链接器对目标机器的了解甚少。产生目标文件的编译器和汇编器已经完成了大部分工作。


目标文件:

目标文件有三种形式:

可从定位目标文件。包含二进制代码和数据,其形式可以在编译时与其他可从定位目标文件合并起来,创建一个可执行目标文件。

可执行目标文件。包含二进制代码和数据,其形式可以直接被拷贝带存储器并执行。

共享目标文件。一种特殊类型的可从定位目标文件,可以在加载时或者运行时被动态的加载到存储器并链接。


编译器和汇编器生成可从定向的目标文件(包括共享目标文件)。连接器生成可执行目标文件。


可执行和可连接格式(executable and Linkable format ,ELF)

Screen Shot 2013-11-01 at 下午2.41.11.png


ELF头 以一个 16字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。ELF头剩下的布冯包含帮助连接器语法分析和解释目标文件的信息。


。text :已编程序的机器代码。

。rodata :制度数据,,比如printf语句重点二个是串和开关语句的跳转表。

。data :已初始化的全局变量。局部C变量咋运行时保存在栈中,既不出现在data节中叶不出现在。bss节中。

。bss :未初始化的全局变量。在目标文件中这个节不占据实际空间,他仅仅是一个占位符。目标文件格式区分初始化和未初始化变量市委了空间效率:在目标文件中,未初始化的变量不需要占据任何实际的齿盘空间。

。symtab :一个符号表,他存放在程序中定义和应用的函数和全局变量的信息。。symtab符号表不包含局部变量的条目。


。rel。text 一个text节中位置的列表,当连接器吧这个目标文件和其他目标文件结合时,需要修改这些位置。一般而言任何调用外部函数或者引用全局变量的指令都需要修改。另一方面,调用本地函数的指令则不需要修改。

。rel。data :被模块引用或定义的任何全局变量的从定位信息。一般而言,任何已初始化的全局变量,如果他的初始值是一个全局变量地址或者外部定义函数的地址,都需要被改变。

。line : 原始C程序中的行号和。text节中机器指令之间的映射。

。strtable : 一个字符串表,其中包括。symtab和debug字节符号表,以及头部中的名字。



符号和符号表:

每个可重定位的目标模块m都有一个符号表,他包含m所定义和引用的符号信息。在链接器的上下文中,有三种不同的的符号:


1有m模块定义并能被其它模块引用的全局符号。全局链接符号对应于飞静态的C函数以及被定义未不带static属性的全局变量

2有其它模块定义并被m模块引用的全局符号。这些符号称为外部符号(external),对应于定义在其他模块中的C函数和变量

3只有被,模块m定义金额引用的本体符号。有的本地符号对应于static属性的C函数和全局变量。这些符号在模块中随处课件,但不能被其他模块引用。


认识到本地链接符号本地程序变量的不同是很重要的。。symtab中的符号表不报含地应于本地非静态程序变量的任何符号。这些符号在运行时在栈中被管理,连接器对此类符号不感兴趣。

有趣的是,带有C static属性的过程变量是不在栈中管理的。相反,编译器在。data和。bss中为每个定义分配空间,并在符号表中创建一个有唯一名字的本地链接器符号。


利用static属性隐藏变量和函数名字:

C程序员使用static 属性在模块内部隐藏变量和函数声明,就像你在java和C++中使用public和private 声明一样。C源代码扮演者模块的角色。任何声明带有static属性的全局变量或者函数都是模块私有的。尽可能使用static保护你的变量和函数时很好的变编程习惯。


7.6 /477符号解析:

对C++和java中中连接器符号的毁坏

C++和java都允许重载方法,这些方法在源代码中有相同的名字,却有不同的参数列表。那么连接器是如何区别这些不同的重载函数之间的差异的呢?C++和java中能使用函数重载,是因为编译器建每个唯一的方法和参数列表祝贺成一个对连接器来说唯一的名字。这种编码过程叫做毁坏。而相反的过程叫做恢复。


7.61 、478连接器如何解析多重定义的全局符号:

在编译时,编译器向汇编器输出每个全局符号,或者是强,或者时弱,而汇编器会把这个隐含地编码在可重定位目标文件的符号表里。函数和已初始化的全局变量时强符号,未初始化的全局变量时弱符号。


根据强弱符号的定义,unix连接器使用如下规则来处理多重定义的符号:

规则一:不允许有不多强符号。

规则二: 如果有一个强符号和多个弱符号,那么选择强符号

规则三: 如果有多个弱符号,那么从中任意选一个。


7。62/480

与静态库链接:


迄今为止,我们都是假设连接器读取一组可重定位目标文件,并把它们链接起来,成为一个输出的可执行文件,实际上,所有的编译器系统都提供一种机制,将所有相关的目标模块打包成一个单独的文件,称为静态库,他可以用作链接器的输入。当连接器构造一个输出的可执行文件时,他只拷贝静态库中被程序引用的目标模块



7.7 /484  重定位


一旦连接器完成了符号解析这一步,塔基吧大妈中的每个符号引用和确定的一个符号定义(即它的一个输入目标模块中的一个符号条目)联系起来。在此时,连接器就知道它的输入目标木块中的代码节和数据节的确切大小。现在就可以重新定位了。在这个步骤中,将合并输入模块,并为每个符号分配运行时地址。重定位由两布组成:

1重定位节和符号定义。在这一步中,连接器将所有同类型的节合并为同一类型的的聚合节。列如,来自输入模块的。data节被全部合并成一个节,这个节成为输出的可执行目标文件的。data节。然后,连接器将运行时存储器地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给驶入模块定义的每个符号。当这一步完成时,程序中的每个指令和全局变量都有唯一的运行时地址了。

2重定位节中的符号引用。在这一步中,连接器修改代码和数据节中对每个符号的引用,使得他们指向正确地运行时地址。为了执行这一步,连接器依赖于重定位条目中的可重定位目标模块中的数据结构。


7.8/488 可执行目标文件

我们已经看到连接器是如何将多个目标模块和并成一个可执行目标文件的。我们的C程序,开始时时一组ascii文本文件,已经被转化成一个二进制文件,而且这个文件包含加载程序到存储器并运行的所有信息。



7.9 、489  加载可执行目标文件 


加载器将可执行目标文件的代码和数据从磁盘拷贝到存储器中,然后通过跳转到程序的第一条指令或入口点来运行程序。这个将程序口碑靠存储器并运行的过程叫做加载(loading)。

当加载器运行时,他创建如图的存储器映象。在可执行中,段头表的指导下,加载器将可执行文件的相关内容拷贝到代码段和数据段。接下来,加载器跳转到程序的入口点,也就是符号_start的地址。


7.10 /490 动态链接共享库

我们在7.62节中研究的静态库解决了许多如何让大量相关函数对应用程序可用的问题。然而,静态库仍然有一些明显的缺点。静态库和所有的软件一样,需要定期的维护和更新。如果应用程序员想使用一个库的最新版本,他们必须一某种方式了解到该库的更新情况,然后显示的将它们的程序与更新了的库重新链接。

另一个问题是,几乎每个C程序都使用的标准I/O函数,如printf何scanf。在运行时,这些函数的代码会被复制到每个运行进程的文本段中。这是对稀缺的存储器系统资源的极大浪费。


共享库(shared library) 是致力于解决静态库的一个缺陷的现代创新产物。共享库是一个目标模块,在运行时,可以加载到任意的存储器地址,并和一个在存储器中的程序链接起来。这个过程称为动态链接(dynamic linking) 是由一个叫做动态连接器的程序来完成的。

共享库也称为共享目标(shared object),在UNIX中通常用。so后缀来表示,微软操作系统大量的利用了共享库,他们称为DLL(动态链接库)。


7.12 、494与位置无关的代码(PIC)

共享库的一个主要的目的就是允许多个正在运行的进程共享存储存储器中相同的代码库,因而节约了宝贵的储存器资源。那么多个进程时如共享程序的一个拷贝的呢?

。。。。

一种更好的办法是编译库代码,使得不需要连接器修稿库代码就可以在任何地址加载和执行这些代码。这样的代码就叫和地址无关的代码(Position-Independent Code ,PIC)。

对同一个模块的的过程调用是不需要特殊处理的,因为引用时pc相对的,已知偏移量就已经是PIC了。然而,对外部定义的过程调用和对全局变量的引用通常不是PIC,因为他们要求在连接时重定位。


PIC数据引用:

编译器通过以下这个有趣的事实来生成对全局变量的PIC引用: 无论我们在存储器中的何处加载一个目标模块(包括共享模块),数据段总是被分配在紧随在代码段后面。因此,代码段中任何指令和数据段中任何变量之间都是一段运行时常量,域代码和数据段的绝对存储器位置时无关的。






0 1
原创粉丝点击