我有一些代码从 COM 对象 ( IDirect3D9
) 调用方法,但每次调用都会导致运行时检查失败 #0。失败是由于 ESP 没有在调用中正确保存,所以某种堆栈问题(因为 COM 方法都是__stdcall
)。不寻常的部分是方法签名和环境的简单性。
该代码仅在 32 位模式下构建,带有 MSVC 10 (VS 2010 SP1),使用 DirectX SDK(2010 年 6 月)标头和库。我已经重新安装了 SDK 以确保标头没有损坏,但运气不好。
我已经连接了 VS 的调试器和 WinDBG,以及在重新启动/更新驱动程序后多次运行代码。问题每次都会出现,并且是相同的。在 gflags 中启用堆验证(和大多数其他选项)似乎没有提供更多信息,也没有使用 Application Verifier 运行。两者都只是报告与弹出窗口相同的错误,或者不久之后导致的段错误。
如果没有调用(而是返回一个常量值),程序将按预期运行。我不知道这里可能出了什么问题。
有问题的函数是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 个监视器等)。前 3 次调用,由我的同一个对象(的单个实例CVoodoo3D8
)调用,都成功,没有堆栈警告。第四个没有。
如果我对调用重新排序,以便GetAdapterModeCount
在同一对象中的任何其他调用之前立即调用,则会出现相同的运行时检查失败。从测试来看,这似乎排除了前一个调用破坏堆栈的可能性。调用这 4 个函数的 4 个方法都发生在不同的位置,并且GetAdapterModeCount
从该文件中的任何位置调用都会导致问题。
这将我们带到了不寻常的部分。不同的类 ( CVoodoo3D9
) 也调用相同的方法序列IDirect3D9
,具有相似的参数,但不会失败(它是 D3D9 的等效包装类)。这些对象不是同时使用的(代码选择或其他取决于我需要的渲染过程),但每次都给出相同的行为。另一个类的代码保存在另一个文件中,这导致我怀疑预处理器问题(稍后会详细介绍)。
在那之后没有提供任何信息,我检查了我的代码和参数的调用约定。再一次,什么都没有曝光。代码库与 SAL 一起编译/w4 /wX
并且已经有一段时间了,大多数函数和所有 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
正确的方法声明(from ):
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,程序段错误。如果我不调用GetAdapterModeCount
并简单地返回一个值,程序将按预期执行。
此外,发布版本(无 RTC)在类似的点出现段错误,堆栈:
d3d8.dll!CEnum::EnumAdapterModes() + 0x13b bytes
Voodoo_DX89.dll!ClassCreate() + 0x963 bytes
虽然我不确定地址的含义。据我所知,它与调试版本中的段错误不同。这些在我的方法返回后在程序中,这似乎是在我从 D3D8 检索数据的方法之一期间。编辑:段错误发生在我目前正在调试的稍后调用中。
在这一点上,我完全不知道出了什么问题或怎么回事,并且没有东西可以检查。