0

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 つの間にあるようです。編集:セグメンテーション違反は、現在デバッグ中の後の呼び出しで発生します。

この時点で、私は何がうまくいかないのか、どのようにうまくいかないのか完全に途方に暮れており、チェックするものはありません。

4

1 に答える 1

5

あなたがしていることや、生成されたアセンブリ コードに問題はありません。

ただし、1 つの懸念にはお答えできます。

ecx,dword ptr [eax+8]

これが行っていることは、m_Object のアドレスを ecx レジスターに移動することです。+8 は、クラス内の m_Object へのオフセットであり、おそらく正しいでしょう。

見るべきもの。このポイントに到達するまで、アセンブリ コードをステップ実行します。

6423861B  call        edx  
6423861D  cmp         esi,esp

その時点で、esi および esp レジスターをチェックします (VS では、マウスをレジスター名の上に置くだけです)。

呼び出しが実行される前に、ESI は ESP よりも 12 高くなければなりません。呼び出しの後、それらは等しいはずです。そうでない場合は、それらが何であるかを投稿してください。

アップデート:

だから私の目を引くのは、あなたが呼び出していることを示している4つのメソッドのうち、GetAdapterModeCountD3D8とD3D9の間で異なる署名しかなく、その署名はスタックの違いである4バイトだけ異なるということです。

m_Object はどのように取得されますか? これは D3D8 と D3D9 の間のある種のアダプターであるため、m_Object が実際にある時点で IDirect3D9 としてキャストされている IDirect3D8 オブジェクトである可能性はありますか? D3Dオブジェクトを別の方法で取得している場合、それはエラーを説明し、別のコンテキストでそれが機能する理由を説明します。

于 2012-01-03T05:02:42.467 に答える