前言
上一篇其实是对一个章节主线内容的小小总结,之所以打算写这些简单的总结,是因为发现自己一本书即便看了两三遍,也还是不能系统性的将知识点串联在一起。与其花时间往往复复的翻看,不如把每章的重点简要整理下。这篇是关于生成可执行文件的一个核心内容:链接,从静态链接开始。
简介
可执行文件(即程序)是由链接器将输入的目标文件加工再组合而成。由上篇内容的ELF文件结构描述基本也可以了解到,生成可执行文件的本质是将各输入目标文件各个相似段的内容进行合并,比如最重要的代码段和数据段,这个合并的过程即是链接。
链接有静态动态之分,实质上工作原理和内容都相差无几,区别只在于链接的时间点。
静态链接是先将所有关联的目标文件全部链接成完整的可执行文件,之后该执行文件再整体装载运行,即程序运行;
动态链接则是
先装载运行可执行文件,运行过程中操作系统发现该执行文件不完整,需要依赖其他目标文件,于是再对关联目标文件进行链接,链接完成后继续运行。
对于整个链接的过程,主要分为以下两个步骤:1,空间与地址分配;2,符号解析与重定位 。
空间与地址分配
在这一步中,链接器会扫描所有的输入目标文件,获取它们的各个段的长度、属性和位置,并且将输入目标文件的符号表中所有符号定义和引用进行收集,统一放入一个全局符号表。这时候,链接器将能够获得所有输入目标文件的段长度,并将它们相同的段进行合并,计算出最终所要生成文件中的各个合并段的长度与位置,为各目标文件分配地址与空间并建立映射关系。
经过这一步,各输入文件中的各个段在链接后生成的执行文件的虚拟地址可以确定,又由于各个符号在其合并后段内的相对位置也是固定的,这就意味着诸如“main”等函数或变量可以根据它们所在段的虚拟地址再加上其所在段内的偏移量就能确定其本身的虚拟地址。
补充:
链接器为目标文件分配地址与空间。这里的“地址与空间”包含两个含义:
链接器为目标文件分配地址与空间。这里的“地址与空间”包含两个含义:
1) 在输出的可执行文件中的空间;
2) 在程序装载后的虚拟地址中的虚拟空间;
对于有实际数据的段,比如“.text”和“.data”,它们在文件中和虚拟地址中都要分配空间;而对于“.bss”段,因为它没有实际内容,只是一个标识,分配空间则只局限于虚拟地址空间。
2) 在程序装载后的虚拟地址中的虚拟空间;
对于有实际数据的段,比如“.text”和“.data”,它们在文件中和虚拟地址中都要分配空间;而对于“.bss”段,因为它没有实际内容,只是一个标识,分配空间则只局限于虚拟地址空间。
符号解析与重定位
(这里会使用第一步中收集到的所有信息,读取输入目标文件中各个段的数据、重定位信息,并且进行符号解析与重定位、调整代码中的地址等。第二步是链接过程的核心,尤其是重定位的过程。)
经过空间与地址分配的阶段之后,链接器就开始进入符号解析和重定位的步骤。此时,所有符号的虚拟地址可以确定,所以链接器会根据符号的虚拟地址对每个需要重定位的指令进行修正。
补充:
哪些是需要重定位的指令?
在上篇博文的简单介绍中,对于可重定位的ELF文件中会包含有重定位表(一个或多个段),分别保存着相应代码段或者数据段中需要修改重新定位的符号。一般来说,需要进行重定位的指令指的是引用了外部模块的符号,通俗地说,就是引用了在本文件以外定义的变量或函数。
重定位的过程,伴随着符号的解析。因为每个需要重定位的符号都意味着涉及到该符号的外部引用。于是,当链接器需要确定该符号的目标地址时,链接器就会去查找所有目标文件的符号表组成的全局符号表,再找到相应的符号,之后才能进行重定位,这个过程就是符号解析的过程。
静态库
补充:
为什么要有静态库?
因为多个零散的目标文件直接提供给使用者,很大程度上会造成文件传输、管理和组织上的不方便,所以人们使用压缩程序将目标文件压缩,并且对其进行编号和索引,便于查找和检索,于是形成了静态库文件。