私は空のプログラムを書いて、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 命令は
の省略形です。つまり、関数の開始時に行われた処理を「元に戻す」 - フレーム ポインタとスタックを元の状態に復元します。
この関数を呼び出した人に戻ります。スタック (対応する呼び出し命令がそこに配置されている) から命令ポインターをポップし、そこにジャンプします。