怀疑可执行和可重定位目标文件

时间:2010-05-06 22:12:39

标签: c executable object-files

我写了一个简单的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相对应。我错了什么?

感谢。 巴拉

3 个答案:

答案 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