rubyインタープリンタ(MRI)をデバックビルドしてgdbでデバッグ
MRIのビルド
./configure時の–enable-debug-envオプションは、コンパイラによる最適化を抑止してgdbでソースをおえるようにしてくれているはず。 システムのrubyと競合しないように–prefixでインストール先を指定する。
./configure --enable-debug-env --prefix /home/sato/ruby/ CFLAGS="-g3 -O0"
make
make install
#環境変数を設定 さっきインストールしたrubyの実行ファイルが呼び出されるように環境変数を設定してやる。
RUBYPATH=$HOME/ruby/bin
export RUBYPATH
export PATH=$RUBYPATH:$PATH
export RUBYLIB=$RUBYPATH/lib/ruby
#gdb用のヘルパーをホームディレクトリに入れておく rubyの内部のデータは殆どがVALUEポインタで表現され、後でキャストしてから使用される。 この時、キャスト後が何のデータであるかがわからなければ、gdbのprintで中を覗くことができない。 このため、rubyのソースにバンドルされているgdb用のヘルパーを使う。
以下からダウンロード https://github.com/ruby/ruby/blob/trunk/.gdbinit
cp .gdbinit ~/
定義されたヘルパーの一覧はhelp user-definedで見ることができる。
(gdb) help user-defined
User-defined commands.
The commands in this class are those defined by the user.
Use the "define" command to define a command.
List of commands:
******略******
rb_p -- User-defined
rb_ps -- Dump all threads and their callstacks
rb_ps_thread -- User-defined
rb_ps_vm -- Dump all threads in a (rb_vm_t*) and their callstacks
rbi -- User-defined
rp -- Print a Ruby's VALUE
rp_class -- Print the content of a Class/Module
rp_id -- Print an ID
rp_imemo -- Print the content of a memo
rp_string -- Print the content of a String
ruby_gdb_init -- User-defined
sdr -- User-defined
重要なのはrpで、これでVALUEの中身を知ることができる。
#irbを起動しgdbをアタッチ
まず、起動したirbのプロセスIDを調べる。
$ ps x|grep irb 1742 pts/1 Sl+ 0:00 irb
別のターミナルから起動したirbにアタッチする。
gdb /home/sato/ruby/bin/irb -p 1742
ブレークポイントを設定してみる。
(gdb) b proc_curry Breakpoint 3 at 0x7f60aa2bff64: file proc.c, line 2755. (gdb) c Continuing
proc_curryはProc#curryのC側での実装。 以下の関数がそれにあたる。
```c
rb_define_method(rb_cMethod, "curry", rb_method_curry, -1);
static VALUE
proc_curry(int argc, const VALUE *argv, VALUE self)
{
int sarity, max_arity, min_arity = rb_proc_min_max_arity(self, &max_arity);
VALUE arity;
rb_scan_args(argc, argv, "01", &arity);
if (NIL_P(arity)) {
arity = INT2FIX(min_arity);
}
else {
sarity = FIX2INT(arity);
if (rb_proc_lambda_p(self)) {
rb_check_arity(sarity, min_arity, max_arity);
}
}
return make_curry_proc(self, rb_ary_new(), arity);
}
この状態で、irb側から以下のようにProc#curryを呼び出す
Proc.new {|x,y|x+y}.curry
gdbがbreakする。
Breakpoint 1, proc_curry (argc=0, argv=0x7f48ea86d258, self=139951178000880) at proc.c:2755
warning: Source file is more recent than executable.
2755 int sarity, max_arity, min_arity = rb_proc_min_max_arity(self, &max_arity);
コードの中の現在位置を確認
(gdb) l
2750 * p b.curry[] #=> :foo
2751 */
2752 static VALUE
2753 proc_curry(int argc, const VALUE *argv, VALUE self)
2754 {
2755 int sarity, max_arity, min_arity = rb_proc_min_max_arity(self, &max_arity);
2756 VALUE arity;
2757
2758 rb_scan_args(argc, argv, "01", &arity);
2759 if (NIL_P(arity)) {
selfのVALUEポインタを見てみる。
(gdb) rp self
T_DATA(proc): $4 = (struct RTypedData *) 0x7f48ec4029f0
(gdb) print *(struct RTypedData *) self
$5 = {basic = {flags = 12, klass = 139951178175800}, type = 0x7f48eae51ca0 <proc_data_type>,
typed_flag = 1, data = 0x7f48ec85bd80}
(gdb) print (*(struct RTypedData *) self)->basic
$6 = {flags = 12, klass = 139951178175800}
(gdb) print (*(struct RTypedData *) self)->basic->klass
$7 = 139951178175800