任何一个链接器和加载器的基本工作都非常简单: 将更抽象的名字与更底层的名字绑定起来,好让程序员使用更抽象的名字编写代码。
- 也就是说,它可以将程序员写的一个诸如
getline的名字绑定到“iosys模块内可执行代码的612字节处”或者可以采用诸如“这个模块的静态数据开始的第450个字节处”这样更抽象的数字地址然后将其绑定到数字地址上。 - 链接器对每一个程序的部分地址进行绑定并分配相对地址,加载器完成最后的重定位步骤并赋予的实际地址。
linker: 它扮演的是一个“聚焦”功能的角色,即将相关代码或数据收集到一个段中。
重定位: 为程序不同部分分配加载地址,调整程序中的数据和代码以反映所分配地址
- 在很多系统中,重定位不止进行一次。对于链接器的一种普遍情景是由多个子程序来构建一个程序,并生成一个链接好的起始地址为0的输出程序,各个子程序通过重定位在大程序中确定位置。当这个程序被加载时,系统会选择一个加载地址,而链接好的程序会作为整体被重定位到加载地址。
符号解析: 当通过多个子程序来构建一个程序时,子程序间的相互引用是通过符号进行的。
- 主程序可能会调用一个名为sqrt的计算平方根例程,并且数学库中定义了sqrt例程。链接器通过标明分配给sqrt的地址在库中来解析这个符号,并通过修改目标代码使得call指令引用该地址。
尽管有相当一部分功能在链接器和加载器之间重叠,定义一个仅完成程序加载的程序为加载器,一个仅完成符号解析的程序为链接器是合理的。他们任何一个都可以进行重定位,而且曾经也出现过集三种功能为一体的链接加载器。
链接器和加载器共有的一个重要特性就是他们都会修改目标代码,他们也许是唯一比调试程序在这方面应用更为广泛的程序。这是一个独特而强大的特性,而且细节非常依赖于机器的规格,如果做错的话就会引发令人困惑的bug。
就象编译或汇编一样,链接基本上也是一个两遍的过程。链接器将一系列的目标文件、库、及可能的命令文件作为它的输入,然后将输出的目标文件作为产品结果,此外也可能有诸如加载映射信息或调试器符号文件的副产品。如图所示。
- 每个输入文件都包含一系列的段(segments),即会被连续存放在输出文件中的代码或数据块。每一个输入文件至少还包含一个符号表(symbol table)。有一些符号会作为导出符号,他们在当前文件中定义并在其他文件中使用,通常都是可以在其它地方被调用的当前文内例程的名字。其它符号会作为导入符号,在当前文件中使用但不在当前文件中定义,
通常都是在该文件中调用但不存在于该文件中的例程的名字。 - 当链接器运行时,会首先对输入文件进行扫描,得到各个段的大小,并收集对所有符号的定义和引用。它会创建一个列出输入文件中定义的所有段的段表,和包含所有导出、导入符号的符号表。
- 利用第一遍扫描得到的数据,链接器可以为符号分配数字地址,决定各个段在输出地址空间中的大小和位置,并确定每一部分在输出文件中的布局。
- 第二遍扫描会利用第一遍扫描中收集的信息来控制实际的链接过程。它会读取并重定位目标代码,为符号引用替换数字地址,调整代码和数据的内存地址以反映重定位的段地址,并将重定位后的代码写入到输出文件中。通常还会再向输出文件中写入文件头部信息,重定位的段和符号表信息。如果程序使用了动态链接,那么符号表中还要包含运行时链接器解析动态符号时所需的信息。在很多情况下,链接器自己将会在输出文件中生成少量代码或数据,例如用来调用覆盖中或动态链接库中的例程的“胶水代码”,或在程序启动时需要被调用的指向各初始化例程的函数指针数组。
- 在链接器运行时,链接器会识别出解析未定义符号所需的共享库,但是链接器会在输出文件中标明用来解析这些符号的库名称,而不是在链接时将他们链入程序,这样可以在程序被加载时进行共享库绑定。
链接器和加载器的核心动作是重定位和代码修改。
- 作为链接过程的一部分,链接器会修改目标代码以反映实际分配的地址。例如,考虑如下这段将变量a中的内容通过寄存器eax移动到变量b的x86代码片段。
mov a,%eax
mov %eax,b
如果a定义在同一文件的位置0x1234,而b是从其它地方导入的,那么生成的代码将会是:
A1 34 12 00 00 mov a,%eax
A3 00 00 00 00 mov %eax,b
每条指令包含了一个字节的操作码和其后4个字节的地址。第一个指令有对地址1234的引用(由于x86使用从右向左的字节序,因此这里是逆序),而第二个指令由于b的位置是未知的因此引用位置为0。
现在想象链接器将这段代码进行链接,a所属段被重定位到了0x10000,b最终位于地址0x9A12。则链接器会将代码修改为:
A1 34 12 01 00 mov a,%eax
A3 12 9A 00 00 mov %eax,b
也就是说,链接器将第一条指令中的地址加上0x10000,现在它所标识的a的重定位地址就是0x11234,并且也补上了b的地址。虽然这些调整影响的是指令,但是目标文件中数据部分任何相关的指针也必须修改。