4.静态链接

来源:互联网 发布:批处理自动卸载软件 编辑:程序博客网 时间:2024/05/20 21:46
/*b.c*/int shared=1;void swap(int *a ,int *b){    *a^=*b^=*a^=*b;}
/*a.c*/extern int shared;int main(){    int a=100;    swap(&a,&shared);}

经过编译后我们就得到了“a.o”和“b.o”。从代码中可以看到,“b.c”总共定义了两个全局符号。模块“a.c”里面引用到了“b.c”里面的“swap”和“shared”。我们接下来要做的就是把“a.o”和“b.o”这两个目标文件链接在一起并最终形成一个可执行文件“ab”。
4.1 空间与地址分配
输出文件中的空间如何分配给输入文件?
4.1.1按序叠加
这里写图片描述
将输入的目标文件按照次序叠加起来,如上图。因为每一个段都需要有一定的地址和空间对齐要求,比如对于x86的硬件来说,段的装载地址和空间对齐单位是页,也就是4096字节,那么就是说如果一个段的长度只有一个字节,它也要在内存中占用4096字节。这样会造成内存空间大量的内部碎片。
4.1.2相似段合并
这里写图片描述
第一步:空间与地址分配
获得各个段的长度、属性和位置,并且将输入目标文件中的符号表中的所有的符号定义和符号引用收集起来,统一放到一个全局符号表。这一步中,链接器将能够获得所有输入目标文件的段长度,并且将它们合并,计算出输出文件中各个段合并后的长度与位置,并建立映射关系。
第二步:符号解析与重定位
使用上面第一步中收集到的所有信息,读取输入文件中段的数据、重定位信息,并且进行符号解析与重定位、调整代码中的地址等。事实上第二步是链接过程的核心,特别是重定位过程。
这里写图片描述
在链接之前,目标文件中的所有VMA都是0,因为虚拟空间还没有被分配,默认为0.链接之后,可执行文件ab中各个段被分配到相应的虚拟地址。这里的输出程序ab中,“.text”段被分配到0x08048094,大小为0x72字节;在Linux下,ELF可执行文件默认从地址0x08048000开始分配。
4.1.3符号地址的确定
这时候输入文件各个段在链接后的虚拟地址就已经确定了,链接器开始计算各个符号的虚拟地址。因为各个符号在段内的相对位置是固定的,所以这时候其实“main”、“shared”和“swap”的地址已经是确定的,只不过链接器需要给每个符号加上一个偏移量,使他们能够调整到正确的位置。
4.2符号解析与重定位
4.2.1重定位
编译器把这两条指令的地址部分暂时用地址“0x00000000”和“0xFFFFFFFC”代替着,把真正的地址计算工作留给了链接器。
近址相对位移调用指令:它后面跟的是调用指令的下一条指令的偏移量。
4.2.2重定位表
有一个叫“重定位表”的结构专门用来保存这些与重定位相关的信息。它在ELF文件中往往是一个或多个段。
对于每个要被重定位的ELF段都有一个对应的重定位表,而一个重定位表往往是ELF文件中的一个段,所以其实重定位表也可以叫重定位段,我们统称为重定位表。
重定位表的结构也很简单,它是一个ELF32_Rel结构的数组,每个数组元素对应一个重定位入口:
typedef struct{
Elf32_Addr r_offset;
Elf32_word r_info;
}Elf32_Rel
r_offset 重定位入口的偏移(与段对应得,所以不用指明某段的偏移了吧?!)
对于可重定位文件来说:这个值是该重定位入口所要修正的位置的第一个字节相对于段起始的地址。
r_info 重定位入口的类型和符号。这个成员的低8位表示重定位入口的类型*(决定了地址修正方式),高24位表示重定位入口的符号在符号表中的下标。
4.2.3符号解析
重定位的过程中,每个重定位的入口都是对一个符号的引用,那么当链接器需要对某个符号的引用进行重定位时,它就要确定这个符号的目标地址,这时候链接器就会去查找所有输入目标文件的符号表所组成的全局符号表,找到相对应的符号后进行重定位。
4.2.4指令修正方式
不同的处理器对于地址的格式和方式都不一样。
可以根据不同的重定位入口类型进行正确修正。
这里写图片描述
4.3 COMMON块
目前链接器本身并不支持符号的类型,即变量类型对于链接器来说是透明的。
让我们来分析多个符号定义类型不一致的几种情况,主要分为三种情况:
两个或两个以上强符号类型不一致
有一个强符号类型,其他都是弱符号,出现类型不一致
两个或两个以上若符号类型不一致
早期的Fortman没有动态分配空间的机制,程序员必须事先声明它所需要的临时使用空间大小。Fortman把这种空间叫做COMMON块,当不同的目标文件需要的COMMON块空间大小不一致时,以最大的那块为准。
如果两个弱符号类型冲突,则以输入文件中最大的那个为标准。当然如果有一个符号为强符号,那么最终输出结果中的符号所占空间与强符号相同。
但是如果有弱符号大于强符号,会出现警告。
在目标文件中,编译器为什么不直接把未初始化的全局变量也当作未初始化的局部静态变量一样处理,为他在bss段分配空间,而是将其标记为一个COMMON类型的变量:
当编译器将一个编译单元编译为一个目标文件时,如果包含若符号(未初始化的全局变量就是典型的弱符号),那么该弱符号最终所占空间大小此时是未知的,因为其他编译单元中该符号所占的空间可能更大。所以此时无法为该弱符号在bss段分配空间,因为所需要的空间未知。但是链接器在链接的过程中可以确定弱符号的大小,所以可以在最终输出文件的bss段为其分配空间。
一旦一个未初始化的全局变量不是以COMMON块的形式存在,那么它就相当于一个强符号,如果其他目标文件中还有同一个变量的强符号定义,链接时就会发生符号重复定义错误。
4.4C++相关问题
4.4.1重复代码消除
4.4.2全局构造与析构
4.4.3C++与ABI
4.5静态库链接
一个程序如何做到输入输出呢?最简单的办法是使用操作系统提供的应用程序编程接口(API)。
printf函数对字符串进行一些必要的处理之后,最后会调用操作系统提供的API。各个操作系统下,往终端输出字符串的API都不一样,在Linux下,它是一个“write”的系统调用,而在Windows下它是“writeconsole”系统API。
库是许多目标文件的集合,如libc.a就包含了很多.o文件。
类似的,lib.exe可以列举lib文件中的内容。
为什么静态链接库里面的一个目标文件只包含一个函数?
因为链接器在链接静态库的时候是以目标文件为单位的。如果包含多个函数的话,会导致很多没用的函数一起链接进了输出结果中,造成空间的浪费。
4.6 链接过程控制
4.6.1链接控制脚本
4.6.2 最“小”的程序
4.6.3 使用ld链接脚本
4.6.4 ld链接脚本语法简介
4.7 BFD库

原创粉丝点击