4


2

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

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

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

各内部関数は異なる引数を取ることができますが、これは物理エントリポイント名とともにホストに伝達されます。 私のすべての物理dllエントリポイントは単一のvoid *ポインタを取るように定義されており、最初の引数とホストに伝えられた既知の引数リストからのオフセットから作業することでスタックから後続のパラメータを整列します。

ホストは私のプラグインの中の関数を正しい引数で正しく呼び出すことができ、すべてうまくいきます…​ しかし、私は、a)自分の関数が4バイトのポインタを取る__stdcall関数として定義されているので、スタックをクリーンアップしていないことを認識しています。呼び出し側がより多くの引数をスタックにプッシュしたとしても終了します。 そして、b)ret 4が戻り時にスタックから4バイトを飛び出しすぎて引数を取らない関数を扱うことはできません。

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

b)を解決するには、__ cdeclに切り替えて、まったくクリーンアップしないようにします。 私は、裸の関数に切り替えて私自身の一般的な議論をきれいにするコードを書くことによってa)を解決できると思います。

ちょうど呼ばれた関数によって使用される引数空間の量を知っているので、私はそれができるだけ単純であることを望みました:

extern "C" __declspec(裸)__declspec(dllexport) {void * pX = RealEntryPoint(reinterpret_cast(

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

しかしretはコンパイル時定数が必要なのでうまくいきません。 助言がありますか?

更新しました:

Rob Kennedyの提案に感謝します。これはうまくいくようです…​

extern "C" __declspec(裸)__declspec(dllexport)void * __stdcall EntryPoint(void * pArg1){__asm {push ebp //スタックフレームを設定するmov ebp、esp mov eax、0x0 // argを返すための呼ばれるfuncのためのスペース使用されたスペース、init to 0 push eax //実際のエントリへの呼び出しのためのスタックの設定エントリポイントpush esp lea eax、pArg1 push eax呼び出しRealEntryPoint //結果はeaxに残されます。呼び出し元のためにそのまま残します。 pop ecx mov esp、ebp //スタックフレームを削除するpop ebp pop edx //戻りアドレスを追加esp、ecx //呼び出し引数の 'x'バイトを削除するedx //戻りアドレスをretに戻す}}}

これは正しいですか?

1 Answer


4


`+ ret +`は定数の引数を必要とするため、関数に定数のパラメーターを設定する必要がありますが、その状況は関数から戻る準備ができた時点でのみ必要です。 それで、関数の終わりの直前に、これをしてください:

  1. スタックの一番上から戻りアドレスをポップして、 一時的; `+ ECX +`は良い場所です。

  2. 次のいずれかの方法で、スタックから可変個の引数を削除します それぞれを個別にポップするか、「+ ESP +」を直接調整します。

  3. リターンアドレスをスタックに戻します。

  4. 定数引数で `+ ret +`を使用します。

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