ディスアセンブルされたコードを読む場合は、下から読む ことをお勧めします。
xenowire が過去に作成した MSIL Analyzer (以下アナライザ) や パンダナス でも、下から読むこと+各OpCodeによるスタックの変化予測で動きを捉えていました。
また、インデントによりコードを視覚的に把握できるようになります。
このサイトの IL に関する各ページのインデントも、スタックの動きを視覚的に把握するため意図的に挿入されています。
次のコードは、あるアプリケーションの関数の一部分です。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
|
ldloc.0
ldc.i4.0
ldloc.0
callvirt instance int32 string::get_Length()
ldc.i4.1
sub
callvirt instance string string::Substring(int32, int32)
stloc.0
ldsfld class [mscorlib]System.Collections.Hashtable Visionary::a
callvirt instance class [mscorlib]System.Collections.ICollection class [mscorlib]System.Collections.Hashtable::get_Values()
newobj instance void class [mscorlib]System.Collections.ArrayList::.ctor(class [mscorlib]System.Collections.ICollection)
ldloc.0
ldnull
newobj instance void class Pandects::.ctor(string, class [System.Drawing]System.Drawing.Bitmap)
callvirt instance int32 class [mscorlib]System.Collections.ArrayList::IndexOf(object)
stloc.1
ldloc.1
ldc.i4.0
blt.s _fin
ldsfld class [mscorlib]System.Collections.Hashtable Visionary::a
callvirt instance class [mscorlib]System.Collections.ICollection class [mscorlib]System.Collections.Hashtable::get_Values()
newobj instance void class [mscorlib]System.Collections.ArrayList::.ctor(class [mscorlib]System.Collections.ICollection)
ldloc.1
callvirt instance object class [mscorlib]System.Collections.ArrayList::get_Item(int32)
castclass class Pandects
callvirt instance class [System.Drawing]System.Drawing.Bitmap class Pandects::a()
ret
_fin: ldsfld class [System.Drawing]System.Drawing.Bitmap Visionary::f
ret
|
何をどの順序で行っているか分かるでしょうか?
ディスアセンブラは往々にして上記のようなコードを出力します。
特に、引数/変数のpopと、メンバ関数を呼び出すためのpopが混ざり、非常に読みづらいコードになっています。
下の画面は、アナライザのスタック定義ファイルの内容です。
アナライザがスタック定義ファイルを元に、スタックをシミュレーションした結果が下のマトリクスです。
前述の通り、アナライザは 下から コードを読んでいきますので、
各行の値は、それより上のコードに求めるデータ数を示しています。
従って 0 が三つ並んでいる行は、「期待されたデータが全て揃った」 ―― 「処理単位の完結」 を示しています。
0 0 0 // ldloc
1 0 0 // ldc
1 1 0 // ldloc
1 1 1 // callvirt
1 1 1 // ldc
1 1 2 // sub
1 2 0 // callvirt
1 0 0 // stloc
0 0 0 // ldsfld
0 1 0 // callvirt
0 1 0 // newobj
1 0 0 // ldloc
1 0 1 // ldnull
1 0 2 // newobj
1 1 0 // callvirt
1 0 0 // stloc
0 0 0 // ldloc
1 0 0 // ldc
2 0 0 // blt
0 0 0 // ldsfld
0 0 1 // callvirt
0 0 1 // newobj
0 1 0 // ldloc
0 1 1 // callvirt
0 1 0 // castclass
1 0 0 // callvirt
1 0 0 // ret
0 0 0 // ldsfld
1 0 0 // ret
このマトリクスを元にしたアナライザによる整形結果です:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
|
ldloc.0
ldc.i4.0
ldloc.0
callvirt instance int32 string::get_Length()
ldc.i4.1
sub
callvirt instance string string::Substring(int32, int32)
stloc.0
ldsfld class [mscorlib]System.Collections.Hashtable Visionary::a
callvirt instance class [mscorlib]System.Collections.ICollection class [mscorlib]System.Collections.Hashtable::get_Values()
newobj instance void class [mscorlib]System.Collections.ArrayList::.ctor(class [mscorlib]System.Collections.ICollection)
ldloc.0
ldnull
newobj instance void class Pandects::.ctor(string, class [System.Drawing]System.Drawing.Bitmap)
callvirt instance int32 class [mscorlib]System.Collections.ArrayList::IndexOf(object)
stloc.1
ldloc.1
ldc.i4.0
blt.s _fin
ldsfld class [mscorlib]System.Collections.Hashtable Visionary::a
callvirt instance class [mscorlib]System.Collections.ICollection class [mscorlib]System.Collections.Hashtable::get_Values()
newobj instance void class [mscorlib]System.Collections.ArrayList::.ctor(class [mscorlib]System.Collections.ICollection)
ldloc.1
callvirt instance object class [mscorlib]System.Collections.ArrayList::get_Item(int32)
castclass class Pandects
callvirt instance class [System.Drawing]System.Drawing.Bitmap class Pandects::a()
ret
_fin:
ldsfld class [System.Drawing]System.Drawing.Bitmap Visionary::f
ret
|
引数/変数のpop と メンバ関数を呼び出すためのpop が区別されてインデントされています。
メンバ関数を呼び出すためのpop と callvirt のインデントの深さが同じであることは、
C++ でメンバ関数を呼び出すときの . あるいは -> 、静的メンバであれば :: の意味を表すものとなっています。
ldloc.0
callvirt instance int32 string::get_Length()
引数/変数は一段階深くインデントされており、どの opcode のための引数/変数であるのかが分かるようになっています。
ldloc.1
ldc.i4.0
blt.s _fin
castclass は、pop して push するというスタックの積載量には特に変化を与えない処理であるため、インデントの深さは直後の行と変わりません。
castclass class Pandects
callvirt instance class [System.Drawing]System.Drawing.Bitmap class Pandects::a()
callvirt は 引数の個数分 pop しますが、
返り値の無いものは push 無しであるため、callvirt 自身のインデントの深さは直後の行と変わりません。
返り値のあるものは引数と同じく、直後の行より一段階深くインデントされています。
sub は 2回 pop しますが必ず 1回 push するため、直後の行に対して一段階深くインデントされています。
sub
callvirt instance string string::Substring(int32, int32)
stloc.0
最後にアナライザによる C# へのディスコンパイル結果です:
1
2
3
4
5
6
7
|
|
V_0 = V_0.Substring( 0, V_0.get_Length() - 1 );
V_1 = new System.Collections.ArrayList( Visionary.a.get_Values() ).IndexOf( new Pandects( V_0, null ) );
if( V_1 < 0 )
return Visionary.f;
return ( ( class Pandects ) new System.Collections.ArrayList( Visionary.a.get_Values() ).get_Item( V_1 ) ).a();
|
以上から、
- V_0 の 最後の 1文字を切り取り V_0 に格納
- V_0 を元に Pandects オブジェクトを生成し、 Visionary.a (ArrayList) の中のインデックス番号を V_1 に格納
- Visionary.a に探している値が存在しない場合は Visionary.f を return
- Visionary.a に探している値が存在した場合はその値を Pandects にキャストし、メンバ関数a()を呼び出してその返り値を return
していることが分かります。
なお、下から読むこの方法は、スタックシミュレーションマトリクスに 0 が並ぶことを利用しているため、pushされる順番がゴチャゴチャになっているコードに対しても有効です。