31

.NET 関数を逆アセンブルすると、それらはすべて似たようなパターンで始まることに気付きます。この最初のコードは何をしますか?

このコードは、関数が実行するはずの実際のコードの前に表示されます。ある種のパラメーター数の検証ですか?

機能1

private static void Foo(int i)
{
   Console.WriteLine("hello");
}

00000000  push        ebp 
00000001  mov         ebp,esp 
00000003  push        eax 
00000004  mov         dword ptr [ebp-4],ecx 
00000007  cmp         dword ptr ds:[005C14A4h],0 
0000000e  je          00000015 
00000010  call        65E0367F 
//the console writleline code follows here and is not part of the question

機能2

static private void Bar()
{
   for (int i = 0; i < 1000; i++)
   {
      Foo(i);
   }
}

00000000  push        ebp 
00000001  mov         ebp,esp 
00000003  push        eax 
00000004  cmp         dword ptr ds:[006914A4h],0 
0000000b  je          00000012 
0000000d  call        65CC36CF 
// the for loop code follows here

func3

private static void Foo()
{
   Console.WriteLine("hello");
}

00000000  push        ebp 
00000001  mov         ebp,esp 
00000003  cmp         dword ptr ds:[005614A4h],0 
0000000a  je          00000011 
0000000c  call        65E3367F 

[編集] では、これは正しい説明ですか?

//fix stackframe
00000000  push        ebp 
00000001  mov         ebp,esp 
//store eax so it can be used locally
00000003  push        eax 
//ensure static ctor have been called
00000004  cmp         dword ptr ds:[006914A4h],0 
//it has been called, ignore it
0000000b  je          00000012
//it hasn't been called, call it now 
0000000d  call        65CC36CF 

また?

4

3 に答える 3

19

このプロローグには2つの部分があります。

スタックフレームの設定

これにより、現在のEBPレジスタがスタックに格納され、スタックポインタ(ESP)の値がEBPに割り当てられます。

push        ebp 
mov         ebp,esp

スタックに格納されているローカル変数がある場合(つまり、使用可能なレジスタに十分なスペースがない場合)、ESPはそのサイズによって移動され、現在の関数のスタックフレームを構築します。

また、関数の最後にこれらの操作が逆になっているので、前の関数のスタックフレームが復元されます。

EBPは、常に現在の関数
ESPのスタックフレームの先頭を最後まで指す必要があります(スタックが下に大きくなるため、x86ではアドレスが低くなります)。

これは一般的な呼び出し規約の一部であり、例外がスローされたときにスタックを展開するために必要です。これは.net固有ではなく、windows/x86のほとんどの呼び出し規約で使用されます。

スタックフレームを設定した後、いくつかのレジスタをスタックに格納するのが一般的です。これは、特定のレジスタを一時変数として使用したい場合があるためですが、呼び出し規約では、それらを保存するために関数が必要です。したがって、それらをスタックにバックアップする必要があります。どのレジスタを保持する必要があり、どのレジスタを変更できるかは、使用する呼び出し規約によって異なります。

スタック上のローカル変数を参照する場合[ebp-x]、whereはスタックebpフレームの先頭を指し、xはスタックフレームのどこに変数が格納されているかを示すオフセットです。[esp+y]または、スタックフレームの端からオフセットして使用することもできます。

静的コンストラクター/初期化子の呼び出し

danbystromが気付いたように、2番目の部分は、静的コンストラクター/初期化子の呼び出しである可能性が最も高いです。静的コンストラクターはプログラムの起動時に呼び出されるのではなく、最初のアクセス時に呼び出されるため、静的コンストラクターが既に実行されていることをジッターが保証できないすべてのアクセスは、呼び出されているかどうかを確認し、呼び出されていない場合は呼び出す必要があります。

00000004  cmp         dword ptr ds:[006914A4h],0 
0000000b  je          00000012 
0000000d  call        65CC36CF

これはのようなものif (globalVar!=0) Call Function_65CC36CFです。ほとんどの場合、グローバル変数は静的コンストラクターが実行されたかどうかを示し、呼び出しは静的コンストラクターの呼び出しです。


私の知る限り、分解に関するあなたのコメントは正しいです。


スタックフレームに関するこのOldNewThingブログエントリを確認してください:壊れたスタックトレースを救済する方法:EBPチェーンの回復

于 2011-02-10T13:46:21.387 に答える
2

メソッドはすべて静的であるため、コードはクラスの静的初期化子がまだ実行されているかどうかを確認するために使用されます。

00000007  cmp    dword ptr ds:[005C14A4h],0  ; test if static initializer has executed
0000000e  je     00000015                    ; skip call to initializer if already done
00000010  call   65E0367F                    ; call static initializer
00000015  ....                               ; continue with the method's code
于 2011-02-10T13:54:45.667 に答える
0

プッシュと移動について話している場合、それは単にコールスタックを修正しているだけです。それらのセグメントの残りの部分が頭のてっぺんから何をしているのかわかりません。

于 2011-02-10T13:47:47.407 に答える