おしながき

ELFファイルフォーマット

  • .eh_frameセクションの構造と読み方

DWARFファイルフォーマット

NCURSESライブラリ

  • NCURSES Programing HOWTO ワタクシ的ほんやく
    1. Tools and Widget Libraries
    2. Just For Fun !!!
    3. References
  • その他、自分メモ
  • NCURSES雑多な自分メモ01


最近の更新 (Recent Changes)

2019-09-24
2013-10-10
2013-10-03
2013-10-01
2013-09-29
目次に戻る:DWARFファイルフォーマット

.debug_frameセクションの構造:INTEL64(amd64)での例の解析(その2)

それでは、解析スタート

では、.debug_frameの最後として、.debug_frameセクションのバイナリデータからCFAをデコードしてみまっす。

ネタにするソースコードは、その1のものをそのまま使ってます。
また、以下の解析例では、その1編のソースのコンパイル結果から、.debug_frameセクションをバイナリのままひっこ抜いて、こいつを

"hexdump -C dwarftest2"

でダンプ出させて、これにコメントを入れる形で書いています。

  * まず、.debug_frameセクションの冒頭から見て行きます。
  * 多分、この冒頭は、CIEですね。 --------------------------------------------------------------------------
00000000  14 00 00 00  : length = 0x00000014 = 20Byteですね。
00000004  ff ff ff ff  : CIE_id = 0xffffffff なので、このブロックは「CIE」なんだ。
00000008  01           : CIE:version = 0x01
  * ということで、.debug_frame自体のバージョンは「1」ですね。
00000009  00           : CIE:augmentation
  * これは0x00が来るまでの連続データブロックです、0x00なんで「ベンダ拡張なし」です。
0000000a  01           : CIE:code_alignment_factor = 0x01
  * ということで、コードは1バイト飛び、アラインメントなしです。
0000000b  78           : CIE:data_alignment_factor = 0x78
  * 別途どっかでまとめますが、sLEB128の表記で0x78は「-8」です。
  * ところで、なんでわざわざ「-8」何ですかね?
0000000c  10           : CIE:return_address_register = 0x10 = 16
  * r16にリターンアドレスが入ってます。ってこと。
    ただし、このr16、INTEL64/amd64ではr15までしかないので、「DWARFで定義できる仮想レジスタ」であることに注意。

  * ここから、CIE:initial_instructions になります。命令単位をハンドデコードしてみます。*
0000000d  0c 07 08     : これが1つ目の命令。
  * まず、"0x0c"は、上位2bitは"00"、下位6bitは"0x0c"になります。
  * これを[]の表の"1Byte目 上位2bit/下位6bit"で探すと、"DW_CFA_def_cfa"命令になります。
  * そして、この命令は引数1"uLEB128 Reg."、引数2"uLEB128 offset"を取ることが分かります。
  * で、バイナリを見ると、引数1=0x07、引数2=0x08 ですね。
  * この命令は、引数1で指定されたレジスタのアドレスに引数2のoffsetを足した場所が、CFAですよ、ってもんです。
  * なので、この命令からは、「 Reg7 + 8 のアドレスが示す場所を、Call Frame Addressとします 」という宣言が理解できます。
  * なお、DWARF規約の原文では、この命令を DW_CFA_def_cfa(7,8)の様に表現しています。

00000010  90 01        : これが、2つ目の命令です。
  * "0x90"は、上位2bit="10"、下位6bit="0x10"(10進数=16、2進数=010000)です。
  * これを命令表から探すと、上位2bitが2進数で"10"のものは、"DW_CFA_offset"になります。
  * そして、下位6bitは対象レジスタ番号であり、これは10進数で16ですが、r16ですね。
  * で、こいつはuLEB128のfactored offsetを取ります。そして、このfactored offsetは0x01=1です。
  * はい。このfactored offset ですが、これはこの引数のオフセットの値に、CIE:data_alignment_factorを掛けた値を
    指定レジスタの値であるアドレスに加える、ものでした。CIE:data_alignment_factorは、-8でした。
  * なので、最終的には「レジスタr16の値を、CFA-8のアドレスにコピーする」と読めます。
  * レジスタr16は、CIEのヘッダで、仮想的にリターンアドレスが入っている、となったもんです。
  * よって、結論としては「CFA-8のアドレスに、リターンアドレスが入っているよ」となります。これはその1での解析と一致しますね。
  * あ、そういえばCIEのヘッダ部分のdata_alignment_factorがなんで-8なんですかねぇ、って書きましたが、
    これ見てひらめきました。なんとなく、これも「命令部の容量減らし」のためなんですね。すっげぇー関心関心なのです。

00000012  00 00 00 00 00 00  : で、残りは全部「0x00」です。
  * 命令コード「0x00」は、DW_CFA_nopで、領域あまったから適当に突っ込んでおく、何もしない命令です。

  * で、CIEは読み終りました。


  * そして、FDEです。ではいきますかね。 -------------------------------------------------------------------

00000018  1c 00 00 00   : length = 0x0000001c = 28Byte ですね。
  * なので、00000037までが、1つのFDEになります。
0000001c  00 00 00 00   : CIE_id = 0x00000000
  * .debug_frameセクションの頭から0x00000000の場所にあるCIEを参照しまっす、ってことです。
    この例から見ても、あってますね。
00000020  b0 05 40 00 00 00 00 00  : FDE:initial_location = 0x00000000004005b0
  * 解析その1編のfunc2関数ですね。
  * アドレスの表記は、64bitなんで、8Byteになってます。
00000028  14 00 00 00 00 00 00 00  : FDE:address_range = 0x0000000000000014 = 20Byte
  * FDE:initial_location から 20Byte の範囲をこのFDEで表現します、ってことです。
  * えっと、20Byte足すと、0x00000000004005c4です。func2関数のコード範囲とあってますね。

  * さ、ここからが本題、FDE:insturctionsです。1命令づつ見て行きます。
00000030  41            : DW_CFA_advance_loc(1)
  * 0x41→ High 2bit="01", Low 6bit="0x01" です。
  * High 2bit="01"は、DW_CFA_advance_locですね。そして、Low 6bitから、delta=1 となります。
  * よって、現在のプログラムアドレス(以下、pc)を0x4005b1にします。

00000031  0e 10         : DW_CFA_def_cfa_offset(16)
  * "0x0e"は、命令表から、"DW_CFA_def_cfa_offset"命令と分かります。
  * 引数1にnon-fact. offsetをuLEB128で取ります。これは0x10=16ですね。
  * よって、現在のCFAを "r7+8" → "r7+16"に変えます。と読めます。

00000033  86 02         : DW_CFA_offset(6,2)
  * "0x86"は、High 2bit="10", Low 6bit="0x06"です。これは命令表から"DW_CFA_offset"と分かります。
  * Low 6bitから、対象レジスタはr6です。また、引数1のfactored offsetは2です。
  * offsetは、factoredですので、CIE:data_alignment_factor -8 * 2 = -16がオフセットになります。
  * この命令の意味から、CFIルール表のr6の値、すなわち関数呼び出し前のレジスタr6の値は、
    CFA-16の場所にコピーして保存されたことになります。

00000035  43            : DW_CFA_advance_loc(3)
  * これは、アドレス00000030の最初の命令と同じですね。
  * この命令によって、CFIルール表には、プログラムアドレス0x4005b4の行が作成されました。

00000036  0d 06         : DW_CFA_def_cfa_register(6)
  * "0x0d"は、"DW_CFA_def_cfa_register"となります。また、引数1の"0x06"から対象レジスタはr6です。
  * この命令は、CFIルール表の現在のCFA値(ルール)のレジスタ番号を、引数1で指定されたレジスタ番号のもの
    に書き換えるものでした。
  * よって、こいつは現在のCFA "r7+16" → "r6+16"に書き換えています。これもその1と比較してOKですね。

  * で、1つ目のFDEが読み終りました。 --------------------------------------------------------------------


  * 次に、FDEの2つ目です。  が、これ1つ目のFDEとおんなじですね。 ---------------------------------------
00000038  1c 00 00 00
0000003c  00 00 00 00
00000040  d0 05 40 00 00 00 00 00
00000048  29 00 00 00 00 00 00 00
00000050  41
00000051  0e 10
00000053  86 02
00000055  43
00000056  0d 06

  * 最後に、FDEの3つ目です。  が、これも1つ目、2つ目ののFDEとおんなじですね。 -------------------------
00000058  1c 00 00 00 00 00 00 00
00000060  00 06 40 00 00 00 00 00
00000068  48 00 00 00 00 00 00 00
00000070  41
00000071  0e 10
00000073  86 02
00000075  43
00000076  0d 06
00000078

  * で、あっさり読み終りました。。。

.debug_frame解析が終っちゃいました。。。

ということで、ネット上の参考文献がほとんど無い中、苦手過ぎる英語を無理矢理、ってことで結構大変だったんですがね。。。。なんとか無事.debug_frame解析完了です。
ちゃんと、つじつま合いましたね。

ただ。。。折角ここまで解析して、気づいたこと。
「.debug_frame →デバッガ作る上では、思ってたよりはあんま使えないかも。。。ガビーンー!」
なんとなく、
「今の命令アドレスから、その命令アドレスは「どの関数」にいて、その呼び出し元はどこ、を特定する」
目的以外には、あんま使えんのかなぁってとこでしょうか。 (まぁ、これが大事なんですけどね。)

目次に戻る:DWARFファイルフォーマット