YARV のバイトコードと戯れる方法

YARV では、バイトコードを直接書いて実行する方法が提供されています。バイトコードといってもバイトとかは出てこなくて、配列やシンボルを使って命令列を表現します。こんな感じ。

# encoding: utf-8
# good_example.rb

# ヘッダ
header = [
  "YARVInstructionSequence/SimpleDataFormat", 1, 1, 1,
  { :arg_size=>0, :local_size=>1, :stack_max=>3 },
  "<dummy>", "foo.rb", :top, [], 0, []
]

# バイトコード本体 (スタックマシン)
body = [
  [:putnil],                   # レシーバを積む
                               # (関数呼び出しのときは nil)
  [:putobject, 1],             # 1 を積む
  [:send, :p, 1, nil, 8, nil], # p を呼ぶ (引数 1 個)
  [:leave]                     # 終わり
]

bytecode = header + [body]

# バイトコードを読み込む
iseq = VM::InstructionSequence.load(bytecode)

# 実行する
iseq.eval  #=> p 1

ただし、VM::InstructionSequence.load は (今のところ) 呼び出せないようになっています (iseq.c の中でメソッド定義がコメントアウトされています) 。これはベリファイアがないためです。つまり、変なバイトコードを読み込ませるといくらでも ruby が落とせます。以下は、ローカル変数が 0 個なのに 10000 番目のローカル変数を読み込もうとしている不正なバイトコードです。実行すると落ちます。

# encoding: utf-8
# bad_example.rb

# ヘッダ (ローカル変数は 0 個 + 特殊変数)
header = [
  "YARVInstructionSequence/SimpleDataFormat", 1, 1, 1,
  { :arg_size=>0, :local_size=>1, :stack_max=>3 },
  "<dummy>", "foo.rb", :top, [], 0, []
]

# バイトコード本体 (スタックマシン)
body = [
  [:getlocal, 10000], # 10000 番目のローカル変数を読む
  [:leave]            # 終わり
]

bytecode = header + [body]

# バイトコードを読み込む
iseq = VM::InstructionSequence.load(bytecode)

# 実行する
iseq.eval  #=> SEGV

そこで (次に続きます) 。