7

次の奇妙なコードのチャンクに遭遇しました。次の typedef があると想像してください。

typedef int (*MyFunctionPointer)(int param_1, int param_2);

そして、関数では、次の方法で DLL から関数を実行しようとしています。

LPCWSTR DllFileName;    //Path to the dll stored here
LPCSTR _FunctionName;   // (mangled) name of the function I want to test

MyFunctionPointer functionPointer;

HINSTANCE hInstLibrary = LoadLibrary( DllFileName );
FARPROC functionAddress = GetProcAddress( hInstLibrary, _FunctionName );

functionPointer = (MyFunctionPointer) functionAddress;

//The values are arbitrary
int a = 5;
int b = 10;
int result = 0;

result = functionPointer( a, b );  //Possible error?

問題は、LoadLibrary で取得したアドレスの関数が 2 つの整数引数を取るかどうかを知る方法がないことです。DLL 名は実行時にユーザーによって提供され、エクスポートされた関数の名前がリストされ、ユーザーテストするものを選択します(実行時も :S:S )。では、最後の行で関数呼び出しを行うことで、スタック破損の可能性への扉を開いているのではないでしょうか? これがコンパイルされることはわかっていますが、指している関数に間違った引数を渡した場合、どのような実行時エラーが発生するのでしょうか?

4

8 に答える 8

4

予想されるパラメーターと使用されるパラメーターの数または型、および呼び出し規則が異なる場合に考えられる 3 つのエラーがあります。

  • 呼び出し規約が異なる場合、間違ったパラメーター値が読み取られます
  • 関数が実際に与えられたよりも多くのパラメーターを期待している場合、ランダムな値がパラメーターとして使用されます (ポインターが含まれている場合の結果を想像させてください)
  • いずれにせよ、戻りアドレスは完全にガベージになるため、関数が戻るとすぐに、ランダム データを含むランダム コードが実行されます。

一言で言えば:Undefined behavior

于 2010-03-04T15:38:14.367 に答える
4

残念ながら、知る方法はありません。プログラマーは、関数ポインターを取得して使用するときに、事前にプロトタイプを知っている必要があります。

事前にプロトタイプがわからない場合は、DLL で既知の関数を呼び出すことにより、関数名とそのパラメーターを列挙できる DLL を使用して、ある種のプロトコルを実装する必要があると思います。もちろん、このプロトコルに準拠するように DLL を作成する必要があります。

于 2010-03-04T14:58:41.893 に答える
3

それが __stdcall 関数であり、名前マングリングをそのまま残した場合(どちらも大きな if ですが、それでも確かに可能です)、名前@nnの最後にあるnnのは数字です。その数は、関数が引数として期待するバイト数であり、戻る前にスタックをクリアします。

したがって、それが大きな懸念事項である場合は、関数の生の名前を調べて、スタックに入れるデータの量がスタックから消去されるデータの量と一致することを確認できます。

これは、Machiavelli ではなく、Murphy に対する保護に過ぎないことに注意してください。DLL を作成するときは、エクスポート ファイルを使用して関数の名前を変更できます。これは、名前のマングリングを取り除くためによく使用されますが、期待するパラメーターについて読者を誤解させるために、関数の名前を xxx@12 から xxx@16 (またはその他のもの) に変更することもできると確信しています。

編集: (主に msalters のコメントへの返信): __stdcall をメンバー関数のようなものに適用できないのは事実ですが、C または C++ で記述されているかどうかにかかわらず、グローバル関数のようなものには確かに使用できます。

メンバー関数などの場合、関数のエクスポートされた名前はマングルされます。その場合、 を使用UndecorateSymbolNameして完全な署名を取得できます。それを使用することはやや自明ではありませんが、とてつもなく複雑でもありません。

于 2010-03-04T15:21:57.090 に答える
1

私はそうは思いません、それは良い質問です。唯一の規定は、関数ポインターが機能するためのパラメーターが何であるかを知っている必要があるということです.そうしないと、やみくもにパラメーターを詰め込んで呼び出すと、クラッシュまたはジャンプします二度と見られることはありません...関数が期待するものとパラメーターのタイプに関するメッセージを伝えるのはプログラマー次第です。幸いなことに、それを逆アセンブルして、スタックポインターと期待されるアドレスを見て見つけることができます「スタック ポインター」(sp) を使用して、パラメーターの型を調べます。

たとえば、PE Explorer を使用すると、どの関数が使用されているかを調べたり、逆アセンブリ ダンプを調べたりできます...

これがお役に立てば幸いです。よろしくお願いします、トム。

于 2010-03-04T15:02:38.483 に答える
1

DLL コードでクラッシュするか (壊れたデータが渡されたため)、または Visual C++ がこの種の問題を検出するためにデバッグ ビルドにコードを追加していると思います。「関数呼び出しで ESP の値が保存されませんでした」のようなメッセージが表示され、呼び出しの近くのコードが示されます。それは役立ちますが、完全に堅牢ではありません-間違っているが同じサイズの引数を渡すのを止めることはないと思います(たとえば、x86のchar *パラメーターの代わりにint)。他の回答が言うように、本当に知っておく必要があります。

于 2010-03-04T15:15:00.847 に答える
1

一般的な答えはありません。標準は、特定の状況で特定の例外をスローすることを義務付けていますが、それとは別に、準拠するプログラムがどのように実行されるかを説明し、特定の違反が診断につながる必要があると時々述べています。(あちこちにもっと具体的なものがあるかもしれませんが、確かに覚えていません。)

そこでコードが行っていることは、標準に従っているわけではありません。また、キャストがあるため、コンパイラーは先に進んで、プログラマーが望む愚かなことを文句なしに行う権利があります。したがって、これは実装上の問題になります。

実装のドキュメントを確認できますが、おそらくそこにもありません。実験するか、実装で関数呼び出しがどのように行われるかを調べることができます。

残念ながら、答えは、すぐに明らかにならずに何かを台無しにする可能性が非常に高いです。

于 2010-03-04T15:43:45.713 に答える
0

ほとんどのC/C ++コンパイラでは、呼び出し元が呼び出し前にスタックを設定し、後でスタックポインタを再調整します。呼び出された関数がポインタまたは参照引数を使用しない場合、結果は無価値になりますが、メモリの破損はありません。そして、再実行が言うように、ポインタ/参照の間違いは、ほとんどの場合、わずかなテストで現れます。

于 2010-03-04T15:06:23.483 に答える
0

通常、LoadLibrary と GetProcByAddrees を呼び出す場合、プロトタイプを示すドキュメントがあります。さらに一般的には、すべての windows.dll と同様に、ヘッダー ファイルが提供されます。これが間違っているとエラーが発生しますが、通常は非常に簡単に観察でき、本番環境に忍び込むようなエラーではありません。

于 2010-03-04T15:01:19.420 に答える