RubyVMの様子を観察したい

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

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

いい記事があったのでこちらの記事を参考にさせていただきました。
[RubyプログラムをVM命令単位で実行する](http://www.callcc.net/diary/20121216.html)

環境はMacです。

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

```bash
brew install gdb
```

Macだとgdbにコード署名とやらをする必要があります。次の記事を参考に署名しました。
[OS XでGDBを使う（ためにコード署名をする）](http://qiita.com/takahashim/items/204ffa698afe09bd4e28)

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

```bash
sudo pkill taskgated
```

最後に署名します。

```bash
codesign -s gdb-cert $(which gdb)
```

# Rubyビルド
Rubyのソースコードを持ってきて、ちょろっとデバッグ用のコードを追加します。
[2.3.2にパッチをあてたもの](https://github.com/nownabe/ruby/tree/2.3.2_trace)をGitHubにpushしました。 ([diff](https://github.com/nownabe/ruby/commit/d21082f0d183f8770b17afd37bdcd508d685be8a))

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

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

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

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

```bash
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-env`で`debug.c`もビルドされてデバッグが便利な感じになります。

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

makeします。

```bash
make -j8
```

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

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

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

```bash
$ 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](https://qiita-image-store.s3.amazonaws.com/0/43208/df9425ea-78b9-7097-0943-3e9c2e45949d.gif)

# おわりに
`rb_p`以外にもいろいろ[便利なコマンド](https://github.com/ruby/ruby/blob/v2_3_2/.gdbinit)が用意されています。

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