rubyプロセスのコアファイルからバックトレースする。(おまけ: python)

某所で、「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スクリプトのファイル名と行番号は取れそうです。

CentOS 5にAspire Timeline (3810T)の有線LANドライバを入れる

今年(2009年)の夏に、Aspire Timeline(3810T)を買いました。
元々Windows Vistaが入っていましたが、ついでにCentOS 5.32009/10に、CentOS 5.4がリリースされましたねを入れてデュアルブートできるようにしました。

ただ、CentOS 5.3には (たぶん、CentOS 5.4にも) Aspire Timelineの有線LANドライバが入ってないので、ドライバを探してきて入れました。

ドライバは、http://partner.atheros.com/Drivers.aspxのAR81Family Linux Driverになります。私がダウンロードしたときは、AR81Family-linux-v1.0.0.10.tar.gzっていうバージョンでした。

いつの間にか、リンク切れになってました(partner.atheros.comのサイト自体なくなったようです)。12/2時点では、ドライバはここにあります。(katty9015さん、情報ありがとうございます)
ファイル名もAR81Family-linux-v1.0.0.10.tar.gzからAR81Family-linux-v1.0.1.0.tar.gzに変わったので、以下の記事を読むときは注意してください。

USBメモリとかでAspire TimelineのCentOS上にAR81Family-linux-v1.0.0.10.tar.gzを入れた後は、以下のようにコンパイル/インストールすれば、有線LANが使えるようになります。

$ tar xzvf AR81Family-linux-v1.0.0.10.tar
$ cd src/
$ make
$ make install       # 要root権限
$ reboot             # 再起動

おまけ (Intel VTの有効化)

Aspire Timeline(3810T)のCPUはIntel VTに対応してるのですが、BIOSで有効化できないため、そのままだとIntel VTが使えません。
とはいえ、CentOSのXenでWindowsが起動できないのも味気ないです。
以下の2つのリンク先を参考にいじれば、BIOSでIntel VTを有効にできます。Acerのサポート外になってしまうので、いじるときは自己責任でお願いします。