x86、win32 での空のプログラムの GCC アセンブリ出力



私は空のプログラムを書いて、stackoverflow コーダーを悩ませていますが、そうではありません。 gnu ツールチェーンを調べているところです。


以下は私には深すぎるかもしれませんが、空のプログラムの話を続けるために、GNU が消費するものである C コンパイラの出力を調べ始めました。


gcc version 4.4.0 (TDM-1 mingw32)

test.c:


int main()
{
return 0;
}

gcc -S test.c


    .file   "test.c"
.def ___main; .scl 2; .type 32; .endef
.text
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
call ___main
movl $0, %eax
leave
ret

ここで何が起こっているのか説明できますか?ここにそれを理解するための私の努力があります。 as を使用しました マニュアルと私の最小限の x86 ASM 知識:



  • .file "test.c" 論理ファイル名のディレクティブです。

  • .def :ドキュメントによると 「シンボル名のデバッグ情報の定義を開始する」 .シンボル (関数名/変数) とは何ですか? また、どのようなデバッグ情報ですか?

  • .scl :ドキュメントでは、「シンボルが静的であるか外部であるかにかかわらず、ストレージ クラスはフラグを立てることができます」 .これは同じ静的ですか? および外部 私はCから知っていますか?その「2」は何ですか?

  • .type :パラメータ 「シンボル テーブル エントリのタイプ属性として」 を保存します。 、わかりません。

  • .endef :問題ありません。

  • .text :これは問題です。セクションと呼ばれるもののようで、コードの場所だと読んだことがありますが、ドキュメントはあまり教えてくれませんでした。

  • .globl 「シンボルを ld に見えるようにする」 、マニュアルはこれについて非常に明確です。

  • _main: これはメイン関数の開始アドレス (?) かもしれません

  • pushl_ :EBP をスタックに配置するロング (32 ビット) プッシュ

  • movl :32 ビット移動。疑似 C:EBP = ESP;

  • andl :論理積。疑似 C:ESP = -16 & ESP 、これが何を意味するのかよくわかりません。

  • call :IP をスタックにプッシュし (呼び出されたプロシージャが戻る方法を見つけることができるように)、__main の場所に進みます。 は。 (__main とは?)

  • movl :このゼロは、コードの最後に返す定数でなければなりません。 MOV はこのゼロを EAX に配置します。

  • leave :ENTER 命令 (?) の後にスタックを復元します。なぜですか?

  • ret :スタックに保存されている命令アドレスに戻ります


ご協力ありがとうございました!


答え:



で始まるコマンド。アセンブラへの指示です。これは、これが「file.c」であり、その情報を exe のデバッグ情報にエクスポートできることを示しているだけです。



.def ディレクティブは、デバッグ シンボルを定義します。 scl 2 は、ストレージ クラス 2 (外部ストレージ クラス) を意味します。type 32 は、この sumbol が関数であることを示します。これらの数値は pe-coff exe-format によって定義されます


___main は、gcc が必要とするブートストラップを処理するために呼び出される関数です (これは、c++ 静的初期化子の実行や必要なその他のハウスキーピングなどを行います)。



テキスト セクションを開始します - コードはここにあります。



_main シンボルをグローバルとして定義します。これにより、リンカーや、リンクされている他のモジュールから見えるようになります。



_main と同じで、_main が関数であることを示すデバッグ シンボルを作成します。これはデバッガで使用できます。



新しいラベルを開始します (アドレスになります)。上記の .globl ディレクティブは、このアドレスを他のエンティティから見えるようにします。



古いフレーム ポインター (ebp レジスター) をスタックに保存します (この関数が終了したときに元の位置に戻すことができます)



スタック ポインタを ebp レジスタに移動します。 ebp はしばしばフレーム ポインターと呼ばれ、現在の「フレーム」(通常は関数) 内のスタック値の先頭を指します (ebp を介してスタック上の変数を参照すると、デバッガーに役立ちます)



スタックを fffffff0 で AND し、16 バイト境界に効果的に整列させます。スタック上の整列された値へのアクセスは、整列されていない場合よりもはるかに高速です。これらの先行するすべての命令は、ほぼ標準関数のプロローグです。


call        ___main

gcc が必要とするものを初期化する ___main 関数を呼び出します。 Call は現在の命令ポインターをスタックにプッシュし、___main のアドレスにジャンプします



0 を eax レジスタに移動します (return 0 の 0;) eax レジスタは、stdcall 呼び出し規約の関数の戻り値を保持するために使用されます。



leave 命令は

の省略形です。

つまり、関数の開始時に行われた処理を「元に戻す」 - フレーム ポインタとスタックを元の状態に復元します。



この関数を呼び出した人に戻ります。スタック (対応する呼び出し命令がそこに配置されている) から命令ポインターをポップし、そこにジャンプします。