web-dev-qa-db-ja.com

GAS:.cfi_def_cfa_offsetの説明

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が使用されているのですか?

52
void-pointer

DWARF spec がセクション6.4で言うように:

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

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

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

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

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

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

次に、mainでコードを実行します。これにより、subq $8, %rspを実行して、8バイトのスタックを自分用に予約します。

:                :
|    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」は任意の値です。コンパイラーは、基本ブロックの内部番号付けなど、実装の詳細に基づいて固有のラベル名を生成します。)

76

GCCによって生成されたアセンブリの.cfi_def_cfa_offsetディレクティブで使用される値について説明します。

マシューは良い説明をしました。これは、GASマニュアルの セクション7.10 CFIディレクティブ の定義です。

.cfi_def_cfa_offsetは、CFAを計算するためのルールを変更します。レジスターは同じままですが、オフセットは新しいです。 CFAアドレスを計算するために定義されたレジスタに追加されるのは絶対オフセットであることに注意してください。

そして.cfi_adjust_cfa_offset

.cfi_def_cfa_offsetと同じですが、オフセットは前のオフセットから加算または減算された相対値です。

1
jww