Visual Studio でカスタム プロローグ コードとエピローグ コードを使用してネイキッド関数を作成する

StackOverflow https://stackoverflow.com/questions/886836

質問

私は制御できないホストによって呼び出される dll にプラグイン コードを作成しています。

ホストは、プラグインが __stdcall 関数としてエクスポートされると想定します。ホストには、関数の名前と、期待される引数の詳細が通知され、LoadLibrary、GetProcAddress を介してその呼び出しを動的に作成し、手動で引数をスタックにプッシュします。

通常、プラグイン DLL は定数インターフェイスを公開します。私のプラグインは、DLL のロード時に設定されるインターフェイスを公開します。これを実現するために、私のプラグインは、DLL のコンパイル時に定義される一連の標準エントリ ポイントを公開し、必要に応じて公開されている内部機能にそれらを割り当てます。

各内部関数は異なる引数を取ることができますが、これは物理エントリポイント名とともにホストに伝達されます。すべての物理 DLL エントリポイントは、単一の void * ポインターを受け取るように定義されており、最初の引数とホストに通知された既知の引数リストからのオフセットに基づいて、スタックから後続のパラメーターを自分でマーシャリングします。

ホストは正しい引数を指定してプラグインの関数を正常に呼び出すことができ、すべて正常に動作します...ただし、a) 私の関数は、4バイトのポインターを取る__stdcall関数として定義されているため、スタックをクリーンアップしていないことを認識しています。そのため、関数は常に「ret 4」を実行します。呼び出し元がさらに多くの引数をスタックにプッシュした場合でも終了します。b) ret 4 は戻り時にスタックから 4 バイトをポップしすぎるため、引数を取らない関数を処理できません。

プラグインからホストの呼び出しコードをトレースすると、実際には a) はそれほど大したことではないことがわかります。ホストはディスパッチ呼び出しから戻るまでスタックスペースを失い、その時点でスタックフレームをクリーンアップしてゴミをクリーンアップします。しかし...

b) は __cdecl に切り替え、クリーンアップをまったく行わないことで解決できます。a) は、ネイキッド関数に切り替えて、独自の汎用引数クリーンアップ コードを作成することで解決できると思います。

呼び出されたばかりの関数によって使用される引数スペースの量はわかっているので、次のように単純になることを期待していました。

extern "C" __declspec(naked) __declspec(dllexport) void  * __stdcall EntryPoint(void *pArg1)
{                                                                                                        
   size_t argumentSpaceUsed;
   {
      void *pX = RealEntryPoint(
         reinterpret_cast<ULONG_PTR>(&pArg1), 
         argumentSpaceUsed);

      __asm
      {
         mov eax, dword ptr pX
      }
   }
   __asm
   {
      ret argumentSpaceUsed
   }
}

しかし、retにはコンパイル時定数が必要なので、それは機能しません...助言がありますか?

更新しました:

ロブ・ケネディの提案のおかげで、私はこれにたどり着きました、それはうまくいくようです...

extern "C" __declspec(naked) __declspec(dllexport) void  * __stdcall EntryPoint(void *pArg1)
{      
   __asm {                                                                                                        
      push ebp          // Set up our stack frame            
      mov ebp, esp  
      mov eax, 0x0      // Space for called func to return arg space used, init to 0            
      push eax          // Set up stack for call to real Entry point
      push esp
      lea eax, pArg1                
      push eax                      
      call RealEntryPoint   // result is left in eax, we leave it there for our caller....         
      pop ecx 
      mov esp,ebp       // remove our stack frame
      pop ebp  
      pop edx           // return address off
      add esp, ecx      // remove 'x' bytes of caller args
      push edx          // return address back on                   
      ret                        
   }
}

これは正しいように見えますか?

役に立ちましたか?

解決

以来 ret 定数の引数が必要な場合は、関数が一定数のパラメータを持つように調整する必要がありますが、その状況が必要になるのは、関数から戻る準備ができた時点でのみです。したがって、関数の終了直前にこれを実行します。

  1. 戻りアドレスをスタックの最上位からポップし、一時アドレスに格納します。 ECX 良いところです。
  2. 可変数の引数をスタックから削除するには、各引数を個別にポップするか調整します。 ESP 直接。
  3. 戻りアドレスをスタックにプッシュして戻します。
  4. 使用 ret 定数引数を使用します。

ちなみに、あなたが (a) として言及している問題は、一般的なケースでは実際に問題です。呼び出し側が常にスタック ポインタではなくフレーム ポインタを使用して独自のローカル変数を参照しているように見えるのは幸運でした。ただし、関数がそれを行う必要はなく、ホスト プログラムの将来のバージョンがそのように動作し続けるという保証はありません。また、コンパイラは、呼び出しの間だけ一部のレジスタ値をスタックに保存し、その後それらの値を再びポップできることを期待する傾向があります。コードを作成するとそれが壊れてしまいます。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top