hogetrace が素晴らしいので僕と行情報

すばらしい。

http://d.hatena.ne.jp/yupo5656/20071008/p1

以下なんかもうちょい調べてから書けって感じの適当情報。

なんか行情報読むとメモリ喰う&リークってのは、あのインターフェース(find_*_line はなんか一個アドレス渡してその行情報返す感じなので、繰り返し読んだ時に毎度 .debug_info & .debug_line をなめると遅いはず)考えるになんか前の情報残してるのかな…と、興味あったんで bfd/dwarf2.c あたり見ると、 all_comp_units ってのに残してるみたいでかつコレ解放もしてないんじゃないかにゃー的だった。軽くしかみてないけど。

あ、こんなコメントが。

     We keep a list of all the previously read compilation units, and
     a pointer to the next un-read compilation unit.  Check the
     previously read units before reading more.  */

こっからホントかウソかわからない、もうちょっと調べてから書けって感じの DWARF2 における行情報嘘入門:

http://dwarfstd.org/Download.php

正確な情報を知りたい人は今すぐ↑をクリック!

それか DWARF2 でぐぐった方がいい気もする。

これでこの先を読んでるのは私か粗探しをしたいいじめっ子のどちらかになりました。粗探しお願いします。

たぶん .debug_info と .debug_line の両方を読まないといけない。 .debug_info の方は、シンボルの情報とか型の情報とか入ってて、まぁ DWARF2 のメインの情報とたぶん言っていいようなもんだと思う。

この情報ってのはなんかまぁのっぺりした配列で木構造作ってるかんじ(意味不明な説明)。でなんか各エントリは DIE とか呼ばれててそれは tag と具体的な情報の attribute からなってる。具体的には readelf -wi とかすると見れる。なんか見たら DW_TAG_compile_unit ってのが根っこになったのっぺりとした木構造が見れると思う。

具体的に見るとですな。

 <0><15063>: Abbrev Number: 1 (DW_TAG_compile_unit)
     DW_AT_stmt_list   : 0xf2d
     DW_AT_entry_pc    : 0
     DW_AT_producer    : (indirect string, offset: 0xfa57): GNU C++ 4.1.2 (Ubuntu 4.1.2-0ubuntu4)
     DW_AT_language    : 4      (C++)
     DW_AT_name        : (indirect string, offset: 0x31bf8): main.cpp
     DW_AT_comp_dir    : (indirect string, offset: 0x11d51): /home/i/src/tracef-0.15/src

とかそういうのが入ってるわけ。 compile unit っていうのはその名の通りコンパイル単位で一個の .cc とかにつき一個できるのね。無論このファイル名とかは全然使えなくて、なぜならどうせ include とかしまくって他のコード読んでるから。

ちょっと脇に寄ってこの main.cpp とかがどこに置いてあるかについて。

indirect どうこうって書いてある通り、この場にはなくて .debug_str ってセクションに入ってる。同じ文字列何度も埋めたりするのを防止したりとかが目的なんだろうね(というか固定長にしとかんと DIE の読み飛ばしが大変だからとかか)。試しに readelf -a (セクションだけ出すなら -S らしいがそんなもん覚えてないのでとりあえず全部出すオプションの -a だけ覚えるのをオススメ。あとは -w? 系しか使った覚えないような) とかしてセクション情報見ると、

 Section Headers:
   [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
   [36] .debug_str        PROGBITS        00000000 140fe0 063838 01  MS  0   0  1

とか書いてある。ここの 0x140fe0 とかいうオフセットとさっきの main.cpp の隣に書いてあった 0x31bf8 を足し算すると 0x172bd8 らしい。

 i@umu src/tracef-0.15/src> od -A x -t x1z -j 0x172bd8 tracef | head -1
 172bd8 6d 61 69 6e 2e 63 70 70 00 61 72 67 3c 33 3e 00  >main.cpp.arg<3>.<

はいはいホントにあったね、おめでとう。調べながら書いてるから無かったら書いた文章無駄になるじゃんとか思いながら書いてたのであって良かったです。

で元に戻って行情報。行情報はですね…

たぶんまずさっきの DW_TAG_compile_unit の中の DW_AT_stmt_list に書いてある数字が .debug_line セクション内のオフセットになっている。 .debug_line っていう別セクションになってるのはフォーマットが違うからで、なんでフォーマットが違うかっていうとさっきのフォーマットで行情報つっこんでいくとアホみたいなサイズになるから。 PC 一個記録するたびにファイル名へのポインタ一個と行番号とかで 8Byte とか使ってると

 i@umu src/tracef-0.15/src> readelf -wl tracef | wc
   35429  364035 1841813

ええと tracef の例だとだいたい 3 万個くらいは PC 記録してるとして(ちなみに readelf -wl は .debug_line 読むヤツ) 240kB とかになるわけですな。それはアホらしいのでなんかもっと効率良いフォーマットで入ってるわけ。実際、

 Section Headers:
   [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
   [32] .debug_line       PROGBITS        00000000 5faccb 0178aa 00      0   0  1

とか見ると 74kB くらいにおさまってるわけ。思ったより減ってなかったけど。どう入れてるかっていうと最初の PC と行番号を記録した後に、後は PC が 3Byte 増えて行が 1つ進んだ、とか差分だけ記録していく感じ。 readelf -wl すると雰囲気はわかる。そのへんは bfd/dwarf2.c の decode_line_info だと思う。 DW_LNS_advance_pc だとか DW_LNS_advance_line とかそんな感じ。 LNS は line number standard opcode とかいうらしくて DWARF line number program とかいう VM の opcode なんですよこれが。

で、この行情報を全部デコードするとか全部メモリの収めるとかするとアホみたいなことになるので、 .debug_info の方を使って先にしぼり込みをしておく必要がある。 DW_TAG_compile_unit に DW_AT_high_pc とか DW_AT_low_pc があればそれを使ってその compile unit を読む必要があるかどうかが調べられるんだけど、 bfd/dwarf2.c とかに

              /* DW_AT_low_pc and DW_AT_high_pc are optional for
                 compilation units.  If we don't have them (i.e.,
                 unit->high == 0), we need to consult the line info
                 table to see if a compilation unit contains the given
                 address.  */

って書いてある通り、これはオプショナルであって必ずしも入ってない。入ってないかこの compile unit がアタリの可能性があったら .debug_info のその compile unit を全部なめる必要があって、まぁなんか bfd はその読んだ時の情報を残してるみたいなのでこれはメモリをたくさん使うだろうなーというかんじ。

一方 gdb だとたぶん全部読んでたりはしなくて、なんかとりあえず partial_symtab (gdb/symtab.h) にグローバル関数のアドレスと行情報やら一番デカい PC とか一番小さい PC とか (texthigh と textlow) だけ読み出しておいて、もし具体的な行情報が必要なら psymtab_to_symtab (gdb/symfile.c) を呼んでそこから partial_symtab 内の read_symtab っていう関数ポインタを呼んで再びアーキテクチャ依存の完全な symtab 作る作業 (dwarf2 なら gdb/dwarf2read.c で dwarf2_psymtab_to_symtab を仕込んである) に入る、と。

んで texthigh と textlow をどうやって調べるかっていうとそれは .debug_info を全部なめた時に、 DW_TAG_subprogram と DW_TAG_inlined_subroutine という関数やらインライン関数が入ってるところを見ていて、この二つの要素はたぶん DW_AT_high_pc や DW_AT_low_pc が必須なので、その時に叩き込んでいる、と。

だから行情報が知りたいアドレスがあったら、全部の partial_symtab をなめながら texthigh と textlow がその範囲か調べて (gdb/symtab.compile の find_pc_sect_psymtab)、それがヒットしたら psymtab_to_symtab を使って完全な情報を起こして調べる (gdb/stack.c の backtrace_command_1 とか) という手続きになる、と。

よくわかってないところ

  • psymtab の再帰っぷり
  • なにがよくわかってないのかがわからない
なにかあれば下記メールアドレスへ。
shinichiro.hamaji _at_ gmail.com
shinichiro.h