ちょっと思いついて、sigdump gem を入れていなくても、gdb で ruby プロセスにアタッチすれば、Ruby レベルのバックトレースを簡単に取れるんじゃなかろうかと思って試してみた。ついでに C レベルのバックトレースも取りたい。
(1) rb_print_backtrace() と rb_backtrace()
CRubyの内部に、Cレベルバックトレースを出力するrb_print_backtrace関数と、Rubyレベルバックトレースを出力する rb_backtrace関数があるので、それを利用する。
gdb で ruby プロセスにアタッチして、
gdb $HOME/.rbenv/versions/2.4.1/bin/ruby [PID]
以下のようにコマンドを打つ。
call write(2, "== c backtrace ==\n", 18)
call rb_print_backtrace()
call write(2, "== ruby backtrace ==\n", 21)
call rb_backtrace()
しかし、これらではカレントスレッドのバックトレースしか出せない。
また、gdbプロセスの標準出力ではなく、対象プロセスのSTDERRに出力がでるので、どこに結果が出たのかわからなくなりそう、という懸念があった。
(2) backtrace と rb_eval_string
Cレベルバックトレースは gdb の機能である backtrace
コマンドで表示できるので、スレッド一覧を info threads
で取って、全スレッドに対して backtrace
コマンドを打ち込めば取れる。これは何も問題ない。
Rubyレベルバックトレースを、call rb_eval_string("コード")
に以下のような Ruby コードを打ち込んで取得するというアイデアを思いついた。
File.open('/tmp/dump', 'a') {|f|
Thread.list.each {|th|
f.write %Q[ Thread #{th} status=#{th.status} priority=#{th.priority}\n]
th.backtrace.each {|bt|
f.write %Q[ #{bt}\n]
}
}
}
実際にやってみると、gdb を detach してもプロセスが復帰しなくなってしまった。。。 gdb から ruby コードを実行するとなんやかんやあって壊れるらしい。
(3) ruby レポジトリにある .gdbinit で定義されている rb_ps を使う
もうすでにアイデア倒れだった感があるのだが、rubyレポジトリに .gdbinitというデバッグ用 gdb スクリプトがあり、そこに C レベルおよび Ruby レベルのスタックトレースを表示する rb_ps
という関数が実は定義されているので、それを使うことができる。
gdb -x .gdbinit $HOME/.rbenv/versions/2.4.1/bin/ruby [PID]
(gdb) rb_ps
* #<Thread:0x7f46bb0a5ee8 rb_thread_t:0x7f46bb0725d0 native_thread:0x7f46ba514740>
0x7f46ba16d700 <thread_join_m at thread.c:980>:in `join'
loop.rb:17:in `<main>'
* #<Thread:0x7f46bb202750 rb_thread_t:0x7f46bb3e03d0 native_thread:0x7f46b89c0700>
0x7f46ba0e4f30 <rb_f_sleep at process.c:4388>:in `sleep'
loop.rb:6:in `block (2 levels) in <main>'
0x7f46ba1a72b0 <rb_f_loop at vm_eval.c:1137>:in `loop'
loop.rb:4:in `block in <main>'
* #<Thread:0x7f46bb202660 rb_thread_t:0x7f46bb3e47e0 native_thread:0x7f46b87be700>
0x7f46ba0e4f30 <rb_f_sleep at process.c:4388>:in `sleep'
loop.rb:13:in `block (2 levels) in <main>'
0x7f46ba1a72b0 <rb_f_loop at vm_eval.c:1137>:in `loop'
loop.rb:11:in `block in <main>'
表示に 2.6 sec ぐらいかかってちょっと遅い感はあったが、しっかり動く。
結論
CRuby の C level interaface の変更に追随するのは大変なので、ruby core team にメンテナンスされている .gdbinit を持ってきて使うのが一番楽、という結論に至ってしまった。