7

簡単なHelloWorldプログラムを作成しました。

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

再配置可能オブジェクトファイルと実行可能ファイルがどのように見えるかを理解したかったのです。main関数に対応するオブジェクトファイルは

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です。私が理解していないことの1つは、なぜそれが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 

これがcallq4003c0です。ただし、バイナリ命令はe8 e1 feffffです。4003c0に対応するものはありません。私が間違っているのは何ですか?

ありがとう。バラ

4

3 に答える 3

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変わったものです。その再配置はリンク時に通知され、修正されます。ダイナミックライブラリリンクの場合、プログラムが実行されるまで再配置と修正が完全に解決されない可能性がありますが、この例からアイデアが得られることを願っています。

于 2010-05-06T22:26:41.573 に答える
7

E8命令( )内の呼び出しのターゲットは、現在の命令ポインター(IP)値からの相対オフセットcallとして指定されます。

最初のコードサンプルでは、​​オフセットは明らかに0x00000000です。それは基本的に言う

call +0

の実際のアドレスはprintfまだ不明であるため、コンパイラは32ビット値0x00000000をプレースホルダーとして配置するだけです。

オフセットがゼロのこのような不完全な呼び出しは、当然、現在のIP値への呼び出しとして解釈されます。プラットフォームでは、IPは事前にインクリメントされます。つまり、ある命令が実行されると、IPには次の命令のアドレスが含まれます。つまり、アドレスでの命令0xEが実行されると、IPには値が含まれます0x13。そして、これcall +0は当然、命令の呼び出しとして解釈されます0x130x13これが、不完全なコードの逆アセンブルでそれがわかる理由です。

コードが完了すると、プレースホルダーオフセットは、コード内の関数0x00000000の実際のオフセットに置き換えられます。printfオフセットは、正(順方向)または負(逆方向)にすることができます。あなたの場合、呼び出し時のIPはですが、機能0x4004DFのアドレスはです。このため、マシン命令には、負の値である、に等しい32ビットのオフセット値が含まれます。つまり、コードに表示されるのは実際にはprintf0x4003C00x4003C0 - 0x4004DF-287

call -287

-2870xFFFFFEE1バイナリです。これはまさにあなたのマシンコードに見られるものです。使用しているツールが逆方向に表示しただけです。

于 2010-05-06T23:01:48.920 に答える
5

呼び出しはx86で相対的であり、e8がある場合はIIRCであり、呼び出し場所はaddr+5です。

e1 fe ff ffaはリトルエンディアンでエンコードされた相対ジャンプです。それは本当に意味しfffffee1ます。

次に、これを呼び出し命令のアドレス+5に追加します。 (0xfffffee1 + 0x4004da + 5) % 2**32 = 0x4003c0

于 2010-05-06T22:32:29.660 に答える