【问题标题】:Doubts in executable and relocatable object file对可执行和可重定位目标文件的怀疑
【发布时间】:2010-05-06 22:12:38
【问题描述】:

我编写了一个简单的 Hello World 程序。

   #include <stdio.h>
    int main() {
    printf("Hello World");
    return 0;
    }

我想了解可重定位目标文件和可执行文件的外观。 main函数对应的object文件是

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 的内容。我错了什么?

谢谢。 巴拉

【问题讨论】:

    标签: c executable object-files


    【解决方案1】:

    在第一种情况下,看一下指令编码 - 函数地址所在的位置全为零。那是因为对象还没有被链接,所以外部符号的地址还没有被连接。当您最终链接到可执行格式时,系统会在其中粘贴另一个占位符,然后动态链接器最终会在运行时为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 的调用变成的。该重定位将在链接时被注意到并修复。在动态库链接的情况下,重定位和修复可能在程序运行之前无法完全解决,但我希望你会从这个例子中得到想法。

    【讨论】:

      【解决方案2】:

      E8 指令 (call) 中的调用目标指定为相对于当前指令指针 (IP) 值的相对偏移量

      在您的第一个代码示例中,偏移量显然是0x00000000。它基本上说

      call +0
      

      printf 的实际地址尚不清楚,因此编译器只是将 32 位值 0x00000000 作为占位符放在那里。

      这种零偏移的不完整调用自然会被解释为对当前IP值的调用。在您的平台上,IP 是预先递增的,这意味着当执行某些指令时,IP 包含下一条指令的地址。 IE。当地址0xE 处的指令被执行时,IP 包含值0x13。而call +0 自然被解释为对指令0x13 的调用。这就是为什么你会在不完整代码的反汇编中看到0x13

      代码完成后,将占位符0x00000000偏移量替换为代码中printf函数的实际偏移量。偏移量可以是正数(向前)或负数(向后)。在您的情况下,调用时的 IP 是0x4004DF,而printf 函数的地址是0x4003C0。因此,机器指令将包含一个等于0x4003C0 - 0x4004DF 的32 位偏移值,即负值-287。所以你在代码中看到的其实是

      call -287
      

      -287 是二进制的0xFFFFFEE1。这正是您在机器代码中看到的内容。只是你使用的工具反了显示而已。

      【讨论】:

        【解决方案3】:

        调用在 x86 中是相对的,IIRC 如果你有 e8 ,调用位置是 addr+5。

        e1 fe ff ff a 是小端编码的相对跳转。这真的意味着fffffee1

        现在将这个添加到调用指令的地址 + 5: (0xfffffee1 + 0x4004da + 5) % 2**32 = 0x4003c0

        【讨论】:

        • +5是因为相对于调用后的next指令,调用长度为5个字节。
        • x86 上的调用可以是相对的或绝对的。只是E8是相对调用。
        • 是的,我忘记了也有绝对目的地,但它们要么由段指定:选择器,要么是指向要跳转到的地址的指针。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-08-14
        • 2018-10-18
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-08-30
        相关资源
        最近更新 更多