66

GCC で生成されたアセンブリの .cfi_def_cfa_offset ディレクティブで使用される値について説明が必要です。.cfi ディレクティブが呼び出しフレームとスタックの巻き戻しに関与していることは漠然と知っていますが、たとえば、次の C プログラムをコンパイルする際に、GCC が出力するアセンブリで値 16 と 8 が使用されている理由について、より詳細な説明が必要です。私の64ビットUbuntuマシンで。

C プログラム:

#include <stdio.h>

int main(int argc, char** argv)
{
        printf("%d", 0);
        return 0;
}

次のように、ソース ファイル test.c で GCC を呼び出しましたgcc -S -O3 test.c。-O3 によって非標準の最適化が可能になることはわかっていますが、簡潔にするために、生成されるアセンブリのサイズを制限したかったのです。

生成されたアセンブリ:

        .file   "test.c"
        .section        .rodata.str1.1,"aMS",@progbits,1
.LC0:
        .string "%d"
        .text
        .p2align 4,,15
.globl main
        .type   main, @function
main:
.LFB22:
        .cfi_startproc
        subq    $8, %rsp
        .cfi_def_cfa_offset 16
        xorl    %edx, %edx
        movl    $.LC0, %esi
        movl    $1, %edi
        xorl    %eax, %eax
        call    __printf_chk
        xorl    %eax, %eax
        addq    $8, %rsp
        .cfi_def_cfa_offset 8
        ret
            .cfi_endproc
.LFE22:
        .size   main, .-main
        .ident  "GCC: (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2"
        .section        .note.GNU-stack,"",@progbits

生成されたアセンブリで .cfi_def_cfa_offset ディレクティブに値 16 と 8 が使用されるのはなぜですか? また、ローカル関数の開始ラベルと関数終了ラベルに 22 という数字が使用されているのはなぜですか?

4

2 に答える 2

98

DWARF仕様がセクション6.4で述べているように:

[...]コールフレームは、スタック上のアドレスによって識別されます。このアドレスをCanonicalFrameAddressまたはCFAと呼びます。通常、CFAは、前のフレームの呼び出しサイトでのスタックポインタの値として定義されます(現在のフレームに入るときの値とは異なる場合があります)。

main()libcは( Cランタイムサポートコード内の)別の場所から呼び出され、call命令が実行されるとき%rspに、スタックの最上位(最も低いアドレスであり、スタックは下に向かって大きくなります)を指します(スタックは下に向かって成長します)。ここでは正確に何であるかは重要ではありません):

:                :                              ^
|    whatever    | <--- %rsp                    | increasing addresses
+----------------+                              |

%rspこの時点でのの値は、「呼び出しサイトでのスタックポインタの値」、つまり仕様で定義されているCFAです。

命令が実行されると、call64ビット(8バイト)のリターンアドレスがスタックにプッシュされます。

:                :
|    whatever    | <--- CFA
+----------------+
| return address | <--- %rsp == CFA - 8
+----------------+

ここで、でコードを実行しています。このコードは、それ自体のためにさらに8バイトのスタックを予約するためmainに実行されます。subq $8, %rsp

:                :
|    whatever    | <--- CFA
+----------------+
| return address |
+----------------+
| reserved space | <--- %rsp == CFA - 16
+----------------+

スタックポインタの変更は、ディレクティブを使用してデバッグ情報で宣言され.cfi_def_cfa_offset、CFAが現在のスタックポインタから16バイトのオフセットにあることがわかります。

関数の最後で、addq $8, %rsp命令はスタックポインターを再び変更するため.cfi_def_cfa_offset、CFAがスタックポインターからわずか8バイトのオフセットにあることを示すために、別のディレクティブが挿入されます。

(ラベルの番号「22」は任意の値です。コンパイラーは、基本ブロックの内部番号付けなど、実装の詳細に基づいて一意のラベル名を生成します。)

于 2011-09-23T23:29:22.247 に答える