LoginSignup
19
8

More than 5 years have passed since last update.

RubyVMの様子を観察したい

Last updated at Posted at 2016-11-16

はじめに

Rubyのプログラムは内部的にはコンパイルされてバイトコードとなり、RubyVM上で実行されます。
RubyVMはスタックマシンというタイプのVMで、オブジェクトをスタックに積み上げて演算を行っています。

諸事情によりスタックの様子を観察しながらRubyVMの命令単位でRubyプログラムを実行したかったのでやってみました。

いい記事があったのでこちらの記事を参考にさせていただきました。
RubyプログラムをVM命令単位で実行する

環境はMacです。

gdbセットアップ

gdbというデバッグツールを使います。
Homebrewでインストールできます。

brew install gdb

Macだとgdbにコード署名とやらをする必要があります。次の記事を参考に署名しました。
OS XでGDBを使う(ためにコード署名をする)

taskgatedの再起動とありますが、プロセスをkillしてやればOKです。

sudo pkill taskgated

最後に署名します。

codesign -s gdb-cert $(which gdb)

Rubyビルド

Rubyのソースコードを持ってきて、ちょろっとデバッグ用のコードを追加します。
2.3.2にパッチをあてたものをGitHubにpushしました。 (diff)

このパッチはかんたんに言うと、insns.defというRubyVMの命令定義ファイルから命令の実体を自動生成するtool/instructions.rbを修正して、各命令の先頭にrb_vmdebug_stack_dump_raw_current()の実行と、命令名の出力を追加しています。

今回はこのパッチを使います。まずはcloneしてください。

git clone git://github.com/nownabe/ruby.git -b 2.3.2_trace --depth 1
cd ruby

configureを作ります。autoconfコマンドが入ってなければbrew install autoconfでインストールできます。

autoconf

configureします。ここでデバッグ情報を付与してコンパイルされるように指定します。rubyのREADMEに

Some C compiler flags may be added by default depending on your environment. Specify optflags=.. and warnflags=.. as necessary to override them.

とあったのでoptflagsを使うのがいいみたいです。--enable-debug-envdebug.cもビルドされてデバッグが便利な感じになります。

./configure --enable-debug-env optflags="-g -O0"

makeします。

make -j8

試す

実際にRubyのプログラムを実行してみます。

ENABLE_TRAP=true gdb --args ./ruby --disable-gems -e 'puts "Hello, gdb"'

実行するとこんな感じになります。

$ ENABLE_TRAP=true gdb --args ./ruby --disable-gems -e 'puts "Hello, gdb"'
GNU gdb (GDB) 7.12
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
# ...省略...
Reading symbols from ./ruby...done.
(gdb)

(gdb)ってところで指示を待ってます。

rを入力してRubyプログラムを開始すると、スタックと次に実行されるRubyVMの命令が表示されます。

(gdb) r
Starting program: /Users/nownabe/src/github.com/nownabe/ruby/ruby --disable-gems -e puts\ \"Hello,\ gdb\"
-- stack frame ------------
0000 (0x101100000): 10187f300
0001 (0x101100008): 10187f2d8
0002 (0x101100010): 00000000
0003 (0x101100018): 10185fa28
-- Control frame information -----------------------------------------------
c:0002 p:0000 s:0004 E:001070 EVAL   -e:1 [FINISH]
c:0001 p:0000 s:0002 E:0023f0 (none) [FINISH]

### Next: trace ########################
[New Thread 0x1333 of process 40014]

Thread 1 received signal SIGTRAP, Trace/breakpoint trap.
0x00007fff9386c8ea in __kill () from /usr/lib/system/libsystem_kernel.dylib
(gdb)

RubyVMのスタックとコントロールフレーム、次の命令が表示されています。
cで次の命令を実行できます。

(gdb) c
Continuing.
-- stack frame ------------
0000 (0x101100000): 10187f300
0001 (0x101100008): 10187f2d8
0002 (0x101100010): 00000000
0003 (0x101100018): 10185fa28
-- Control frame information -----------------------------------------------
c:0002 p:0002 s:0004 E:001070 EVAL   -e:1 [FINISH]
c:0001 p:0000 s:0002 E:0023f0 (none) [FINISH]

### Next: putself ########################

Thread 1 received signal SIGTRAP, Trace/breakpoint trap.
0x00007fff9386c8ea in __kill () from /usr/lib/system/libsystem_kernel.dylib
(gdb)

trace命令が実行されました。もう一度cで進んでみます。

(gdb) c
Continuing.
-- stack frame ------------
0000 (0x101100000): 10187f300
0001 (0x101100008): 10187f2d8
0002 (0x101100010): 00000000
0003 (0x101100018): 10185fa28
0004 (0x101100020): 1018d6628
-- Control frame information -----------------------------------------------
c:0002 p:0003 s:0005 E:001070 EVAL   -e:1 [FINISH]
c:0001 p:0000 s:0002 E:0023f0 (none) [FINISH]

### Next: putstring ########################

Thread 1 received signal SIGTRAP, Trace/breakpoint trap.
0x00007fff9386c8ea in __kill () from /usr/lib/system/libsystem_kernel.dylib
(gdb)

putself命令が実行されて、スタックに何か積まれました。
これが何かを確かめてみましょう。スタックの値を使い、rb_p 0x1018d6628と入力してみます。
rb_pはいわゆるRubyのpメソッドです。

(gdb) rb_p 0x1018d6628
main
(gdb)

mainと表示されました。putself命令でmainが積まれたことがわかります。

putstringを実行してみましょう。

(gdb) c
Continuing.
-- stack frame ------------
0000 (0x101100000): 10187f300
0001 (0x101100008): 10187f2d8
0002 (0x101100010): 00000000
0003 (0x101100018): 10185fa28
0004 (0x101100020): 1018d6628
0005 (0x101100028): 10185f988
-- Control frame information -----------------------------------------------
c:0002 p:0005 s:0006 E:001070 EVAL   -e:1 [FINISH]
c:0001 p:0000 s:0002 E:0023f0 (none) [FINISH]

### Next: opt_send_without_block ########################

Thread 1 received signal SIGTRAP, Trace/breakpoint trap.
0x00007fff9386c8ea in __kill () from /usr/lib/system/libsystem_kernel.dylib

さらにスタックに積まれました。これを確認してみると、文字列を確認できます。

(gdb) rb_p 0x10185f988
"Hello, gdb"
(gdb)

tty.gif

おわりに

rb_p以外にもいろいろ便利なコマンドが用意されています。

メソッドの呼び出しなんかをすると新しいコントロールフレームができたりして楽しい感じになるのでぜひやってみてください :relaxed:

19
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
8