Hello World VM (HWVM) for CIL on Ruby

CIL 版 (.NET Framework 版) でも HWVM を作る方針は Java の場合とほとんど同じ.C#Visual Basic などで書いたコードは,コンパイラで .exe ファイル (PE ファイル) に変換される.この PE ファイルの中に,CIL で記述されたプログラム(instruction code)が入っているので,それを読みだして実行してやればいい.ただし,Java のクラスファイルに対応する PE ファイル (.exe ファイル)の構造が複雑で,読み出すのが少々面倒になっている.

exe ファイルに含まれる CIL のコードを見るには,Windows で ildisasm.exe を使うか,mono プロジェクトの monodis コマンドを使う.当然だけど,どっちを使ってもだいたい同じ出力になる.下にはったのは gmcs で HelloWorld.cs をコンパイルして作った HelloWorld.exe を monodis で逆アセンブルしたもの,

bash$ monodis HelloWorld.exe

.assembly extern mscorlib
{
  .ver 2:0:0:0
  .publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
}
.assembly 'HelloWorld'
{
  .custom instance void class [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::'.ctor'() =  (
		01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78   // ....T..WrapNonEx
		63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01       ) // ceptionThrows.

  .hash algorithm 0x00008004
  .ver  0:0:0:0
}
.module HelloWorld.exe // GUID = {6D7D8154-D163-485B-8733-E5437E6641D0}


  .class public auto ansi beforefieldinit HelloWorld
  	extends [mscorlib]System.Object
  {

    // method line 1
    .method public hidebysig specialname rtspecialname 
           instance default void '.ctor' ()  cil managed 
    {
        // Method begins at RVA 0x20ec
	// Code size 7 (0x7)
	.maxstack 8
	IL_0000:  ldarg.0 
	IL_0001:  call instance void object::'.ctor'()
	IL_0006:  ret 
    } // end of method HelloWorld::.ctor

    // method line 2
    .method public static hidebysig 
           default void Main (string[] argv)  cil managed 
    {
        // Method begins at RVA 0x20f4
	.entrypoint
	// Code size 11 (0xb)
	.maxstack 8
	IL_0000:  ldstr "Hello World!"
	IL_0005:  call void class [mscorlib]System.Console::WriteLine(string)
	IL_000a:  ret 
    } // end of method HelloWorld::Main

  } // end of class HelloWorld

この中で,実行するべきプログラム(Main 関数に相当する部分)は,末尾のほうにある

    // method line 2
    .method public static hidebysig 
           default void Main (string[] argv)  cil managed 
    {
        // Method begins at RVA 0x20f4
	.entrypoint
	// Code size 11 (0xb)
	.maxstack 8
	IL_0000:  ldstr "Hello World!"
	IL_0005:  call void class [mscorlib]System.Console::WriteLine(string)
	IL_000a:  ret 
    } // end of method HelloWorld::Main

この部分.実行する必要がある命令は以下の3つ.

ldstr "Hello World!" 文字列 Hello World! をスタックに積む
call void class... System.Console の WriteLine(string) メソッドを呼びだす
ret メソッドの実行を終了する

ちなみに Visual Studiocsc.exe でコンパイルすると少し違ったコードになるようだ.csc.exe でコンパイルして ildasm.exe で逆アセンブルしたものはこんな感じになった.Visual Studio のバージョンによっても結果が変わるかもしれない.

  .method public hidebysig static void  Main(string[] argv) cil managed
  {
    .entrypoint
    // コード サイズ       13 (0xd)
    .maxstack  8
    IL_0000:  nop
    IL_0001:  ldstr      "Hello World!"
    IL_0006:  call       void [mscorlib]System.Console::WriteLine(string)
    IL_000b:  nop
    IL_000c:  ret
  } // end of method HelloWorld::Main

gmcs と比べると,nop という命令(何もしない命令)が入っているせいで,このプログラムの場合は 5 行分動かさないといけない.HWVM 的にはどっちのプログラムがきてもちゃんと動くように作っておく.

Java と同じように,CIL の命令も ldstr などの文字列がそのままファイルに入っているわけではなく,それぞれの命令に対応した数値が入っている.具体的には以下のような感じ.

0x72 0x01 0x00 0x00 0x70 ldstr "Hello World!"
0x28 0x02 0x00 0x00 0xA0 call void [mscorlib]System.Console::WriteLine(string)
0x2A ret

HelloWorld.exe をバイナリエディタでのぞいてみると,以下の部分にこれに該当するデータが入っている.

Java の場合と同様に,この部分を抜きだすことを最初に考える必要がある.ただ,Java に比べるとこの作業は簡単じゃない.とりあえず Java のほうがひととおり終わってから,CIL について書く予定,