gdbscript's archives

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

by ginriki | 11 月 9th, 2009 

某所で、「rubyプロセスがSEGVとかした時のコアファイルから、rubyスクリプトのバックトレースは取れるの?」って聞かれたので、ちょっと調べてみました。

結論としては、「rubyメソッドの呼び出し位置(ファイル名, 行番号)は取れるけど、呼び出し時の実引数を見るのは難しい」っていう感じです。
rubyのスタックフレームは、当然、rubyプロセスのメモリ上に構築されるので、スタックフレームのデータ構造さえわかれば、ある程度は表示できます。

ただ、コアファイルを出力したrubyプロセスはすでに存在しないので、ruby実装に使われているC関数をデバッガで(正確にはrubyプロセス上で)実行することができません。
そのため、オブジェクトのinspectなどを実行することが難しく、実引数のオブジェクトが何か調べることが困難です。ということで、今回はスタックフレームを表示させる方法を以下で説明します。

rubyスタックフレームを表示するGDBスクリプトは以下になります(rb_dump.gdb)。
(moriyoshiさんのブログ「 GDBで実行中のスクリプト言語のスタックフレームをダンプしてみる試み」のコードをほとんどそのまま使わせていただきました。ありがとうございます。)

CODE:
  1. define dump_rb_bt_from_core
  2.   set $t = ruby_frame
  3.   while $t
  4.     printf "[0x%08x] ", $t
  5.     if $t->node.nd_file
  6.       printf "(%s:%d)\n", $t->node.nd_file, ($t->node.flags>> 19) & ((1 <<(sizeof(NODE*) * 8 - 19)) - 1)
  7.     else
  8.       printf "(UNKNOWN)\n"
  9.     end
  10.     set $t = $t->prev
  11.   end
  12. end
  13.  
  14. document dump_rb_bt_from_core
  15.   dumps the current frame stack from core file. usage: dump_rb_bt_from_core
  16. end

今回はサンプルプログラムとして、0アドレスにアクセスするC拡張ライブラリを実行するrubyスクリプトを用意します。
まず、C拡張ライブラリ(segv.c)、

C:
  1. #include "ruby.h"
  2.  
  3. VALUE do_segv(VALUE self){
  4.         *(char*)(0x0) = 0;
  5.         return Qnil;
  6. }
  7.  
  8. void Init_segv(){
  9.         VALUE module;
  10.         module = rb_define_module("Segv");
  11.         rb_define_module_function(module, "do_segv", &do_segv, 0);
  12. }

で、後は適当にextconf.rbを作って、segv.soを作ります。

次にsegv.soを呼び出すRubyスクリプト(test.rb)。

RUBY:
  1. require 'segv'
  2.  
  3. def test1(a)
  4.   test2(a)
  5. end
  6.  
  7. def test2(b)
  8.   Segv::do_segv
  9. end
  10.  
  11. test1(1)

さて、サンプルプログラムの用意ができたので、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スクリプトのファイル名と行番号は取れそうです。

IA-32のjmp, call命令でTrapするGDBスクリプト

by ginriki | 12 月 3rd, 2008 

jmp/call命令の実行直前までプログラムを進めるGDBスクリプトです。
こういう用途に使えるGDBコマンドがないようなので、自分で作りました。
必要な方はご自由に使ってください。
- script.gdb

使い方は以下の通り、

$ gdb a.out
(gdb) source script.gdb
(gdb) break main
(gdb) run
# main関数でbreak

(gdb) run_until_call_jmp
# main関数内のcall or jmp命令直前で停止

jmp,call命令のop codeの調べ方

IA-32命令セットにおける、JMP, CALLのオペコードは下記の通りになりますIA-32 インテル アーキテクチャ・ソフトウェア・ディベロッパーズ・マニュアル 中巻より抜粋。http://www.intel.com/jp/download/index.htm

JMP命令のオペコード
callオペコード

cb、cw、cd、cpは、オペコードの後に続く1,2,4,6バイトの値です。
jmpやcall命令では、jmp/call先のアドレス値を表しています。

/2とか/3とかっていうのは、ModR/Mバイトのdigit(= op codeの一部) 上記マニュアルの2章(特に2.4~2.6)参照を表してます。
op codeの一部なので、当然、命令を判定する際にdigitのチェックが必要です。

digitは/0~/7まであり、ModR/Mバイトの下位3bitに対応します。

例えば32bitの絶対間接nearジャンプのオペコードは下記になります。

16進表記:  FF /4
2進表記 :  11111111 xxxxx100

xxxxxの部分の値によって、ジャンプ先のアドレス値として参照するレジスタ/メモリアドレスが変化します。
例えば、eaxレジスタに格納されたアドレス値へジャンプする場合は、xxxxx=11000 です。

こんな感じで命令のop codeを調べて実装したのが上記のGDBスクリプトです。
スクリプトを改造する時の参考にしてください。

ブログで紹介した商品

  • Image of デバッガの理論と実装 (ASCII SOFTWARE SCIENCE Language)