当您编译源文件时,它通常被编译器/汇编器分成几个部分。作为一个假设的示例,假设使用以下部分:
- .text - 包含所有可执行代码
- .const - 包含常量数据
- .data - 包含读/写初始化数据
- .bss - 包含读/写未初始化的数据
在单个源文件中,编译器/汇编器将适当的内容分配给适当的部分,并给出该部分中使用的符号从零开始的偏移量。
例如:
int i;
const j = 3;
int k = 4;
int l;
int main()
{
return 1;
}
这可能会产生以下符号表:
Symbol Section Offset
i .bss 0
j .const 0
k .data 0
l .bss 4
main .text 0
在目标文件中,除了符号表之外,还可以保存各段的数据。在此示例中,.text 部分将包含“return 1”的目标代码,const 部分将包含 3,data 部分将包含 4。.bss 部分不需要位于目标文件中,因为变量尚未初始化。
链接器可能做的第一件事是连接输入目标文件的所有部分并相应地调整符号偏移量。
现在我们来谈谈所谓的“重定位”或“地址绑定”。假设在假设的系统中,可执行代码从地址 0x1000 开始。我们还假设程序的数据部分希望在可执行代码之后的偶数页边界处开始。链接器将分配 0x1000 作为连接的 .text 部分的基础并调整所有符号。然后以类似的方式将 .const、.data 和 .bss 节的基部放置在内存中的适当位置。
有时,某个部分中会有符号引用。链接器必须更新这些引用以反映所引用符号的最终位置。目标文件可能包含“重定位记录”,如下所示
section offset symbol
.text 0x1234 foo
链接器将转到每个部分中的每个偏移量并更新那里的值以反映最终的符号值。
完成所有这些后,生成的“绝对”目标文件可以加载到内存中(当然是在正确的位置!)并执行。