Ildasm.exe チュートリアル
このチュートリアルでは、.NET Framework SDK に付属している Microsoft Intermediate Language (MSIL) 逆アセンブラ (Ildasm.exe) の概要について説明します。Ildasm.exe は、.NET Framework の .exe アセンブリまたは .dll アセンブリを解析し、人間が読むことのできる形式で情報を表示します。Ildasm.exe は Microsoft Intermediate Language (MSIL) コードだけでなく、名前空間と型、およびそれらのインターフェイスも表示します。Ildasm.exe を使用すると、.NET Framework のネイティブ アセンブリ (Mscorlib.dll など) や、他の開発者や自分が作成した .NET Framework アセンブリを調べることができます。ほとんどの .NET Framework 開発者にとって、Ildasm.exe は欠くことのできないツールになります。
このチュートリアルでは、SDK に付属している Visual C# バージョンの WordCount サンプルを使用してください。Visual Basic バージョンも使用できますが、2 つの言語バージョンで生成される MSIL はそれぞれ異なり、画面イメージも同一ではありません。WordCount は <FrameworkSDK>\Samples\Applications\WordCount\ ディレクトリにあります。このサンプルをビルドして実行する方法については、Readme.htm ファイルの指示に従ってください。このチュートリアルでは、Ildasm.exe を使用して WordCount.exe アセンブリを調べます。
最初に、WordCount サンプルをビルドし、次のコマンド ラインを使用して Ildasm.exe に読み込みます。
ildasm WordCount.exe
その結果、次の図に示すような Ildasm.exe ウィンドウが表示されます。
Ildasm ウィンドウ内のツリーには、WordCount.exe 内部に含まれるアセンブリ マニフェスト情報と、App、ArgParser、WordCountArgParser、および WordCounter という 4 つのグローバル クラス型が表示されています。
ツリー内のいずれかのグローバル クラス型をダブルクリックすると、その型に関する詳しい情報を表示できます。次の図では、WordCounter クラス型が展開されています。
この図には、WordCounter のすべてのメンバが表示されています。それぞれのシンボルが表す意味について次の表で説明します。
シンボル | 説明 |
---|---|
詳細情報あり | |
名前空間 | |
クラス | |
インターフェイス | |
値クラス | |
列挙型 (Enum) | |
メソッド | |
静的メソッド | |
フィールド | |
静的フィールド | |
イベント | |
プロパティ | |
マニフェストまたはクラスの情報項目 |
.class public auto ansi beforefieldinit エントリをダブルクリックすると、次の情報が表示されます。
この図では、WordCounter 型が System.Object 型から派生した型であることがすぐにわかります。
WordCounter 型は WordOccurrence という名前の別の型を含んでいます。WordOccurrence 型を展開すると、次の図に示すように、この型のメンバを表示できます。
ツリーを参照すると、WordOccurrence が System.IComparable インターフェイス、特に CompareTo メソッドを実装していることがわかります。しかし、以降の説明では WordOccurrence 型については無視して、WordCounter 型を重点的に見ていきます。
WordCounter 型には totalBytes、totalChars、totalLines、totalWords、および wordCounter という 5 つの private フィールドが含まれていることがわかります。これらのフィールドのうち最初の 4 つのフィールドは int64 型のインスタンスですが、wordCounter フィールドは System.Collections.SortedList 型への参照です。
フィールドの次には、メソッドも表示されています。最初のメソッド .ctor はコンストラクタです。この型にはコンストラクタが 1 つしかありませんが、型によっては、それぞれが異なるシグネチャを持つ複数のコンストラクタを持つ場合もあります。WordCounter コンストラクタは、他のすべてのコンストラクタの場合と同様、戻り値の型が void であり、パラメータを受け入れません。このコンストラクタ メソッドをダブルクリックすると、次の図に示すような新しいウィンドウが表示され、そのメソッドに含まれている MSIL コードが表示されます。
MSIL コードは非常に読みやすく理解しやすいコードです。詳細については、<FrameworkSDK>\Tool Developers Guide\Docs\ フォルダ内の Partition III CIL.doc ファイルにある「CIL Instruction Set Specification」を参照してください。ウィンドウの上部には、このコンストラクタが 50 バイトの MSIL コードを必要とすることが示されています。この数値からは、JIT コンパイラによって生成されるネイティブ コードのサイズはわかりません。このサイズは、ホスト CPU およびコードの生成に使用されるコンパイラによって異なるためです。
共通言語ランタイムはスタック ベースです。そのため、なんらかの操作を実行するために、MSIL コードは最初にオペランドを仮想スタックにプッシュし、それから演算子を実行します。演算子はスタックからオペランドを取り出し、必要な操作を実行し、その結果をスタックに戻します。このメソッドが一度に仮想スタックにプッシュできるオペランドの最大数は 8 個です。この数は、MSIL コードの直前に表示される .maxstack 属性を調べるとわかります。
最初のいくつかの MSIL 命令を見てみると、最初の 4 行は次のようになっています。
IL_0000: ldarg.0 ; Load the object's 'this' pointer on the stack
IL_0001: ldc.i4.0 ; Load the constant 4-byte value of 0 on the stack
IL_0002: conv.i8 ; Convert the 4-byte 0 to an 8-byte 0
IL_0003: stfld int64 WordCounter::totalLines
IL_0000 の命令は、メソッドに渡された最初のパラメータを仮想スタックに読み込みます。すべてのインスタンス メソッドは、必ずオブジェクトのメモリのアドレスを渡されます。この引数は引数ゼロと呼ばれ、メソッドのシグネチャに明示的には表示されません。そのため、.ctor メソッドが受け取る引数の数は 0 個のように見えますが、実際には引数を 1 個受け取ります。次に、IL_0000 の命令は、このオブジェクトへのポインタを仮想スタックに読み込みます。
IL_0001 の命令は、4 バイト定数値の 0 を仮想スタックに読み込みます。
IL_0002 の命令は、スタックの一番上の値 (4 バイトの 0) を取り出し、その値を 8 バイトの 0 に変換してからスタックの一番上に戻します。
この時点で、スタックには 8 バイトの 0 とオブジェクトへのポインタという 2 つのオペランドが含まれていることになります。IL_0003 の命令は、これらのオペランドを両方とも使用して、スタックの一番上の値 (8 バイトの 0) をスタック上で識別されたオブジェクトの totalLines フィールドに格納します。
totalChars、totalBytes、totalWords の各フィールドについても、同じ MSIL 命令シーケンスが繰り返されます。
wordCounter フィールドの初期化は、次に示す IL_0020 の命令で開始されます。
IL_0020: ldarg.0
IL_0021: newobj instance void [mscorlib]System.Collections.SortedList::.ctor()
IL_0026: stfld class [mscorlib]System.Collections.SortedList WordCounter::wordCounter
IL_0020 の命令は、WordCounter の this ポインタを仮想スタックにプッシュします。このオペランドは newobj 命令では使用されませんが、IL_0026 の stfld 命令で使用されます。
IL_0021 の命令は、共通言語ランタイムに新しい System.Collections.SortedList オブジェクトを作成し、そのコンストラクタを引数なしで呼び出すように指示します。newobj から制御が戻ると、SortedList オブジェクトのアドレスが仮想スタックに読み込まれます。この時点で、IL_0026 の stfld 命令が SortedList オブジェクトへのポインタを WordCounter オブジェクトの wordCounter フィールドに格納します。
WordCounter オブジェクトのすべてのフィールドが初期化されると、IL_002b の命令が this ポインタを仮想スタックにプッシュし、IL_002c が基本型 (System.Object) のコンストラクタを呼び出します。
IL_0031 の最後の命令はリターン命令であるため、WordCounter コンストラクタを作成したコードに制御が戻されます。コンストラクタは void を返すため、コンストラクタが制御を返すまでは、スタックには何も配置されません。
別の例を次に示します。GetWordsByOccurranceEnumerator メソッドをダブルクリックして、次の図に示すような MSIL コードを表示します。
このメソッドのコードのサイズは 69 バイトで、このメソッドが仮想スタック上に 4 つのスロットを必要とすることがわかります。さらに、このメソッドは 3 つのローカル変数を持っています。1 つは System.Collection.SortedList 型で、他の 2 つは System.Collections.IDictionaryEnumerator 型です。ソース コードで記述されている変数名は、アセンブリが /debug オプションを使用してコンパイルされない限りは MSIL コードに生成されません。/debug が使用されていない場合は、変数名 V_0、V_1、および V_2 がそれぞれ sl、de、および CS$00000003$00000000 の代わりに使用されます。
このメソッドを実行すると、最初に newobj 命令が実行されます。この命令は、新しい System.Collections.SortedList を作成し、このオブジェクトの既定のコンストラクタを呼び出します。newobj から制御が戻ると、作成されたオブジェクトのアドレスが仮想スタックに読み込まれます。stloc.0 命令 (IL_0005) は、この値を System.Collections.SortedList 型のローカル変数 0、または sl (/debug を使用しない場合は V_0) に格納します。
IL_0006 と IL_0007 の命令では、WordCounter オブジェクトの this ポインタ (このメソッドに渡された引数ゼロ) が仮想スタックに読み込まれ、GetWordsAlphabeticallyEnumerator メソッドが呼び出されます。call 命令から制御が戻ると、列挙体のアドレスが仮想スタックに読み込まれます。stloc.1 命令 (IL_000c) は、このアドレスを System.Collections.IDictionaryEnumerator 型のローカル変数 1、または de (/debug を使用しない場合は V_1) に格納します。
IL_000d の br.s 命令によって、while ステートメントの IL テスト条件への無条件分岐が実行されます。この IL テスト条件は IL_0032 の命令で開始されます。IL_0032 では、de (または V_1) (IDictionaryEnumerator) のアドレスが仮想スタックにプッシュされ、IL_0033 でその MoveNext メソッドが呼び出されます。MoveNext が true を返す場合は、列挙されるエントリが存在するので、brtrue.s 命令が IL_000f の命令にジャンプします。
IL_000f と IL_0010 の命令では、sl (または V_0) および de (または V_1) のオブジェクトのアドレスが仮想スタックにプッシュされます。次に、IdictionaryEnumerator オブジェクトの get_Value プロパティ メソッドが、現在のエントリの出現回数を取得するために呼び出されます。この数は 32 ビット値で、System.Int32 に格納されます。このコードはこの Int32 オブジェクトを int 値型にキャストします。参照型を値型にキャストするためには、IL_0016 の unbox 命令が必要です。unbox から制御が戻ると、ボックス化解除された値が仮想スタックに読み込まれます。ldind.i4 命令 (IL_001b) は 4 バイト値を仮想スタックに読み込みます。この値は、現在仮想スタック上にあるアドレスへのポインタです。つまり、ボックス化解除された 4 バイト整数が仮想スタック上に配置されます。
IL_001c の命令では、sl (または V_1) の値 (IDictionaryEnumerator のアドレス) がスタックにプッシュされ、その get_Key プロパティ メソッドが呼び出されます。get_Key から制御が戻ると、System.Object のアドレスが仮想スタックに読み込まれます。コードはディクショナリに文字列が含まれることを認識し、コンパイラが IL_0022 の castclass 命令を使用してこの Object を String にキャストします。
その次のいくつかの命令 (IL_0027 から IL_002d まで) では、新しい WordOccurrence オブジェクトを作成し、このオブジェクトのアドレスを SortedLists の Add メソッドに渡します。
IL_0032 の命令では、while ステートメントのテスト条件が再評価されます。MoveNext が true を返す場合、ループは別の繰り返し処理を実行します。しかし、MoveNext が false を返す場合は、実行はループ内で失敗し、IL_003a の命令で終了します。IL_003a から IL_0040 までの命令は、SortLists オブジェクトの GetEnumerator メソッドを呼び出します。返される値は System.Collections.IDictionaryEnumerator です。この値はスタックに残され、GetWordsByOccurrenceEnumerator の戻り値となります。