任意のアセンブリコードを仮想マシンで実行しちゃうRuby gemを作ってみた
このブログエントリはカーネル/VM Advent Calendar 2013 25日目の記事です。
前回、カーネル/VM探検隊で「バインディングさえあればスクリプト言語でもゲストOSローダを実装出来る」という話をちらっとしました(資料)。
今回の記事では、ゲストOSローダではなくてもっと実験的な、簡単なアセンブリコードをRubyスクリプトからハイパーバイザへ投入する仕組みを実装したのでこれを紹介します。
ruby-virtualmachine
ruby-virtualmachineというgemを作成しました。
これを用いて、任意のアセンブリコードをアセンブル・ロード・VM上で実行、までの一連の処理をわずかな行数のRubyスクリプトで行えます。
サンプルプログラム
以下に、引数で渡された文字を指定回数だけ仮想マシンのシリアルポート(ポート3F8h)へ表示するプログラムの例を示します。
VMの終了には未実装IOポートへのアクセスを利用しています(ポート10h)。
require 'virtualmachine' if ARGV.size < 2 puts "sample1.rb [char] [repeat]" exit 1 end puts "[args]" puts "char:#{ARGV[0]}" puts "repeat:#{ARGV[1]}" puts vm0 = VirtualMachine.new('vm0', 128) vm0.rax = ARGV[0].bytes.to_a[0] vm0.rcx = ARGV[1].to_i vm0.load_asm(<<EOS) mov dx, 3F8h loop: out dx, al dec cx cmp cx, 0 jne loop mov al, 0ah out dx, al mov dx, 10h mov al, 0h out dx, al EOS puts "[registers]" puts "rax:#{vm0.rax}" puts "rbx:#{vm0.rbx}" puts "rcx:#{vm0.rcx}" puts "rdx:#{vm0.rdx}" puts "rip:#{vm0.rip}" puts puts "[run vm]" vm0.run puts puts "[registers]" puts "rax:#{vm0.rax}" puts "rbx:#{vm0.rbx}" puts "rcx:#{vm0.rcx}" puts "rdx:#{vm0.rdx}" puts "rip:#{vm0.rip}"
簡単な使い方としては、vm = VirtualMachine.new(VM名, メモリサイズ)でインスタンスを作成、vm.load_asm(アセンブリコード)で0x10000にアセンブリコードをロード、vm.runで実行です。
実行例
以下が実行結果です。
ここでは、表示する文字に*を、リピート回数に30回を指定しています。
$ sudo ruby sample1.rb '*' 30 [args] char:* repeat:30 [registers] rax:42 rbx:0 rcx:30 rdx:0 rip:65536 [run vm] ****************************** [registers] rax:0 rbx:0 rcx:0 rdx:16 rip:65559
制限
- FreeBSD BHyVeに依存しています。後々Linux KVMへ移植したいと考えています。
- 初期化ルーチンがC拡張の中にハードコーディングされています。ここではページテーブルやGDTの初期化、インストラクションポインタやコントロールレジスタの設定を決め打ちで行い、64bitプロテクトモードで0x10000から起動するように設定されています。このようなコードは全てRubyへ移していく予定です。
- BHyVeの実装上の制約でデバイスが限られています。使えないレガシデバイスが多く存在します。
- BHyVeの実装上の制約でBIOS/UEFIをサポートしないため、ファームウェアをコールする実験には使えません。
- アセンブリロード以外の方法でのRubyからのゲストメモリ空間の読み書きは未実装です。
…というわけで、いまは制限だらけの実装ですが、もう少し機能が増えれば「今までRubyしか書いたことないけど、勉強のためにアセンブリコードを書いてみよう!」なんて用途に使えるかもしれません。