ちょっと思いついて、sigdump gem を入れていなくても、gdb で ruby プロセスにアタッチすれば、Ruby レベルのバックトレースを簡単に取れるんじゃなかろうかと思って試してみた。C レベルのバックトレースも取りたい。
(1) rb_print_backtrace() と rb_backtrace()
CRubyの内部に、Cレベルバックトレースを出力するrb_print_backtrace関数と、Rubyレベルバックトレースを出力する rb_backtrace関数があるので、それを利用する。
gdb で ruby プロセスにアタッチして、
以下のようにコマンドを打つ。
しかし、これらではカレントスレッドのバックトレースしか出せない。
また、gdbプロセスの標準出力ではなく、対象プロセスのSTDERRに出力がでるので、どこに結果が出たのかわからなくなりそう、という懸念があった。
(2) backtrace と rb_eval_string
Cレベルバックトレースは gdb の機能である backtrace
コマンドで表示できるので、スレッド一覧を info threads
で取って、全スレッドに対して backtrace
コマンドを打ち込めば取れる。これは何も問題ない。
Rubyレベルバックトレースを、call rb_eval_string("コード")
に以下のような Ruby コードを打ち込んで取得するというアイデアを思いついた。
実際にやってみると、gdb を detach してもプロセスが復帰しなくなってしまった。。。 gdb から ruby コードを実行するとなんやかんやあって壊れる。
メモ: perl なら gdb から perl コードを実行できるのか ... ? 羨ましい => 追記: この任意の perl コードを gdb から実行するやつは、安全にperlコードを実行できるように perl のイベントループの特定の箇所に breakpoint を貼ってから perl コードを実行する工夫をしているらしい。ただそれでも centos 5.8 の system perl だと segv 起きたりしたとのこと by hirose31
(3) rubyレポジトリにある .gdbinit で定義されている rb_ps を使う
もうすでにアイデア倒れだった感があるのだが、rubyレポジトリに .gdbinitというデバッグ用 gdb スクリプトがあり、そこに C レベルおよび Ruby レベルのスタックトレースを表示する rb_ps
という関数が実は定義されているので、それを使うことができる。
表示に 2.6 sec ぐらいかかってちょっと遅い感はあったが、しっかり動く。
rb_ps は関数呼び出しを使ってないので、core ファイルに対しても使える。便利。
結論
CRuby の C level interaface の変更に追随するのは大変なので、ruby core team にメンテナンスされている .gdbinit を持ってきて使うのが一番楽、という結論に至ってしまった。
gdb を live process に対してアタッチすると、その間プロセスが止まってしまうので注意。固まったプロセスの調査に使うとか、rb_ps は core ファイルに対しても使えるので gcore コマンドで core を吐かせてからゆっくり調査する、とかやると良いだろう。
追記: gem にしました > https://github.com/sonots/gdbdump-ruby