Hatena::ブログ(Diary)

muddy brown thang このページをアンテナに追加 RSSフィード Twitter

2007-09-27

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 として保存しておけば、あとは gdbRubyプロセスにアタッチして

(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) になっていたのを訂正。

yohgakiyohgaki 2007/10/10 18:39 たまたま検索で見つけました。

> 誰かが eval.c を魔窟と表現してたけど、それを言ったら PHP の zend_execute.c は腐海だ。

うむ。確かに、って感じですがどの言語でもVM回りは汚いのかなと想像してます。

moriyoshimoriyoshi 2007/10/11 02:49 ASTのノード表現を丁寧に扱っているか否かが重要に思えます。
そういうノードって、言語の上でのfirst-classなオブジェクトにするわけにもいかないし、微妙な位置づけになりがちなので...。

Cの上でポリモーフィックにオブジェクトを扱えるシステムを一旦作り、そのオブジェクトでまたオブジェクトシステムを構築するのが面倒ですが一番綺麗になるんじゃないかと思いました。

(と書いてもなかなか意味が伝わりにくい話ですが)

いくつかの処理系では自動生成を使っていますが、これも一つ
かと思います。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/moriyoshi/20070927/1190910311
Connection: close