Ruby VM アドベントカレンダー が面白いので、 便乗してVMをテーマに書いてみます。
Ruby(CRuby)は1.9から評価器がVM化され、RubyプログラムはVM命令にコンパイルされてから 動作するようになりました。コンパイル結果は、rubyコマンドに--dump=insnsオプションを 与えることで確認することができます。
$ ruby --dump=insns -e '"abc"+"def"' == disasm: <RubyVM::InstructionSequence:<main>@-e>====================== 0000 trace 1 ( 1) 0002 putstring "abc" 0004 putstring "def" 0006 opt_plus <callinfo!mid:+, argc:1, ARGS_SKIP> 0008 leave
ここでは各VM命令の実行によってVMがどういう動きをするのか見る方法を紹介します。
やり方はいろいろあると思いますが、VM命令の先頭に 「VMの状態を出力してからTRAPを飛ばす」コードを 追加していくことにしましょう。
まず、rubyに以下のパッチを当ててビルドします。
gdb経由で実行してみます。(TRAPを飛ばすために環境変数ENABLE_TRAPを定義しておく必要があります)
$ ENABLE_TRAP= gdb --args ./ruby --disable-gems -e '"abc"+"def"' ... (gdb) r ... -- stack frame ------------ 0000 (0x7ffff6a09010): 00000008 0001 (0x7ffff6a09018): 555555a43d00 0002 (0x7ffff6a09020): 00000008 0003 (0x7ffff6a09028): 555555a39710 -- Control frame information ----------------------------------------------- c:0002 p:0000 s:0004 e:000858 EVAL -e:1 [FINISH] c:0001 p:0000 s:0002 e:000fa8 TOP [FINISH] ### Next: trace ################ Program received signal SIGTRAP, Trace/breakpoint trap. ... (gdb)
最初のVM命令であるtraceの実行直前のVMの状態(スタックフレームとコントロールフレーム)が出力されました。 ここでcontinue(c)と入力すると、
(gdb) c Continuing. -- stack frame ------------ 0000 (0x7ffff6a09010): 00000008 0001 (0x7ffff6a09018): 555555a43d00 0002 (0x7ffff6a09020): 00000008 0003 (0x7ffff6a09028): 555555a39710 -- Control frame information ----------------------------------------------- c:0002 p:0002 s:0004 e:000858 EVAL -e:1 [FINISH] c:0001 p:0000 s:0002 e:000fa8 TOP [FINISH] ### Next: putstring ################ Program received signal SIGTRAP, Trace/breakpoint trap. (gdb)
と、trace実行後の状態が出力されます。
続けてputstringを実行すると、
(gdb) c Continuing. -- stack frame ------------ 0000 (0x7ffff6a09010): 00000008 0001 (0x7ffff6a09018): 555555a43d00 0002 (0x7ffff6a09020): 00000008 0003 (0x7ffff6a09028): 555555a39710 0004 (0x7ffff6a09030): 555555a396e8 -- Control frame information ----------------------------------------------- c:0002 p:0004 s:0005 e:000858 EVAL -e:1 [FINISH] c:0001 p:0000 s:0002 e:000fa8 TOP [FINISH] ### Next: putstring ################ Program received signal SIGTRAP, Trace/breakpoint trap. (gdb)
スタックに値が追加されました。
rubyのソースツリーに含まれている.gdbinitには、 VALUEを与えるとそれをRubyオブジェクトとして出力するrpというコマンドが 定義されているのですが、それを使うと、
(gdb) rp 0x555555a396e8 T_STRING: "abc" bytesize:3 (embed) encoding:1 coderange:7bit $1 = (struct RString *) 0x555555a396e8
追加された値が"abc"であることが分かります。
興味がある人は、メソッド/ブロック呼び出しなどいろいろなコードを動かしてみると面白いかもしれません。