任意のアセンブリコードを仮想マシンで実行しちゃうRuby gemを作ってみた

このブログエントリはカーネル/VM Advent Calendar 2013 25日目の記事です。

前回、カーネル/VM探検隊で「バインディングさえあればスクリプト言語でもゲストOSローダを実装出来る」という話をちらっとしました(資料)。

今回の記事では、ゲストOSローダではなくてもっと実験的な、簡単なアセンブリコードをRubyスクリプトからハイパーバイザへ投入する仕組みを実装したのでこれを紹介します。

ruby-virtualmachine

ruby-virtualmachineというgemを作成しました。
これを用いて、任意のアセンブリコードをアセンブル・ロード・VM上で実行、までの一連の処理をわずかな行数のRubyスクリプトで行えます。

インストー

BHyVeが正常動作するFreeBSD 10.0以降の環境が必要です。
インストールコマンドは以下の通りになります。

$ gem install virtualmachine
サンプルプログラム

以下に、引数で渡された文字を指定回数だけ仮想マシンのシリアルポート(ポート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で実行です。

vm.raxなどとレジスタ名を指定することにより、レジスタの読み書きが可能です。

実行例

以下が実行結果です。
ここでは、表示する文字に*を、リピート回数に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しか書いたことないけど、勉強のためにアセンブリコードを書いてみよう!」なんて用途に使えるかもしれません。