程序员的自我修养(链接、装载与库)--摘录与汇总(一)

指令和地址分开原因:(P59

1、独立权限,防止程序指令非预期改写

程序装载后,数据和指令分别被映射到两个虚存区域。由于数据区域对于进程来说是可读写的,而指令区域对于进程来说是只读的,所以这两个虚存区域的权限可以被分别设置为可读写和只读。这样可以防止程序的指令被有意或者无意地改写。

2、提高 CPU 缓存命中率

指令区和数据区的分离有利于提高程序的局部性。

3、节省内存占用

等系统中运行着多个该程序的副本时,他们的指令都是一样的,所以内存中只需要保存一份该程序的指令部分。而每个副本进程的数据区域是不一样的,他们是进程私有的。

.bss 段和 .data 段 (P59、P65、P66、P111

  • .bss 段只是为“未初始化的全局变量和局部静态变量预留位置,它并没有内容,所以它在文件中不占据空间”
  • .data 段保存的是“已经初始化了的全局静态变量和局部静态变量

注:有些编译器会将全局的未初始化变量存放在目标文件的.bss 段,有些则不存放,只是预留一个未定义的全局变量符号,等到最终链接成可执行文件的时候再在.bss 段分配空间。原则上讲,我们可以简单地把它当做全局未初始化变量存放在.bss段。值得一提的是编译单元内部可见的静态变量的确是存放在.bss 段的

原因:

1、“未初始化的全局变量”在目标文件中并未放到 .bss 段中,而是标记为一个 COMMON 类型的变量,这是因为“未初始化的全局变量”是一个弱符号,其最终占用的大小未知,所以编译器此时无法为该弱符号在 BSS 段分配空间
,也即不能放在 .bss 段中,而链接器在链接过程中就能确定弱符号的大小了,所以“未初始化的全局变量”可以放在最终输出文件的 .bss 段中

现在我们再回头总结性地思考关于未初始化的全局变量的问题:在目标文件中,编译器
为什么不直接把未初始化的全局变量也当作未初始化的局部静态变量一样处理,为它在 BSS
段分配空间,而是将其标记为一个 COMMON 类型的变量?
通过了解链接器处理多个弱符号的过程,我们可以想到,当编译器将一个编译单元编译
成目标文件的时候,如果该编译单元包含了弱符号(末初始化的全局变量就是典型的弱符
号),那么该弱符号最终所占空间的大小在此时是未知的,因为有可能其他编译单元中该符
号所占的空间比本编译单元该符号所占的空问要大。所以编译器此时无法为该弱符号在 BSS
段分配空间,因为所须要空间的大小末知。但是链接器在链接过程中可以确定弱符号的大小,
因为当链接器读取所有输入目标文件以后,任何一个弱符号的最终大小都可以确定了,所以
它可以在最终输出文件的 BSS 段为其分配空间。所以总体米看,未初始化全局变量最终还
是被放在 BSS 段的。
关于多个文件中出现同一个变量的多个定义的原因,还有一种说法是由于早期C 语言
程序员粗心大意,经常忘记在声明变量时在前面加上 〝extern”关键字,使得编译器
会在多个目标文件中产生同一个变量的定义。为了解决这个问题,编译器和链接器干
脆就把未初始化的变量都当作 COMMON 类型的处理。

2、两个或两个以上弱符号类型不一致的处理:
当不同目标文件需要的空间大小不一致时,以最大的那块为准。P111在这里插入图片描述
P92
在这里插入图片描述

.bss 段在目标文件和可执行文件中并不占用文件的空间,但是它在装载时占用地址空间(P100

输出文件中的空间如何分配给输入文件(链接过程的空间与地址分配)(P100

相似段合并
在这里插入图片描述

在链接之前,目标文件中的所有段的 VMA(虚拟地址 Virtual Memory Address) 都是 0,因为虚拟空间还没有被分配,所以他们默认都为 0,等到链接之后,可执行文件中的各个段都被分配到了相应的虚拟地址。

整个链接过程前后,目标文件各段的分配、程序虚拟地址如下图:
在这里插入图片描述

两步链接 – 链接器虚拟空间分配策略(P100

  • 空间与地址分配

收集段信息,合并段,建立映射

  • 符号解析与重定位

符号解析,重定位,调整代码中的地址

在这里插入图片描述

符号地址确定(P103

各个段的虚拟地址已确定,各个符号在段内的相对位置是固定的,所以各个符号的虚拟地址就能算出来(段的虚拟地址+符号在段内的相对位置)
注:经过链接之后,各个段的虚拟地址已经确定,这时候链接器就可以计算各个符号的虚拟地址,因为各个符号在段内的相对位置是固定的,所以这些符号的底子也已经是确定的,只不过链接器需要给每个符号加上一个偏移量,使它们能够调整到正确的虚拟地址。

重定位(P103-P106

概括:当源代码在被编译成目标文件时,编译器并不知道源代码引用的外部符号的地址,因为它们定义在其他目标文件中,所以编译器暂时把地址 0 看做是它们的地址,而链接器经过地址和空间分配之后就可以确定所有符号的虚拟地址了,那么链接器就可以根据符号的地址对每个需要重定位的指令进行地址修正。
那链接器怎么知道哪些指令的地址需要被修正、怎么修正呢?这就引出了重定位表的概念

反汇编结果解读(P104

在这里插入图片描述

重定位表 (P106、P107

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

objdump -r a.o 命令能够查看此目标文件中所有引用到外部符号的地址

  • .rel.text: 保存代码段(.text)的重定位表

RELOCATION RECORDS FOR [.text]

  • .rel.data: 保存数据段(.data)的重定位表

对于可重定位的 ELF 文件来说,它必须包含有重定位表,用来描述如何修改相应的段
里的内容。对于每个要被重定位的 ELF 段都有一个对应的重定位表,而一个重定位表往往就是 ELF 文件中的一个段,所以其实重定位表也可以叫重定位段,我们在这里统一称作重定位表。比如代码段“text”如有要被重定位的地方,那么会有一个相对应叫“ rel.text” 的段保存了代码段的重定位表:如果代码段“.data” 有要被重定位的地方,就会有一个相对应“rel.data”的段保存了数据段的重定位表。我们可以使用 objdump 来查看目标文件的重定位表

每个要被重定位的地方叫一个重定位入口(Relocation Entry),重定位入口的偏移表示该入口在要被重定位的段中的位置

2、重定位表结构

结构体数组
结构体格式如下:

typedef struct {
  Elf32_Addr r_offset; // 重定位入口的偏移
  Elf32_Word r_info; // 重定位入口(低 8 位:类型;高 24 位:重定位入口的符号在符号表中的下标)
}

符号解析

符号解析的目的是将每个符号引用刚好与一个符号的定义关联起来(本句非书上内容摘自网络

通过前面指令重定位的介绍,我们可以更加深层次地理解为什么缺少符号的定义会导致链接错误。其实重定位过程也伴随着符号的解析过程,每个目标文件都可能定义一些符号,也可能引用到定义在其他目标文件的符号。重定位的过程中,每个重定位的入口都是对一个符号的引用,那么当链接器须要对某个符号的引用进行重定位时,它就要确定这个符号的目标地址。这时候链接器就会去查找由所有输入目标文件的符号表组成的全局符号表,找到相应的符号后进行重定位

在这里插入图片描述

这种未定义的符号(UND)都是因为该目标文件中有关于它们的重定位项。所以在链接器扫描完所有的输入目标交件之后所有这些未定义的符号都应该能够在全局符号表中找到,否则链接器就报符号未定义错误

指令修正方式(P109)

不同的处理器指令对于地址的格式和方式都不一样。寻址模式也是千差万别,概况下来有以下两种

  • 绝对寻址
    修正后的地址为该符号的实际地址
  • 相对寻址
    修正后的地址为符号距离被修正位置的地址差

重复代码消除(P113、P114

哪些情况会产生重复的代码:

模版、外部内联函数、虚函数表都有可能在不同的编译单元里生成相同的代码

解法:

将每个模版的实例代码都单独地存放在一个端里,每个段只包含一个模版实例,这样别的编译单元也会生成相同的名字,这样链接器在最终链接时就可以区分这些相同的模版实例段,然后将他们合并入最后的代码段

此种解法存在的问题:相同名称的段可能拥有不同的内容,这种情况链接器可能会随意选择其中任何一个副本作为链接的输入

函数级别的链接:

将每个函数或者变量分别保持到独立的段中
当链接器要用到某个函数时,它就将它合并到输出文件中,对于那些没有用到的函数则将他们抛弃(减小包大小,但是会减慢编译和链接过程)

ABI(Application Binary Interface)(P115-P117

是什么:符号修饰标准、变量内存布局、函数调用方式等这些跟可执行二进制兼容性相关的内容
干啥的:使两个编译器编译出来的目标文件能够相互链接

API(Application Programming Interface)

  • API 源代码级别的接口
  • ABI 二进制层面的接口,兼容程度比 API 更为严格

静态库链接

静态库可以简单的看成是一组目标文件的集合,也就是很多目标文件经过压缩打包之后形成的一个文件。(P118
在这里插入图片描述

链接过程:将所有的目标文件、库文件作为输入,输出可执行文件的过程(P126
控制链接过程是控制输入段如何变成输出段,比如哪些输入段要合并一个输出段,哪些输入段要丢弃;指定输出段的名字、装载地址、属性等(P127

可以通过 ld 的 -s 参数禁止链接器产生符号表,或者使用 strip 命令去除程序中的符号表P128 对于程序构建时减包大小有用


段地址对齐

在这里插入图片描述

Linux 进程地址空间布局(内核版本2.4.x)

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

杨筱毅

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

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

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

打赏作者

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

抵扣说明:

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

余额充值