GDBで実行中のスクリプト言語のスタックフレームをダンプしてみる試み
よく分からない理由で固まってしまったプロセスがあった。テスト環境ではなかなか再現しない。このようなとき、本番環境でデバッガを走らせるようなことも選択肢として考慮したいところ。
今回は Ruby のプロセスが固まってしまったので、Rubyの eval.c と 10 分ほどにらめっこしながら簡単な GDB スクリプトを書いてみた (1.8.x 用)。誰かが eval.c を魔窟と表現してたけど、それを言ったら PHP の zend_execute.c は腐海だ。
あ、あくまでこれは PoC で、スレッドとかどうなってるかは知らないので正しい結果を吐かない可能性が高い。でも役に立ったからいいのだ。
define dump_rb_bt set $t = ruby_frame while $t printf "[0x%08x] ", $t if $t->last_func printf "%s ", rb_id2name($t->last_func) else printf "..." end if $t->node.nd_file printf "(%s:%d)\n", $t->node.nd_file, ($t->node.flags >> 19) & ((1 << (sizeof(NODE*) * 8 - 19)) - 1) else printf "(UNKNOWN)\n" end set $t = $t->prev end end document dump_rb_bt dumps the current frame stack. usage: dump_rb_bt end
これをホームディレクトリに .gdbinit として保存しておけば、あとは gdb で Ruby のプロセスにアタッチして
(gdb) dump_rb_bt
なんてすればOK。もちろん Ruby のバイナリがデバッグ情報を参照できるようになっていないと駄目だけど。
def mugen1 mugen2 end def mugen2 mugen3 end def mugen3 while true end end mugen1
こんなスクリプトを実行中に SIGINT で止めて、dump_rb_bt を実行すると次のようになる。
Program received signal SIGINT, Interrupt. [Switching to Thread -1209084224 (LWP 28859)] 0x08058805 in rb_eval (self=3085875620, n=0x0) at eval.c:2927 2927 { (gdb) dump_rb_bt [0xbfe048e0] mugen3 (/tmp/test.rb:6) [0xbfe05870] mugen2 (/tmp/test.rb:2) [0xbfe06800] mugen1 (/tmp/test.rb:14)
ちなみに、これの元ネタは PHP 版。
define dump_php_bt set $t = $arg0 while $t printf "[0x%08x] ", $t if $t->function_state.function->common.function_name printf "%s() ", $t->function_state.function->common.function_name else printf "??? " end if $t->op_array != 0 printf "%s:%d ", $t->op_array->filename, $t->opline->lineno end set $t = $t->prev_execute_data printf "\n" end end document dump_php_bt dumps the current execution stack. usage: dump_php_bt executor_globals.current_execute_data end
こっちは TSRM を考慮しているので、やや使い方が面倒になっている。
(gdb) dump_php_bt executor_globals.current_execute_data
ところで、行番号を表示するところ
printf "(%s:%d)\n", $t->node.nd_file, ($t->node.flags >> 19) & ((1 << (sizeof(NODE*) * 8 - 19)) - 1)
これが気になった人はエラい。巨大ファイルをロードだ!を読んでみよう。
「むやみにビットマスクを使わない」とか、そういうゆとり教育なら歓迎だ、まったく。
追記: sizeof(NODE*) が sizeof(NODE) になっていたのを訂正。