主页 程序员的自我修养 ——链接、装载与库读书笔记(三)
Post
Cancel

程序员的自我修养 ——链接、装载与库读书笔记(三)

此系列为我学习经典的读书笔记,目的是理清知识脉络帮助理解记忆,内容深度、质量远远不及原书。如果您对相关知识感兴趣,强烈建议阅读原书程序员的自我修养

带着问题学习

  • 静态链接中,输入的目标文件的各个段是如何被合并到输出文件的
  • 链接器如何为他们分配在输出文件中的空间和地址
  • 什么是 COMMON 块,未初始化的全局/静态变量为什么要使用它

空间与地址分配

对于链接器而言,链接主要过程就是将输入的目标文件加工合并成一个输出文件。那么,输出文件中的空间是如何分配给输入文件的呢?

按序叠加

简单策略是按顺序叠加起来,但是问题同样明显。如果目标文件很多的话,输出文件会产生大量零散的段,造成内存空间大量的内部碎片,所以这不是很好的方案。

相似段合并

更实际的方法是将相似的段合并在一起,比如将所有目标文件的 “.text” 合并在一起,然后是 “.data” 段,“.bss” 等。

“链接器为目标文件分配空间和地址”中的“空间和地址”有两个意思:一个是在输出的可执行文件的空间;第二个是在装载后的虚拟地址中的虚拟地址空间。我们这里谈到的空间分配只关注于虚拟地址空间分配。

整个链接过程分两步:

第一步 空间与地址 这一步扫描所有输入文件,获取各个段的长度、属性和位置,并将所有符号表中的定义与引用统一放到全局符号表。

第二步 符号解析与重定位 使用第一步收集到的信息进行符号解析与重定位、调整代码中的地址。这一步是链接过程的核心,特别是重定位。

符号解析与重定位

完成空间和地址的分配后,链接器就进入了符号解析与重定位的步骤,这也是静态链接的核心内容。

重定位

链接器根据符号地址对每个需要重定位的指令进行地址修正。

重定位表

重定位表(Relocation Table)是专门保存有关重定位信息的结构。

符号解析

重定位的过程中伴随着符号解析,每个目标文件都可能定义一些符号,也可能引用到定义在其他目标文件的符号。当链接器扫描完所有输入目标文件后,所有未定义的符号都应该能够在全局符号表中找到,否则就会报符号未定义错误。

指令修正方式

不同的处理器指令对于地址的格式和方式都不一样,比如绝对寻址相对寻址,他们的主要区别就是绝对寻址修正后的地址为该符号的实际地址,相对寻址修正后的地址为符号距被修正位置的地址差

COMMON 块

由于弱符号机制允许同一个符号定义存放在多个文件,所以一旦出现了类型不同的弱符号定义,就需要引入一种 COMMON 块机制。类似事先声明的临时占用空间大小,最终合并时候以最大的为准。

现代链接机制在处理弱符号的时候,就是采用类似 COMMON 块机制。编译器将未初始化的全局变量定义作为弱符号处理,最终链接后输出文件中,以最大的那个为准。

链接过程控制

绝大部分情况下,我们使用链接器默认的链接规则对目标文件进行链接,但对于一些特殊的程序,比如操作系统内核、BIOS,往往受限于特殊环境,需要一些特殊设置,比如指定输出文件的各个段虚拟地址、段的名称、存放位置等。

链接器提供的控制整个链接过程的方法一般分三种:

  • 使用命令行给链接器指定参数
  • 将链接指令存放在目标文件中
  • 使用链接控制脚本

总结

经过刚才的梳理,可以回答开头的问题了吗,如果还不能,或者想了解更多,欢迎阅读原书。如果发现了我的错误与纰漏,也请不吝赐教,十分感谢。

该博客文章由作者通过 CC BY 4.0 进行授权。

程序员的自我修养 ——链接、装载与库读书笔记(二)

程序员的自我修养 ——链接、装载与库读书笔记(四)