さて、.debug_frameセクションですが、困ったことに日本語の解析文書はWeb上では見付からないどころか、英語でも例を出しているものがほとんどありません。。。。 しかも、DWARF3のサンプル(appendix D.6)は、motorola 88000だったります。。。(もはや、motorolaの石は秋葉ですら見掛けません。。。)
ということで、INTEL64(amd64)での例が欲しかったんで、やってみました。
今回の解析のためネタにしたソースは、以下です。(ネタなので、ヘタとか汚いとか言わないよーに!)
/* dwarftest2.c */ #include<stdio.h> int a; ""void func2( int *b )"" { *b = 3; return; } int func1( int a, int *b) { int c; func2( b ); c = a + *b; return c; } int main( int argc, char *argv[]) { int b; int c; a = 1; c = func1( a, &b ); printf("a=%d, b=%d, c=%dn", a, b, c); return; }
なお、解析に使った環境はこれです。
prompt# uname -a FreeBSD xxxxx.koinec.jp 9.1-RELEASE FreeBSD 9.1-RELEASE #0: prompt# gcc -v Using built-in specs. Target: amd64-undermydesk-freebsd Configured with: FreeBSD/amd64 system compiler Thread model: posix gcc version 4.2.1 20070831 patched [FreeBSD]
まず、上記のソースの逆アセンブル結果と、.debug_frameの内容をアセンブラ命令単位で突合して、動きを見て行きます。
で、これをやるために使ったコマンドは以下です。
そして、逆アセンブルのリスト内に、.debug_frameの情報をマージして、コメント入れる形としてみました。
以下の見方ですが、行頭が!の行は、逆アセンブルの結果、#の行は.debug_frame、何もない行はCソースで、それぞれ逆アセンブルした時のコードアドレス単位に並んでいます。では見てみましょう。
# <CIE> ############################################################ # # 00000000 00000014 ffffffff CIE "" cf=1 df=-8 ra=16 # * 00000014: length? = 20Byteって意味でしょうか? # * cf=1 : code_alignment_factor # * df=-8: data_alignment_factor なぜマイナス? # * ra=16: return_address_register # # LOC CFA ra # 00000000 r7+8 c-8 # * なぜ、r6がないのか分かりません。。。 dwarftest2: file format elf64-x86-64-freebsd ---------------------------------------------------------------------------------- 00000000004005b0 <func2>: #include<stdio.h> int a; # .debug_frame for "void func2( int *b )" ########################### # 00000018 0000001c 00000000 FDE cie=00000000 pc=004005b0..004005c4 # * 00000018 : これは、このFDEが.debug_frameの0x18=24Byte目からってこと # * 0000001c : FDE:length 28Byteなんですね。 # * 00000000 : FDE:CIE_pointer このFDEが見るCIEは、.debug_frameの0Byte目からだよってことですね。 # 同時に、-1じゃないんでこいつは「FDE」だよってことです。 # * pc=004005b0..004005c4 : # FDE:initial_location = 004005b0 # FDE:address_range = 20 # ってことみたいです。 # * 以下、FDE:instructionsです。 # LOC CFA r6 ra # 004005b0 r7+8 u c-8 # 004005b1 r7+16 c-16 c-8 # 004005b4 r6+16 c-16 c-8 # * この3行は、以下Cソース、逆アセンブラコード文中にも埋め込んでみます。 # * あと、なぜCIEにはいない「r6」がいるんでしょうか。。。?どっから来たの? # * ちなみに、以下も含め、CIFルール表の各行の値、はその行のアセンブラ命令を実行する前の # 状態です。004005b0なら、"puch %rbp"を実行する直前の状態です。 void func2( int *b ) { # 004005b0 r7+8 u c-8 # * r6: undefined # この段階ではr6は使われていませんってことですね。 # * ra: c-8 # raはレジスタじゃなくって、return addressの場所を指し示しています。 # で、ra=c-8 cは、Call Frame Addressなので、CFA-8の場所です。 # ん、ここで気になります。INTEL64/amd64のABIでは、call命令で関数を呼ぶと # 戻りアドレスが自動的にスタックにpushされちゃう(=スタックポインタSPは-8される) # わけですが、ここでのraはc-8ですね。ということは、逆算すると、 # 「CFA は call命令を呼び出す前のスタック = 戻り先格納スタックアドレス+8」 # になりますね。 # (INTEL64の場合は、てっきりReturn AddressのスタックアドレスがCFAかなって思い込んでました) # * CFA: r7+8 # で、CFAのr7+8を、上のraの内容から逆算すると、r7=CFA-8です。 # これは、まさしく、"push %rbp"する前のSPの値です。よって、r7=spですね。 ! 4005b0: 55 push %rbp # * で、"push %rbp"で呼び出し元(親)関数のBPを保存します。 # 004005b1 r7+16 c-16 c-8 # * 保存した後の状態が、004005b1の行の状態です。 # * ポイントは、r7の値は、004005b0の行から-8されているってことです。(pushしたので) # * CFA: r7 + 16 # はい。rbpのpushで、スタックポインタsp=r7は-8されますね。 # スタックには、rbpの値の前に、return address 8byteもcall命令によってPushされてます。 # よって、CFAの場所は、今のスタックポインタSP=r7に16足す、ということでビンゴ!です。 # * ra: c-8 # cはCFAで、これはpushしてもCFAの位置は変わりません。なので、4005b0と同じく、c-8です。 # よって、c-8のまま変わらず。 # * r6: c-16 # んで、r6です。 # c-16なんで、これはちょうど、400b50でrbpをpushしたアドレスですね。 # r6の値は、CFA-16の場所に今あるよってことを示していますから、r6=bpと考えるのが妥当ですね。 ! 4005b1: 48 89 e5 mov %rsp,%rbp # * で、現在のrsp → rbp にコピーします。 ! 4005b4: 48 89 7d f8 mov %rdi,-0x8(%rbp) # 004005b4 r6+16 c-16 c-8 # * この行の状態を見て行く前に、実行した"mov %rsp, %rbp"ですが、 # rsp=rbpにしたため、r7=r6 になります。まず、これが前提です。 # * ra: c-8 # これは、先程とおんなじですね。CFAはあくまで固定、ですから、 # return addressはCFA-8の場所、も固定ですね。 # * r6: c-16 # これは、注意ですね。まず、4005b1のmovで、rbp、すなわちr6の値を書き換えてます。 # にもかかわらず、r6の値はc-16です。あれぇ?書き換えたよね? # ということで、これから # 「CFIルール表のレジスタ列=関数開始時点のそのレジスタの値の今の格納先」 # を示している、ということが分かります。今のr6の値ではない、と言うことに注意です。 # * CFA: r6+16 # 最後にCFAですが、なぜか"r7+16"から"r6+16"に変わっていますね。 # これは、この行の時点でこそ"r7+16"="r6+16"=CFAなのですが、r7はSPです。 # この後の関数の処理、で自動変数使ったりすると、値がバンバン変わります。 # でも、その度にCFAのinstruction書いていると、データ容量的にたまらんですね。 # ということで、rbpにコピった、スタックポインタ値をベースにCFAも表現しているってことです。 # あと、「CFA列のレジスタ表記は、現在のレジスタの値」ということみたいです。 # おなじr6でも、CFA列とr6列では、意味が違うので注意ですね。 # (こんなん、英語の規約書読んだだけで分かるかいぃー) *b = 3; ! 4005b8: 48 8b 45 f8 mov -0x8(%rbp),%rax ! 4005bc: c7 00 03 00 00 00 movl $0x3,(%rax) return; } ! 4005c2: c9 leaveq ! 4005c3: c3 retq ! 4005c4: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1) ! 4005ca: 66 0f 1f 44 00 00 nopw 0x0(%rax,%rax,1) # * DWARF3の規約書では、関数の終り部分で、レジスタを元に戻して行く様の例があったのですが、 # gccのINTEL64/amd64では、省略されてるっぽいです。つか、使わんって見たのでしょう。 ---------------------------------------------------------------------------------- 00000000004005d0 <func1>: # .debug_frame for "int func1( int a, int *b )" ########################### # 00000038 0000001c 00000000 FDE cie=00000000 pc=004005d0..004005f9 # LOC CFA r6 ra # 004005d0 r7+8 u c-8 # 004005d1 r7+16 c-16 c-8 # 004005d4 r6+16 c-16 c-8 # # * ちなみに、func1(この関数)も、func2と同じっぽいです。よって説明は上記参照ってことで。 int func1( int a, int *b) { # 004005d0 r7+8 u c-8 ! 4005d0: 55 push %rbp # 004005d1 r7+16 c-16 c-8 ! 4005d1: 48 89 e5 mov %rsp,%rbp # 004005d4 r6+16 c-16 c-8 ! 4005d4: 48 83 ec 20 sub $0x20,%rsp ! 4005d8: 89 7d ec mov %edi,-0x14(%rbp) ! 4005db: 48 89 75 e0 mov %rsi,-0x20(%rbp) int c; func2( b ); ! 4005df: 48 8b 7d e0 mov -0x20(%rbp),%rdi ! 4005e3: e8 c8 ff ff ff callq 4005b0 <func2> c = a + *b; ! 4005e8: 48 8b 45 e0 mov -0x20(%rbp),%rax ! 4005ec: 8b 00 mov (%rax),%eax ! 4005ee: 03 45 ec add -0x14(%rbp),%eax ! 4005f1: 89 45 fc mov %eax,-0x4(%rbp) return c; ! 4005f4: 8b 45 fc mov -0x4(%rbp),%eax } ! 4005f7: c9 leaveq ! 4005f8: c3 retq ! 4005f9: 0f 1f 80 00 00 00 00 nopl 0x0(%rax) ---------------------------------------------------------------------------------- 0000000000400600 <main>: # .debug_frame for "int main( int argc, char *argv[] )" ########################### # 00000058 0000001c 00000000 FDE cie=00000000 pc=00400600..00400648 # LOC CFA r6 ra # 00400600 r7+8 u c-8 # 00400601 r7+16 c-16 c-8 # 00400604 r6+16 c-16 c-8 # # * main関数も、func2, fucn1と同じですね。 int main( int argc, char *argv[]) { # 00400600 r7+8 u c-8 ! 400600: 55 push %rbp # 00400601 r7+16 c-16 c-8 ! 400601: 48 89 e5 mov %rsp,%rbp # 00400604 r6+16 c-16 c-8 ! 400604: 48 83 ec 20 sub $0x20,%rsp ! 400608: 89 7d ec mov %edi,-0x14(%rbp) ! 40060b: 48 89 75 e0 mov %rsi,-0x20(%rbp) int b; int c; a = 1; ! 40060f: c7 05 4f 03 20 00 01 movl $0x1,0x20034f(%rip) # 600968 <a> ! 400616: 00 00 00 c = func1( a, &b ); ! 400619: 8b 3d 49 03 20 00 mov 0x200349(%rip),%edi # 600968 <a> ! 40061f: 48 8d 75 f8 lea -0x8(%rbp),%rsi ! 400623: e8 a8 ff ff ff callq 4005d0 <func1> ! 400628: 89 45 fc mov %eax,-0x4(%rbp) printf("a=%d, b=%d, c=%dn", a, b, c); ! 40062b: 8b 55 f8 mov -0x8(%rbp),%edx ! 40062e: 8b 35 34 03 20 00 mov 0x200334(%rip),%esi # 600968 <a> ! 400634: 8b 4d fc mov -0x4(%rbp),%ecx ! 400637: bf 93 06 40 00 mov $0x400693,%edi ! 40063c: b8 00 00 00 00 mov $0x0,%eax ! 400641: e8 0e fe ff ff callq 400454 <printf@plt> return; } ! 400646: c9 leaveq ! 400647: c3 retq ! 400648: 90 nop ! 400649: 90 nop ! 40064a: 90 nop ! 40064b: 90 nop ! 40064c: 90 nop ! 40064d: 90 nop ! 40064e: 90 nop ! 40064f: 90 nop
この例を見て、本人的にはまぁすっきりしました。
ということで、一応、ポイントだけ並べてみます。
この例で、実際のソース/アセンブラコードとCFA命令、およびCFA命令によって作られたCFIルール表の関係ははっきりしましたが、
がまだ、不明です。これは、次に見て行きたいとおもいます。
目次に戻る:DWARFファイルフォーマット
[PageInfo]
LastUpdate: 2013-05-28 22:12:47, ModifiedBy: koinec
[License]
FreeBSD Documentation License
[Permissions]
view:all, edit:members, delete/config:members