.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"ってなんだろう?
というわけで、今回はこの辺で終わり。