COM オブジェクト ( ) からメソッドを呼び出すコードが少しありますIDirect3D9
が、すべての呼び出しでランタイム チェック エラー #0 が発生します。失敗は、呼び出し全体で ESP が適切に保持されていないために発生するため、何らかの種類のスタックの問題 (COM メソッドがすべてであるため__stdcall
) です。珍しい部分は、メソッドの署名と状況の単純さです。
コードは、DirectX SDK (2010 年 6 月) ヘッダーとライブラリを使用して、MSVC 10 (VS 2010 SP1) で 32 ビット モードのみでビルドされます。ヘッダーが破損していないことを確認するために SDK を再インストールしましたが、運が悪かったのです。
VSのデバッガーとWinDBGの両方を接続してコードを実行し、再起動/ドライバーの更新後に複数回実行しました。問題は毎回発生し、同じです。gflags でヒープ検証 (および他のほとんどのオプション) を有効にしても、それ以上の情報は提供されないようです。どちらも、ポップアップと同じエラー、またはその直後に発生した segfault を報告するだけです。
呼び出しがなければ (代わりに定数値を返す)、プログラムは期待どおりに実行されます。ここで何がうまくいかないのかについてのアイデアがありません。
問題の関数はIDirect3D9::GetAdapterModeCount
、D3D8-to-9 ラッパー (古いゲームのグラフィックス アップグレード プロジェクトの一部) から呼び出されます。より一般的な情報については、完全なファイルがここにあります。
次の形式の呼び出しをすべて試しました。
UINT r = m_Object->GetAdapterModeCount(D3DADAPTER_DEFAULT, D3DFMT_X8R8G8B8);
UINT r = m_Object->GetAdapterModeCount(0, (D3DFORMAT)22);
UINT adapter = D3DADAPTER_DEFAULT;
D3DFORMAT format = D3DFMT_X8R8G8B8; // and other values
UINT r = m_Object->GetAdapterModecount(adapter, format);
これらすべてがチェックの失敗を引き起こします。m_Object
は有効なIDirect3D9
であり、以前はさまざまな他の呼び出しに使用されていました。具体的には次のとおりです。
201, 80194887, Voodoo3D8, CVoodoo3D8::GetAdapterCount() == 3
201, 80195309, Voodoo3D8, CVoodoo3D8::GetAdapterIdentifier(0, 2, 0939CBAC) == 0
201, 80195309, Voodoo3D8, CVoodoo3D8::GetAdapterDisplayMode(0, 0018F5B4) == 0
201, 80196541, Voodoo3D8, CVoodoo3D8::GetAdapterModeCount(0, D3DFMT_X8R8G8B8) == 80
シーケンスはデバッグ トレース コードによってログに記録され、正しいように見え、期待値 (3 つのモニターなど) を返します。私の側の同じオブジェクト(の単一インスタンスCVoodoo3D8
)による最初の3つの呼び出しは、スタック警告なしですべて成功します。4番目はそうではありません。
呼び出しを並べ替えてGetAdapterModeCount
、同じオブジェクト内の他のオブジェクトの直前に呼び出されるようにすると、同じ実行時チェックの失敗が表示されます。テストから、これはスタックを破壊する直前の呼び出しを除外するようです。これら 4 つの関数を呼び出す 4 つのメソッドはすべて異なる場所で発生し、GetAdapterModeCount
このファイル内のどこからでも呼び出すと問題が発生します。
これは私たちを珍しい部分に導きます。別のクラス ( CVoodoo3D9
) も同様のパラメーターを使用して同じ一連のIDirect3D9
メソッドを呼び出しますが、失敗しません (D3D9 の同等のラッパー クラスです)。オブジェクトは同時に使用されません (必要なレンダリング プロセスに応じてコードが選択されます) が、両方とも毎回同じ動作をします。他のクラスのコードは別のファイルに保持されているため、プリプロセッサの問題が疑われました (これについては後ほど説明します)。
その後、何の情報も得られなかったので、コードとパラメーターの呼び出し規約を調べました。繰り返しますが、何も明らかになりませんでした。/w4 /wX
コードベースは、ほとんどの関数で SAL を使用し、すべての PREfast ルールを有効にして (そして合格)、しばらくの間コンパイルされています。
特に、このクラス内で呼び出された場合、メソッドへの呼び出しがコードからのものであるか、オブジェクトを使用する別のプログラムからのものであるかにかかわらず、呼び出しは失敗します。呼び出された場所に関係なく失敗しますが、このファイル内でのみ失敗します。
完全な方法は次のとおりです。
UINT STDMETHODCALLTYPE CVoodoo3D8::GetAdapterModeCount(UINT Adapter)
{
UINT r = m_Object->GetAdapterModeCount(D3DADAPTER_DEFAULT, D3DFMT_X8R8G8B8);
gpVoodooLogger->LogMessage(LL_Debug, VOODOO_D3D_NAME, Format("CVoodoo3D8::GetAdapterModeCount(%d, D3DFMT_X8R8G8B8) == %d") << Adapter << r);
return r;
}
その時点までの実行が許可されている場合、呼び出しの直後GetAdapterModeCount
とメソッドが戻ると、チェックの失敗が発生します。
preprocess-to-file オプションで指定されたプリプロセッサの出力には、( からのd3d9.h
) メソッド宣言が次のように正しく含まれています。
virtual __declspec(nothrow) UINT __stdcall GetAdapterModeCount( UINT Adapter,D3DFORMAT Format) = 0;
私のメソッドの宣言は本質的に同じです:
virtual __declspec(nothrow) UINT __stdcall GetAdapterModeCount(UINT Adapter);
私の方法はほとんど拡張されず、次のようになります。
UINT __stdcall CVoodoo3D8::GetAdapterModeCount(UINT Adapter)
{
UINT r = m_Object->GetAdapterModeCount(D3DADAPTER_DEFAULT, D3DFMT_X8R8G8B8);
gpVoodooLogger->LogMessage(LL_Debug, L"Voodoo3D8", Format("CVoodoo3D8::GetAdapterModeCount(%d, D3DFMT_X8R8G8B8) == %d") << Adapter << r);
return r;
}
プリプロセッサの出力は、宣言と定義の両方のメソッドで正しいようです。
障害が発生するまでのアセンブリ リストは次のとおりです。
UINT STDMETHODCALLTYPE CVoodoo3D8::GetAdapterModeCount(UINT Adapter)
{
642385E0 push ebp
642385E1 mov ebp,esp
642385E3 sub esp,1Ch
642385E6 push ebx
642385E7 push esi
642385E8 push edi
642385E9 mov eax,0CCCCCCCCh
642385EE mov dword ptr [ebp-1Ch],eax
642385F1 mov dword ptr [ebp-18h],eax
642385F4 mov dword ptr [ebp-14h],eax
642385F7 mov dword ptr [ebp-10h],eax
642385FA mov dword ptr [ebp-0Ch],eax
642385FD mov dword ptr [ebp-8],eax
64238600 mov dword ptr [ebp-4],eax
UINT r = m_Object->GetAdapterModeCount(D3DADAPTER_DEFAULT, D3DFMT_X8R8G8B8);
64238603 mov esi,esp
64238605 push 16h
64238607 push 0
64238609 mov eax,dword ptr [this]
6423860C mov ecx,dword ptr [eax+8]
6423860F mov edx,dword ptr [this]
64238612 mov eax,dword ptr [edx+8]
64238615 mov ecx,dword ptr [ecx]
64238617 push eax
64238618 mov edx,dword ptr [ecx+18h]
6423861B call edx
6423861D cmp esi,esp
6423861F call _RTC_CheckEsp (6424B520h)
64238624 mov dword ptr [r],eax
明確にするために、エラーは6423861F
(への呼び出し_RTC_CheckEsp
) で発生し、呼び出しまたは準備がスタックを壊したことを示唆しています。同じ通話が他の場所でも機能するため、通話を壊すものではないという前提で作業しています。
私の素人の目には、唯一の異常な部分は のペアですmov register, dword ptr [register+8]
。これは 32 ビット システムであるため、+8
インクリメントしすぎている可能性があるかどうか、またはそうである場合にビルドにどのように組み込まれるかはわかりません。
私のメソッドが戻った直後に、ESP を破壊する呼び出しが原因で、プログラムが segfault します。値を呼び出さずGetAdapterModeCount
に単に値を返せば、プログラムは期待どおりに実行されます。
さらに、リリース ビルド (RTC なし) は、同様の時点でスタックを使用して segfaults を実行します。
d3d8.dll!CEnum::EnumAdapterModes() + 0x13b bytes
Voodoo_DX89.dll!ClassCreate() + 0x963 bytes
アドレスの意味はわかりませんが。私が知る限り、デバッグ ビルドで segfault が発生するのと同じ場所ではありません。これらは、メソッドが返された後のプログラム内にあります。これは、D3D8 からデータを取得するメソッドの 1 つの間にあるようです。編集:セグメンテーション違反は、現在デバッグ中の後の呼び出しで発生します。
この時点で、私は何がうまくいかないのか、どのようにうまくいかないのか完全に途方に暮れており、チェックするものはありません。