2009-05-20 12 views
4

私は制御できないホストから呼び出されるDLLにプラグインコードを書いています。Visual Studioでカスタムプロローグとエピローグコードを使用してネイキッド関数を記述する

ホストは、プラグインが__stdcall関数としてエクスポートされることを前提としています。ホストには、関数の名前と引数の詳細が渡され、LoadLibrary、GetProcAddressを介して動的に呼び出され、手動で引数をスタックにプッシュします。

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

それぞれの内部関数は異なる引数をとることがありますが、これは物理的なエントリポイント名と共にホストに伝えられます。私の物理的なdllエントリーポイントはすべて、単一のvoid *ポインターを取るように定義されています。そして、最初の引数からのオフセットとホストに伝達された既知の引数リストから作業することによって、スタックから後続のパラメーターをマーシャリングします。

ホストは、正しい引数でプラグインの関数を正常に呼び出すことができ、すべて正常に動作します...しかし、私の知っている機能は、それらは4バイトのポインタを取る__stdcall関数として定義されているので、呼び出し側がもっと多くの引数をスタックにプッシュしても、常に最後に 'ret 4'を返します。 b)私は、ret 4が返されたスタックの4バイトをあまりにも多くポップするので、引数を取らない関数を扱うことはできません。

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

__cdeclに切り替えて、まったくクリーンアップしないで、b)を解決できます。私は裸の関数に切り替えて、自分の一般的な引数をクリーンアップコードを書くことによって)私は解決することができると仮定します。

私はちょうど私がそれはのような単純なものであろうと期待していたと呼ばれていた機能で使用される引数のスペースの量を知っているので:

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       
    } 
} 

が、これは右見ていますか?

+0

これは決して使用されていないので個人的にフレームポインタを気にすることはありませんが、それ以外の解決策はうまくいきます。 –

+0

元々は、argsスペースと戻り値のスペースをフレームポインタから索引付けしていましたが、eaxの戻り値を移動してから戻ってしまいました。フレームポインタの必要性がなくなったと思います。私はそれを取り除くべきだと思う...助けてくれてありがとう。 –

答えて

4

retには一定の引き数が必要なため、関数のパラメータ数を一定にする必要がありますが、そのような状況は関数から戻る準備ができた時点でのみ必要です。したがって、関数の終了直前に、次のようにしてください:

  1. スタックの先頭から戻りアドレスをポップして一時的に格納します。 ECXは良い場所です。
  2. 可変個数の引数をスタックから個別にポップするか、ESPを直接調整して、スタックから削除します。
  3. 戻りアドレスをスタックに戻します。
  4. 定数引数を使用してretを使用します。

ところで、(a)と呼ばれる問題は、一般的には問題になります。呼び出し側は、スタックポインタの代わりにフレームポインタを使用して、常に独自のローカル変数を参照しているように見えることは幸いです。しかし、それを行うための機能は必要ではなく、将来のバージョンのホストプログラムが引き続きそのように動作するという保証はありません。コンパイラはまた、呼び出しの間のみスタックにいくつかのレジスタ値を保存し、後にそれらを再びポップできると期待しています。あなたのコードはそれを破るでしょう。

+0

おかげでロブ、私はそれを試してみましょう。 –

関連する問題