我想知道当我们使用链接器获取它的可执行版本时对象代码会发生什么。
我认为Linux和窗口的链接器作业不一样,我在Linux上。
答案 0 :(得分:2)
目标代码缺乏有关大局的信息。它包含函数的可执行代码,但是对其他外部函数以及全局数据的所有引用都不能成为实际指令的一部分,因为它们的地址是未知的。因此,所有这些引用都留空(例如,在目标代码中只填充零字节)并使用符号名称进行注释。
链接器的工作是查看所有缺少的符号名称并将它们与所有导出的名称(即目标文件提供的函数和全局数据)进行匹配,然后找到每个数据的永久位置,最后重写所有用最终存储数据(函数和全局变量)的实际地址替换零字节的代码。
例如,考虑一下这段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:操作数保持为零,但是有一个注释告诉链接器要填写的符号的名称。
答案 1 :(得分:1)
除了Kerrek的答案之外:链接器的工作在某种程度上取决于操作系统。例如,处理外部引用(来自.so或.dll文件)的方式取决于操作系统,以及不同的段(数据,代码等)如何放置在文件中可能取决于操作系统。
可执行文件的标头(也由链接器生成)是特定于操作系统的,它定义了文件的类型以及在何处查找不同的段。 Linux中的可执行文件以&#34; ELF&#34;标题,在窗口中带有&#34; MZ&#34; header(这些是可以在文件开头找到的标识字符)。
答案 2 :(得分:0)
我认为链接器作业对于Linux和Windows
是不一样的
对Kerrek SB的回答只是一些补充:
链接器在所有操作系统上的工作方式相同。只有对象和二进制文件的文件格式不同。