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が使用されているのですか?
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」は任意の値です。コンパイラーは、基本ブロックの内部番号付けなど、実装の詳細に基づいて固有のラベル名を生成します。)
GCCによって生成されたアセンブリの
.cfi_def_cfa_offset
ディレクティブで使用される値について説明します。
マシューは良い説明をしました。これは、GASマニュアルの セクション7.10 CFIディレクティブ の定義です。
.cfi_def_cfa_offset
は、CFAを計算するためのルールを変更します。レジスターは同じままですが、オフセットは新しいです。 CFAアドレスを計算するために定義されたレジスタに追加されるのは絶対オフセットであることに注意してください。
そして.cfi_adjust_cfa_offset
:
.cfi_def_cfa_offset
と同じですが、オフセットは前のオフセットから加算または減算された相対値です。