静态链接(链接、装载与库04)

静态链接

空间与地址分配

对于连接器来说,整个链接过程中,它就是将几个输入目标文件加工后合并成一个输出文件。那么链接过程中将产生第一个问题:对于多个输入目标文件,链接器如何将它们的各个段合并到输出文件?或者说,输出文件中的空间如何分配给输入文件?

按序叠加

一个最简单的方案就是将输入的目标文件按照次序叠加起来。

在有很多输入文件的情况下,输出文件将会有很多零散的段。这种做法非常浪费空间,因为每个段都需要有一定的地址和空间对齐要求。

相似段合并

将相同性质的段合并到一起,比如将所有输入文件的“.text"合并到输出文件的”.test“段。

链接器为目标文件分配地址和空间

“地址和空间”有两个含义:

第一个是在输出的可执行文件中的空间

第二个是在装载后的虚拟地址中的虚拟地址空间

对于有实际数据的段,如“.text .data"来说,它们在文件中和虚拟地址中都要分配空间,因为它们在两者中都存在。

而对于“.bss”这样的段来说,分配空间的意义只局限于虚拟地址空间,因为它在文件中没有内容。

事实上,我们这里谈到的空间分配只关注虚拟地址空间的分配,因为这个关系到链接器后面的关于地址计算的步骤,而可执行文件本身的空间分配与链接过程关系并不是很大。

现在的链接器空间分配的策略基本上都采用第二种,使用这种方法的链接器一般都采用一种叫两步链接的方法。

两步链接
第一步 空间与地址分配

扫描所有的输入目标文件,获得它们的各个段的长度、属性和位置,符号表中所有的符号定义和符号引用收集放到全局符号表。

这一步中,链接器将能够获得所有输入目标文件的段长度,将它们合并,计算输出文件中各个段合并后的长度与位置,并建立映射关系。

第二步 符号解析与重定位

使用第一步中收集到的所有信息,读取输入文件中段的数据、重定位信息,并且进行符号解析与重定位、调整代码中的地址等。

查看各个段的属性
objdump -h a.o

VMA和LMA

VMA表示虚拟地址

LMA表示加载地址

在Linux下,ELF可执行文件默认从地址0x08048000开始分配。

符号地址的确定

在第一步的扫描和空间分配阶段,链接器按照空间分配方法进行分配,这时候输入文件中的各个段在链接后的虚拟地址就已经确定了。

当第一步完成之后,链接器开始计算各个符号的虚拟地址。因为各个符号在段内的相对位置是固定的,所以这时候其实“main”的地址已经是确定的了,只不过链接器需要给每个符号加上一个偏移量,使它们能够调整到正确的虚拟地址。

符号解析与重定位

重定位

当源码编译成目标文件时,编译器并不知道外部符号的地址,因为它们定义在其他目标文件中,所以编译器就暂时把地址0看作是外部符号的地址。这个地址就是重定位入口,需要保存在重定位表中,给链接器进行重定位,把地址0修改为正确的地址。

重定位表

对于可重定位的ELF文件来说,它必须包含有重定位表,用来描述如何修改相应的段里的内容。

对于每个要被重定位的ELF段都有一个对应的重定位表,而一个重定位表往往就是ELF文件中的一个段,所以其实重定位表也可以叫重定位段。

查看目标文件重定位表
objdump -r a.o

可以查看到"a.o"所有引用到外部符号的地址。

每个要被重定位的地方叫一个重定位入口,可以看到该文件有两个重定位入口。

重定位入口的偏移表示该入口在要被重定位的段中的位置。

“RELOCATION RECORDS FOR [.text]”表示这个重定位表示代码段的重定位表,所以偏移表示代码段中需要被调整的位置。

重定位表结构
typdef struct {
	Elf32_Addr r_offset;
	Elf32_Word r_info;
}Elf32_Rel;

符号解析

重定位的过程中,每个重定位的入口都是对一个外部符号的引用,那么当链接器需要对某个符号的引用进行重定位时,它就要确定这个符号的目标地址。

这时候链接器就会去查找由所有输入目标文件的符号表组成的全局符号表,找到相应的符号后进行重定位。

COMMON块

现在的编译器和链接器都支持一种叫COMMON块的机制。这种机制来源于Fortran,早期没有动态分配空间的机制,程序员必须先声明它所需要的临时使用空间的大小。Fortran把这种空间叫COMMON快,当不同目标文件需要的COMMON块空间大小不一致时,以最大那块为准。

当符号都是弱符号时,就需要COMMON类型的链接规则,选择COMMON块大的符号。

GCC的“-fno-common”允许把所有未初始化的全局变量不以COMMON块的形式处理。或者使用“__attribute__”扩展

int global __attribute__((nocommon));

一旦一个未初始化的全局变量不是以COMMON块的形式存在,那么它就相当于一个强符号。

静态库链接

一个静态库可以简单看成一组目标文件的集合,即很多目标文件经过压缩打包后形成的一个文件。比如Linux中最常用的C语言静态库libc位于/usr/lib/libc.a。

ld链接器会自动寻找所有需要的符号及它们所在的目标文件,将这些目标文件从“libc.a"中”解压“出来,最终将它们链接在一起成为一个可执行文件。

链接过程控制

链接控制脚本

链接器一般提供三种方法控制链接过程。

  • 使用命令行来给链接器指定参数,如ld的-o -e
  • 将链接指令存放在目标文件里,编译器经常会通过这种方法向链接器传递指令。
  • 使用链接控制脚本

用-T指定链接脚本

ld -T link.script
ld链接脚本语法简介

链接脚本由一系列语句组成,语句分两种,一种是命令语句,一种是赋值语句。

ld链接脚本语法与C相似之处:

  • 语句之间使用分号作为分隔符
  • 表达式与运算符
  • 注释和字符引用/**/
常用的命令语句

SECTIONS命令
SECTIONS
{
	...
	secname : {contents}
	...
}

secname表示输出段的段名,secname后面必须跟一个空格。

大括号里面的contents描述了一套规则和条件,表示符合这种条件的输入段将合并到这个输出段中。输出段名的命名方法必须符号输出文件格式要求。

条件写法:

filename(sections)

filename 表示输入文件名,sections表示输入段名。

file1.o(.data)表示输入文件名为file1.o的文件中名为.data的段符合条件

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

omnibots

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值