我们知道,可执行文件中的代码段和数据段都是由输入的目标文件中合并而来的。那么链接器是如何将它们的各个段合并到输出文件?或者说,输出文件中的空间如何分配给输入文件的。
按序叠加
最简单的方案就是直接将各个目标文件依次合并,但是这样做输出文件将会有很多零散的段。这种做法非常浪费空间,因为每个段都需要有一定的地址和空间对齐要求,造成空间大量的内部碎片。
相似段合并
一个更实际的方法是将相同性质的段合并到一起。
“地址和空间”其实有两个含义:第一个是在输出的可执行文件中的空间;第二个是在装载后的虚拟地址中的虚拟地址空间。
对于有实际数据的段,它们在文件中和虚拟地址中都要分配空间,因为它们在这两者中都存在;
对于".bss"这样的段,分配空间的意义值局限于虚拟地址空间,因为它在文件中并没有内容。
使用这种方法的链接器一般都采用一种叫两步链接(Two-pass Linking)的方法。
第一步 空间与地址分配:扫描所有的输入目标文件,获得它们的各个段的长度、属性和位置,并且将输入目标文件中的符号表中所有的符号定义和符号引用收集起来,统一放到一个全局符号表。这一步中,链接器将能够获得所有输入目标文件的段长度,并且将它们合并,计算出输出文件中各个段合并后的长度与位置,并建立映射关系。
第二步 符号解析与重定位:使用上面第一步中收集到的所有信息,读取输入文件中段的数据、重定位信息,并且进行符号解析与重定位、调整代码中的地址等。事实上第二步是链接过程的核心,特别是重定位过程。
符号地址的确定
当前面一步完成之后,链接器开始计算各个符号的虚拟地址。因为各个符号在段内的相对位置是固定的,只不过链接器需要给每个符号加上一个偏移量,使它们能够调整到正确的虚拟地址。