GNU/Linux >> Linux の 問題 >  >> Linux

C hello world のアセンブリ出力の各行の意味は何ですか?

方法は次のとおりです:

        .file   "test.c"

元のソース ファイル名 (デバッガーが使用)。

        .section        .rodata
.LC0:
        .string "Hello world!"

ゼロで終わる文字列がセクション「.rodata」に含まれています (「ro」は「読み取り専用」を意味します。アプリケーションはデータを読み取ることができますが、データに書き込もうとすると例外がトリガーされます)。

        .text

次に、コードが入る「.text」セクションに書き込みます。

.globl main
        .type   main, @function
main:

「main」と呼ばれるグローバルに表示される関数を定義します (他のオブジェクト ファイルから呼び出すことができます)。

        leal    4(%esp), %ecx

レジスター %ecx に格納します 値 4+%esp (%esp はスタック ポインターです)。

        andl    $-16, %esp

%esp 16 の倍数になるように少し修正されています。一部のデータ型 (C の double に対応する浮動小数点形式 および long double )、メモリ アクセスが 16 の倍数のアドレスにある場合にパフォーマンスが向上します。これはここでは実際には必要ありませんが、最適化フラグ (-O2) なしで使用すると ...)、コンパイラは非常に多くの一般的な役に立たないコード (つまり、場合によっては役立つかもしれないが、ここでは役に立たないコード) を生成する傾向があります。

        pushl   -4(%ecx)

これは少し奇妙です:その時点で、アドレス -4(%ecx) の単語 andl より前にスタックの一番上にあった単語です .このコードはその単語 (ちなみに、これはリターン アドレスである必要があります) を取得し、再度プッシュします。この種の関数は、16 バイトでアラインされたスタックを持つ関数からの呼び出しで得られるものをエミュレートします。私の推測では、この push 引数のコピー シーケンスの名残です。関数はスタック ポインターを調整したため、関数の引数をコピーする必要があります。これにはスタック ポインターの古い値からアクセスできます。ここでは、関数の戻りアドレスを除いて、引数はありません。この単語は使用されないことに注意してください (繰り返しますが、これは最適化されていないコードです)。

        pushl   %ebp
        movl    %esp, %ebp

これは標準関数のプロローグです:%ebp を保存します (変更しようとしているので)、%ebp を設定します。 スタック フレームを指すようにします。その後、%ebp 関数の引数にアクセスするために使用され、%esp になります。 再び無料。 (はい、引数がないので、これはその関数には役に立ちません。)

        pushl   %ecx

%ecx を保存します (関数の終了時に %esp を復元するために必要になります) andl の前の値で ).

        subl    $20, %esp

スタック上に 32 バイトを予約します (スタックが「ダウン」することに注意してください)。そのスペースは printf() への引数を格納するために使用されます (4 バイトを使用する単一の引数があるため、これはやり過ぎです [これはポインターです])。

        movl    $.LC0, (%esp)
        call    printf

引数を printf() に「プッシュ」します (つまり、 %esp であることを確認します 引数を含む単語を指します。ここでは $.LC0 これは、rodata セクション内の定数文字列のアドレスです)。次に printf() を呼び出します .

        addl    $20, %esp

printf() の場合 戻り、引数に割り当てられたスペースを削除します。この addl subl をキャンセルします

        popl    %ecx

%ecx を回復します (上に押した); printf() 変更されている可能性があります (呼び出し規則では、関数が終了時に復元せずに変更できるレジスタを記述しています; %ecx はそのようなレジスターの 1 つです)。

        popl    %ebp

関数のエピローグ:これにより %ebp が復元されます (pushl %ebp に対応

        leal    -4(%ecx), %esp

%esp を復元します その初期値に。このオペコードの効果は、%esp に格納することです 値 %ecx-4 . %ecx 最初の関数オペコードで設定されました。これにより、%esp への変更がキャンセルされます 、 andl を含む .

        ret

関数の終了。

        .size   main, .-main

これは main() のサイズを設定します 関数:アセンブリ中の任意の時点で、". " は、"現在追加しているアドレス" のエイリアスです。ここに別の命令を追加すると、". で指定されたアドレスになります。 ". したがって、".-main "、ここに、関数 main() のコードの正確なサイズがあります . .size ディレクティブは、その情報をオブジェクト ファイルに書き込むようにアセンブラに指示します。

        .ident  "GCC: (GNU) 4.3.0 20080428 (Red Hat 4.3.0-8)"

GCC は、そのアクションの痕跡を残すのが大好きです。この文字列は、オブジェクト ファイル内の一種のコメントとして終了します。リンカーはそれを削除します。

        .section        .note.GNU-stack,"",@progbits

コードが非実行スタックに対応できることを GCC が記述している特別なセクション。これは通常のケースです。一部の特別な用途 (標準 C ではない) には、実行可能スタックが必要です。最新のプロセッサでは、カーネルは実行不可能なスタック (誰かがスタック上にあるデータをコードとして実行しようとすると例外をトリガーするスタック) を作成できます。コードをスタックに置くことは、バッファ オーバーフローを悪用する一般的な方法であるため、これを「セキュリティ機能」と見なす人もいます。このセクションでは、実行可能ファイルは「非実行可能スタックと互換性がある」とマークされ、カーネルは喜んでそれを提供します。


    leal    4(%esp), %ecx
    andl    $-16, %esp
    pushl   -4(%ecx)
    pushl   %ebp
    movl    %esp, %ebp
    pushl   %ecx
    subl    $20, %esp

これらの命令は C プログラムでは比較されません。常にすべての関数の先頭で実行されます (ただし、コンパイラ/プラットフォームによって異なります)。

    movl    $.LC0, (%esp)
    call    printf

このブロックは printf() 呼び出しに対応します。最初の命令はその引数 (「hello world」へのポインター) をスタックに置き、次に関数を呼び出します。

    addl    $20, %esp
    popl    %ecx
    popl    %ebp
    leal    -4(%ecx), %esp
    ret

これらの命令は最初のブロックとは逆で、ある種のスタック操作のものです。常に実行も


@Thomas Pornin の補足です。 の答えです。

  • .LC0 文字列リテラルなどのローカル定数
  • .LFB0 ローカル関数の開始
  • .LFE0 ローカル関数の終了、

これらのラベルのサフィックスは数字で、0 から始まります。

これは gcc アセンブラーの規則です。


Linux
  1. 各擬似端末(pty)コンポーネント(ソフトウェア、マスター側、スレーブ側)の責任は何ですか?

  2. コマンドラインから使用しているOsXのバージョンを確認するにはどうすればよいですか?

  3. Wcで各行の文字を数えますか?

  1. Psの出力にはどういう意味がありますか?

  2. ntpq -p 出力の refid は何ですか?

  3. *nix とはどういう意味ですか?

  1. Linux での fork() と grep の意味は何ですか?

  2. 各行を前後に出力するコマンド

  3. 逆引き DNS コマンド ライン ユーティリティとは何ですか?