某所で、「rubyプロセスがSEGVとかした時のコアファイルから、rubyスクリプトのバックトレースは取れるの?」って聞かれたので、ちょっと調べてみました。
結論としては、「rubyメソッドの呼び出し位置(ファイル名, 行番号)は取れるけど、呼び出し時の実引数を見るのは難しい」っていう感じです。
rubyのスタックフレームは、当然、rubyプロセスのメモリ上に構築されるので、スタックフレームのデータ構造さえわかれば、ある程度は表示できます。
ただ、コアファイルを出力したrubyプロセスはすでに存在しないので、ruby実装に使われているC関数をデバッガで(正確にはrubyプロセス上で)実行することができません。
そのため、オブジェクトのinspectなどを実行することが難しく、実引数のオブジェクトが何か調べることが困難です。ということで、今回はスタックフレームを表示させる方法を以下で説明します。
rubyスタックフレームを表示するGDBスクリプトは以下になります(rb_dump.gdb)。
(moriyoshiさんのブログ「 GDBで実行中のスクリプト言語のスタックフレームをダンプしてみる試み」のコードをほとんどそのまま使わせていただきました。ありがとうございます。)
[code]
define dump_rb_bt_from_core
set $t = ruby_frame
while $t
printf “[0x%08x] “, $t
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_from_core
dumps the current frame stack from core file. usage: dump_rb_bt_from_core
end
[/code]
今回はサンプルプログラムとして、0アドレスにアクセスするC拡張ライブラリを実行するrubyスクリプトを用意します。
まず、C拡張ライブラリ(segv.c)、
[C]
#include “ruby.h”
VALUE do_segv(VALUE self){
*(char*)(0x0) = 0;
return Qnil;
}
void Init_segv(){
VALUE module;
module = rb_define_module(“Segv”);
rb_define_module_function(module, “do_segv”, &do_segv, 0);
}
[/C]
で、後は適当にextconf.rbを作って、segv.soを作ります。
次にsegv.soを呼び出すRubyスクリプト(test.rb)。
[RUBY]
require ‘segv’
def test1(a)
test2(a)
end
def test2(b)
Segv::do_segv
end
test1(1)
[/RUBY]
さて、サンプルプログラムの用意ができたので、SEGVさせてみます。
$ ulimit -c unlimited $ ruby test.rb test.rb:8: [BUG] Segmentation fault ruby 1.8.5 (2006-08-25) [i386-linux] アボートしました (core dumped)
これでコアファイルが出力されたので、それをGDBで解析します。
今回はCentOS 5.3のyumでインストールしたrubyから出力されたコアファイルなので、
GDBで解析するには、ここからrubyのdebuginfoをインストールしておく必要があります。
$ wget http://debuginfo.centos.org/5/i386/ruby-debuginfo-1.8.5-5.el5_3.7.i386.rpm # rpm -i ruby-debuginfo-1.8.5-5.el5_3.7.i386.rpm
それでは、GDBでコアファイルからrubyのバックトレースをさせてみます。
$ gdb ruby core.29443 GNU gdb Fedora (6.8-37.el5) ... (gdb) backtrace #通常のC関数レベルのバックトレース。 #0 0x00b3a402 in __kernel_vsyscall () #1 0x0056fdf0 in raise () from /lib/libc.so.6 #2 0x00571701 in abort () from /lib/libc.so.6 #3 0x00c514c2 in rb_bug (fmt=) at error.c:214 #4 0x00cbd80b in sigsegv (sig= ) at signal.c:537 #5 [signal handler called] #6 do_segv (self=3086662140) at segv.c:4 #7 0x00c54dd5 in call_cfunc (...) at eval.c:5657 #8 0x00c5c4ab in rb_call0 (...) at eval.c:5810 (gdb) source rb_dump.gdb #この記事の最初に作ったGDBスクリプトをロード (gdb) dump_rb_bt_from_core # rubyバックトレース [0xbfc1da20] (test.rb:8) [0xbfc1e0e0] (test.rb:4) [0xbfc1e7c0] (test.rb:11) [0x00d0f960] Cannot access memory at address 0x4
以上になります。
pythonもコアファイルからスタックフレームが取れるか調べてみましたが、
DebuggingWithGDBのgdbinitスクリプトを読む限り、
rubyと同様に実行していたpythonスクリプトのファイル名と行番号は取れそうです。