デバッグ情報を探る 〜.cfi_xx命令の動作〜
次のようなc言語プログラムを例にとって考えていく。
// test.c int hoge(int x) { return x+1; } int main() { int a = 1; return hoge(a); }
gcc -O0 -S test.c
を実行してコンパイルすると、
; test.s _hoge: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset %rbp, -16 movq %rsp, %rbp .cfi_def_cfa_register %rbp movl %edi, -4(%rbp) movl -4(%rbp), %edi addl $1, %edi movl %edi, %eax popq %rbp retq .cfi_endproc _main: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset %rbp, -16 movq %rsp, %rbp .cfi_def_cfa_register %rbp subq $16, %rsp movl $0, -4(%rbp) movl $1, -8(%rbp) movl $1, -12(%rbp) movl -8(%rbp), %eax addl -12(%rbp), %eax movl %eax, %edi callq _hoge addq $16, %rsp popq %rbp retq .cfi_endproc
このようなアセンブリが生成される。
.cfi_startproc擬似命令では、CFI (Call Frame Infomation)が新たに追加され、CFA (Canonical Frame Address)計算用のレジスタがrspに、オフセットが0に初期化される。続くpushqではrbpをスタックに退避させている。そして、.cfi_def_cfa_offsetでCFAのオフセットを16にしている。このときのスタックには、
のようにデータが格納されていく。
.cfi_offset %rbp, -16では、rbpレジスタの値をCFA-16のアドレスに退避させたことを表している。実際、rbpレジスタの値はスタックの先頭にあるので、(rsp-16)+16 = rsp
はrbpレジスタの値が格納されているアドレスを表す。僕はここで少しハマったのだが、rbpの値が格納されているアドレスはrsp-8ではない。スタックは成長するにつれてアドレスが減るからだ。(下図参照)
実際例えば、
mv (%rsp-16+16) %rx
とすることで、rxレジスタにrbpの元の値を格納できる。
その後、CFAのレジスタを.cfi_def_cfa_register %rbpとすることで、このフレームのCFAを固定している。rspを参照レジスタにすると、スタック操作をするたびにCFAが変わってしまう。
最後に、.cfi_endprocで前のフレームのCFAを復元している。これらの命令によって生成された情報は、デフォルトでは.eh_frameセクションに保存される。