.NETのILアセンブリ言語を追ってみる1

Javaの世界にはJavaバイトコード(.NETでいうところのIL)を書き換えるためのAPIを提供する、「Javassit」というライブラリがありますが、ちょっと調べてみたところ.NETの世界にはJavassitと同等の機能を持ったものは見当たりませんでした。

その代わり、.NETのリフレクションAPIを使用すれば、同じようにILの書き換えができそうだ、ということがわかったので、とりあえずILとは何ぞや、というところを調査してみることにしました。

.NETユーザにとっての定番情報サイトらしい「THE CODE PROJECT」の「Introduction to IL Assembly Language」という記事を読んで、ILアセンブリ言語を学びながら、わかったことをズラズラと書きなぐってみます。

「THE CODE PROJECT-Introduction to IL Assembly Language」http://www.codeproject.com/dotnet/ilassembly.asp

.NETのソースをコンパイルすると、コンパイラはソースを一旦IL(MSILやCILなど)に変換します。ILはJavaでいうバイトコードに相当するもの。

IL自体はバイナリフォーマットで記述されているため人間は読むことができません。しかし他のバイナリーコードアセンブリ言語を持つように、ILにもILアセンブリ言語というアセンブリ言語が用意されています。.NET Runtime(JIT)は、直接ILアセンブリで記述されたコード(以降、ILAsm)を実行できないため、一旦ILアセンブリをILにコンパイルする必要があります。

ILAsmを試す最も簡単な方法は、NotepadなどでILAsmを記述し、.NET Framework SDKに付属するILAsm.exeでコンパイルし、実行することです。

以下に簡単なHelloWorldプログラムをILAsmで記述したものを示します。

  1: //Test.IL
  2: //A simple programme which prints a string on the console
  3:
  4: .assembly extern mscorlib {}
  5: 
  6: .assembly Test
  7: {
  8:     .ver 1:0:1:0
  9: }
 10: .module test.exe
 11: 
 12: .method static void main() cil managed
 13: {
 14:     .maxstack 1
 15:     .entrypoint
 16:     
 17:     ldstr "I am from the IL Assembly Language..."
 18:     
 19:     call void [mscorlib]System.Console::WriteLine (string)
 20:     ret
 21: }
  • 1,2行目 コメント行です。
  • 4行目 mscorlibというライブラリをインポートしています。なおILAsmでは、"."ではじまる命令(ディレクティブ)は特別な意味を持ちます。
  • 6,8行目 このアセンブリの情報を設定しています。ここではこのアセンブリにTestという名前を名づけています。またこのアセンブリのバージョン情報を設定してます。
  • 10行目 このアセンブリのモジュール名(test.exe)を指定しています。アセンブリは必ずなんらかのモジュールに属する必要があります。
  • 12行目 ここからはメソッドの定義です。まず".method"命令でメソッドの定義を開始し、"static"命令で、これから定義するメソッドがstaticであることを定義し、"void"命令で返り値がvoidであることを定義し、"main"でメソッド名が"main"であることを定義し、"()"でメソッド引数をとらないことを定義し、最後に"cil managed"で、コンパイラにこれはマネージドコードであることを伝えます。
  • 14行目 ".maxstack"で、これからメモリ上にロードするデータの最大数をアナウンスします。ここでは1に設定しています。
  • 15行目 ".etnry"命令で、このメソッドがこのアプリケーションの開始点であることをコンパイラに伝えます。
  • 16行目 "ldstr"命令で、"I am from ..."という文字列を、評価スタック(evaluation stack)にロードします。ちなみに、"ldstr"は"Load String"という意味です。多分。
  • 19行目 "call"命令で、mscorlibライブラリの"invokes"メソッドを呼び出します。"call"命令の引数は" void [mscorlib]System.Console::WriteLine (string)"のようになっており、先頭からメソッドの返り値(=void)、メソッドが属するライブラリ名(=mscorlib)、メソッドが属するクラス(=System.Console)、メソッド名(=WriteLine)と、その引数の型(=string)が指定されています。肝心のメソッドの引数は、スタックから取得されます。つまり16行目でスタックにロードしたstringが使用されます。
  • 20行目 これはいうまでもなくメソッドからのリターンを表します。

評価スタック

上の例でも示したように、ある命令を実行するために必要な情報(メソッド引数のデータなど)は、事前に評価スタック上にロードしておきます。その際、対象の命令が必要なデータ分だけスタック領域を確保しておきます(.mexstack命令)。命令の実行が完了すると、スタック上のデータは削除されます。スタックには、stringであろうがintegerであろうがobjectであろうが、どのような型のデータであってもロードできます。

それでは、もう一つのサンプルを見てみます。

 //Add.il
 //Add Two Numbers

 .assembly extern mscorlib {}

 .assembly Add
 {
    .ver 1:0:1:0
 }
 .module add.exe

 .method static void main() cil managed
 {
    .maxstack 2
    .entrypoint
    
    ldstr "The sum of 50 and 30 is = "
    call void [mscorlib]System.Console::Write (string)
    
    ldc.i4.s 50
    ldc.i4 30    
    add
    call void [mscorlib]System.Console::Write (int32)
    ret
 }

このサンプルでは二つの整数値を加算して表示しています。"ldc.i4.s"命令と"ldc.i4"命令で、50と30という数値データをスタック上にロードし、"add"命令は、スタック上の2つのデータを検索し、数値データが発見されると、それらの値を加算し、スタック上から2つのデータを削除し、加算した結果をスタックにロードします。その後、"call"命令でスタックにロードされているデータを、加算の結果として表示しています。

ここで、"ldc.i4.s"命令は、1バイトのInteger型の数値を、"ldc.i4"命令は4バイトのInteger型の数値をそれぞれスタックにロードせよという意味の命令です。

自分用メモ:"s"は"Single Byte"の"s"だというのはわかるが、"ldc"の"c"と、"i4"の"4"ってなんだろう?

というわけで、今回はこの辺で終わり。

参考:http://www.codeproject.com/dotnet/ilassembly.asp