57

私は空のプログラムを書いて、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: 論理積。Pseudo-C: ESP = -16 & ESP、これが何を意味するのかよくわかりません。
  • call: IP をスタックにプッシュし (呼び出されたプロシージャが戻る方法を見つけることができるように)、現在の場所に進み__mainます。(__main とは?)
  • movl: このゼロは、コードの最後に返す定数でなければなりません。MOV はこのゼロを EAX に配置します。
  • leave: ENTER 命令 (?) の後にスタックを復元します。なんで?
  • ret: スタックに保存されている命令アドレスに戻ります

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

4

5 に答える 5

12

ここで概説されている非常によく似た演習があります: http://en.wikibooks.org/wiki/X86_Assembly/GAS_Syntax

あなたはそれのほとんどを理解しました - 私は強調と追加のために追加のメモを作成します.

__mainさまざまな起動初期化を処理する GNU 標準ライブラリのサブルーチンです。C プログラムには厳密には必要ありませんが、C コードが C++ とリンクしている場合にのみ必要です。

_mainあなたのメインサブルーチンです。_mainとはどちら__mainもコードの場所であるため、同じストレージ クラスとタイプを持ちます。私はまだ.sclandの定義を掘り下げてい.typeません。いくつかのグローバル変数を定義することで、いくつかの照明を得ることができます。

最初の 3 つの命令は、サブルーチンの作業用ストレージの専門用語であるスタック フレーム (ほとんどの場合、ローカル変数と一時変数) をセットアップします。プッシュebpすると、呼び出し元のスタック フレームのベースが保存されます。スタックフレームのベースespebpセットに配置します。は、スタック上のandlローカル変数が 16 バイト境界に整列する必要がある場合に備えて、スタック フレームを 16 バイト境界に整列します (x86 SIMD 命令ではその整列が必要ですが、整列によってints やfloats などの通常の型が高速化されます。

この時点espで、ローカル変数にスタック スペースを割り当てるために、通常はメモリ内を下に移動すると予想されます。あなたmainには何もないので、gccは気にしません。

への呼び出し__mainは、メイン エントリ ポイントにとって特別なものであり、通常はサブルーチンには表示されません。

残りはあなたが推測したとおりです。レジスタeaxは、バイナリ仕様で整数リターン コードを配置する場所です。 leaveスタック フレームを元retに戻し、呼び出し元に戻ります。この場合、呼び出し元は低レベルの C ランタイムであり、追加の魔法 (atexit()関数の呼び出し、プロセスの終了コードの設定、オペレーティング システムにプロセスの終了を要求するなど) を行います。

于 2009-08-22T22:22:33.933 に答える
5

それと$-16、%espについて

  • 32 ビット: 10 進数で -16 は 16 進数で 0xfffffff0 に等しい
  • 64 ビット: 10 進数の -16 は、16 進数表現の 0xffffffffffffffff0 に等しい

そのため、ESP の最後の 4 ビット (ところで、2**4 は 16 に等しい) をマスクし、他のすべてのビットを保持します (ターゲット システムが 32 ビットか 64 ビットかに関係なく)。

于 2009-08-22T22:20:27.683 に答える
4

に加えてandl $-16,%esp、これは、下位ビットをゼロに設定すると常に値が%esp 下がって調整され、スタックがx86で下に大きくなるために機能します。

于 2009-08-23T04:31:34.380 に答える
2

私はすべての答えを持っているわけではありませんが、私が知っていることを説明できます。

ebpフロー中の初期状態を格納するために関数によって使用されespます。関数に渡される引数の場所と、独自のローカル変数の場所への参照です。ebp関数が最初に行うことは、指定されたdoingのステータスを保存するpushl %ebpことです。これは、呼び出しを行う関数にとって不可欠であり、それを独自の現在のスタック位置のespdoingに置き換えますmovl %esp, %ebp。この時点での最後の 4 ビットをゼロにするのebpは GCC 固有です。なぜこのコンパイラがそうするのかわかりません。それをしなくてもうまくいくでしょう。ではcall ___main、いよいよ本題に入ります。__main は誰ですか? どちらもわかりません...おそらくGCC固有の手順が増え、最後にmain()が行う唯一のことは、戻り値を0に設定することmovl $0, %eaxleave同じですmovl %ebp, %esp; popl %ebpebp状態を復元retしてから終了します。retどこにいても、その時点からスレッドフローをポップeipして続行します(main()として、このretはおそらくプログラムの終了を処理するカーネルプロシージャにつながります)。

そのほとんどは、スタックの管理に関するものです。少し前に、スタックの使用方法について詳細なチュートリアルを書きました。なぜこれらすべてが作成されたのかを説明すると便利です。しかし、ポルトガル語で...

于 2009-08-22T22:00:19.657 に答える