【发布时间】:2014-03-09 18:14:30
【问题描述】:
我想知道当我们使用链接器获取目标代码的可执行版本时会发生什么。
我认为链接器作业对于 Linux 和窗口都不相同,我在 Linux 上。
【问题讨论】:
标签: assembly compiler-construction linker
我想知道当我们使用链接器获取目标代码的可执行版本时会发生什么。
我认为链接器作业对于 Linux 和窗口都不相同,我在 Linux 上。
【问题讨论】:
标签: assembly compiler-construction linker
对象代码缺少有关全局的信息。它包含函数的可执行代码,但所有对其他外部函数以及全局数据的引用都不能成为实际指令的一部分,因为它们的地址是未知的。因此,所有这些引用都留空(例如,在目标代码中只填充零字节)并用 符号名称 进行注释。
链接器的工作是查看所有丢失的符号名称并将它们与所有导出的名称(即目标文件提供的函数和全局数据)进行匹配,然后为每个数据找到一个永久位置,最后重写所有将零字节替换为最终存储数据(函数和全局变量)的实际地址的代码。
例如,考虑这段 C 代码:
extern int a;
extern int bar(int); // "extern" is redundant here
static int zip(int);
int foo(int x, int y)
{
return 2 * x + 3 * y + zip(x - y) + a * bar(x + y);
}
int zip(int n)
{
return 2 * (n + 1) - (n - 1) / 2;
}
此代码导出一个符号foo,它提供给在此翻译单元中链接的任何人。它还缺少两个符号,a 和 bar。在实现foo 的代码中,对a 和bar 的引用留空,只有在链接器知道这些实际数据所在的位置时才能由链接器填写。
这是 GCC 使用 -O3 为 x86 生成的机器代码:
0000000000000000 <foo>:
0: 89 f9 mov ecx,edi
2: 8d 04 76 lea eax,[rsi+rsi*2]
5: 53 push rbx
6: 29 f1 sub ecx,esi
8: 8d 51 ff lea edx,[rcx-0x1]
b: 8d 1c 78 lea ebx,[rax+rdi*2]
e: 01 f7 add edi,esi
10: 89 d0 mov eax,edx
12: c1 e8 1f shr eax,0x1f
15: 01 c2 add edx,eax
17: d1 fa sar edx,1
19: f7 da neg edx
1b: 8d 44 4a 02 lea eax,[rdx+rcx*2+0x2]
1f: 01 c3 add ebx,eax
21: e8 00 00 00 00 call 26 <foo+0x26>
22: R_X86_64_PC32 bar-0x4
26: 0f af 05 00 00 00 00 imul eax,DWORD PTR [rip+0x0] # 2d <foo+0x2d>
29: R_X86_64_PC32 a-0x4
2d: 01 d8 add eax,ebx
2f: 5b pop rbx
30: c3 ret
注意字节 22 和 29:操作数保留为零,但有一个注释告诉链接器要填充的符号名称。
【讨论】:
此外,Kerrek 的回答是:链接器的工作在某种程度上取决于操作系统。例如,处理外部引用(来自 .so 或 .dll 文件)的方式取决于操作系统,不同段(数据、代码等)在文件中的放置方式也可能取决于操作系统。
可执行文件的标头(也由链接器生成)是特定于操作系统的,它定义了文件的类型以及在哪里可以找到不同的段。 Linux 中的可执行文件以“ELF”标头开头,在 Windows 中以“MZ”标头开头(这些是可以在文件开头找到的标识字符)。
【讨论】:
我认为 Linux 和 Windows 的链接器作业并不相同
只是对 Kerrek SB 的回答的一些补充:
链接器在所有操作系统上的工作方式都相同。只有对象文件和二进制文件的文件格式不同。
【讨论】: