ディスアセンブルされたコードを読む場合は、下から読む ことをお勧めします。

xenowire が過去に作成した MSIL Analyzer (以下アナライザ) や パンダナス でも、下から読むこと+各OpCodeによるスタックの変化予測で動きを捉えていました。

ilastack.png


また、インデントによりコードを視覚的に把握できるようになります。

このサイトの 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が混ざり、非常に読みづらいコードになっています。


下の画面は、アナライザのスタック定義ファイルの内容です。

ilaops.png


アナライザがスタック定義ファイルを元に、スタックをシミュレーションした結果が下のマトリクスです。

前述の通り、アナライザは 下から コードを読んでいきますので、
各行の値は、それより上のコードに求めるデータ数を示しています。

従って 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# へのディスコンパイル結果です:

ilacode.png
すべてを展開すべてを収束
  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();

以上から、

  1. V_0 の 最後の 1文字を切り取り V_0 に格納
  2. V_0 を元に Pandects オブジェクトを生成し、 Visionary.a (ArrayList) の中のインデックス番号を V_1 に格納
  3. Visionary.a に探している値が存在しない場合は Visionary.f を return
  4. Visionary.a に探している値が存在した場合はその値を Pandects にキャストし、メンバ関数a()を呼び出してその返り値を return

していることが分かります。


なお、下から読むこの方法は、スタックシミュレーションマトリクスに 0 が並ぶことを利用しているため、pushされる順番がゴチャゴチャになっているコードに対しても有効です。


トップ 編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード 新規 一覧 単語検索 最終更新 リンク元 ヘルプ 最終更新のRSS xenowire
Last-modified: Tue, 01 Sep 2009 09:20:29 JST