我写了一个简单的Hello World程序。
#include <stdio.h>
int main() {
printf("Hello World");
return 0;
}
我想了解可重定位目标文件和可执行文件的外观。 与主函数对应的目标文件是
0000000000000000 <main>:
0: 55 push %rbp
1: 48 89 e5 mov %rsp,%rbp
4: bf 00 00 00 00 mov $0x0,%edi
9: b8 00 00 00 00 mov $0x0,%eax
e: e8 00 00 00 00 callq 13 <main+0x13>
13: b8 00 00 00 00 mov $0x0,%eax
18: c9 leaveq
19: c3 retq
这里printf的函数调用是callq 13.有一点我不明白为什么它是13.这意味着在地址13调用函数,对吧? 13有下一条指令,对吧?请解释一下这是什么意思??
与main对应的可执行代码是
00000000004004cc <main>:
4004cc: 55 push %rbp
4004cd: 48 89 e5 mov %rsp,%rbp
4004d0: bf dc 05 40 00 mov $0x4005dc,%edi
4004d5: b8 00 00 00 00 mov $0x0,%eax
4004da: e8 e1 fe ff ff callq 4003c0 <printf@plt>
4004df: b8 00 00 00 00 mov $0x0,%eax
4004e4: c9 leaveq
4004e5: c3 retq
这是callq 4003c0。但二进制指令是e8 e1 fe ff ff。没有任何东西与4003c0相对应。我错了什么?
感谢。 巴拉
答案 0 :(得分:7)
在第一种情况下,看一下指令编码 - 它是函数地址所有的零。那是因为该对象尚未链接,因此外部符号的地址尚未连接。当您执行可执行格式的最终链接时,系统会在其中添加另一个占位符,然后动态链接器最终会在运行时为printf()
添加正确的地址。这是我写的“Hello,world”程序的一个简单例子。
首先,反汇编目标文件:
00000000 <_main>:
0: 8d 4c 24 04 lea 0x4(%esp),%ecx
4: 83 e4 f0 and $0xfffffff0,%esp
7: ff 71 fc pushl -0x4(%ecx)
a: 55 push %ebp
b: 89 e5 mov %esp,%ebp
d: 51 push %ecx
e: 83 ec 04 sub $0x4,%esp
11: e8 00 00 00 00 call 16 <_main+0x16>
16: c7 04 24 00 00 00 00 movl $0x0,(%esp)
1d: e8 00 00 00 00 call 22 <_main+0x22>
22: b8 00 00 00 00 mov $0x0,%eax
27: 83 c4 04 add $0x4,%esp
2a: 59 pop %ecx
2b: 5d pop %ebp
2c: 8d 61 fc lea -0x4(%ecx),%esp
2f: c3 ret
然后重新安置:
main.o: file format pe-i386
RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
00000012 DISP32 ___main
00000019 dir32 .rdata
0000001e DISP32 _puts
正如您所看到_puts
的重定位,printf
的调用就是这样。该重定位将在链接时被注意并被修复。在动态库链接的情况下,在程序运行之前,重定位和修正可能无法完全解决,但我希望你能从这个例子中得到想法。
答案 1 :(得分:7)
E8
指令(call
)中的调用目标被指定为当前指令指针(IP)值的 relative offset 。
在您的第一个代码示例中,偏移量显然是0x00000000
。它基本上说
call +0
printf
的实际地址尚不可知,因此编译器只将32位值0x00000000
作为占位符。
这种具有零偏移的不完整呼叫自然会被解释为对当前IP值的调用。在您的平台上,IP是预先递增的,这意味着当执行某些指令时,IP包含下一条指令的地址。即当执行地址0xE
的指令时,IP包含值0x13
。而call +0
自然被解释为对指令的调用0x13
。这就是您在不完整代码的反汇编中看到0x13
的原因。
代码完成后,占位符0x00000000
偏移量将替换为代码中printf
函数的实际偏移量。偏移可以是正(向前)或负(向后)。在您的情况下,通话时的IP为0x4004DF
,而printf
功能的地址为0x4003C0
。因此,机器指令将包含等于0x4003C0 - 0x4004DF
的32位偏移值,该值为负值-287
。所以你在代码中看到的实际上是
call -287
-287
是二进制的0xFFFFFEE1
。这正是您在机器代码中看到的内容。只是您正在使用的工具向后显示它。
答案 2 :(得分:5)
如果你有e8,呼叫在x86,IIRC是相对的,呼叫位置是addr + 5.
e1 fe ff ff
a是小端编码的相对跳跃。这实际上意味着fffffee1
。
现在将其添加到呼叫指令+ 5的地址:
(0xfffffee1 + 0x4004da + 5) % 2**32 = 0x4003c0