トップ 最新 追記 RSS feed

継続にっき

2004|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|12|
2006|01|03|05|06|08|09|10|12|
2007|01|02|03|05|07|12|
2008|10|
2009|01|05|12|
2010|04|05|11|
2011|01|09|12|
2012|02|03|05|09|12|
2013|02|03|
2014|05|09|
2015|12|

2012-12-16 (Sun)

))) RubyプログラムをVM命令単位で実行する

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"であることが分かります。

興味がある人は、メソッド/ブロック呼び出しなどいろいろなコードを動かしてみると面白いかもしれません。


2004|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|12|
2006|01|03|05|06|08|09|10|12|
2007|01|02|03|05|07|12|
2008|10|
2009|01|05|12|
2010|04|05|11|
2011|01|09|12|
2012|02|03|05|09|12|
2013|02|03|
2014|05|09|
2015|12|
トップ 最新 追記 RSS feed