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
そこで (次に続きます) 。