COM方法调用意外破坏了堆栈

ssu*_*ube 0 c++ com stack-smash

我有一些代码从COM对象(IDirect3D9)调用一个方法,但每次调用都会导致运行时检查失败#0.失败是由ESP在调用过程中未正确保留引起的,因此存在某种堆栈问题(因为COM方法都是如此__stdcall).不寻常的部分是方法签名和环境的简单性.

代码仅使用32位模式构建,使用DirectX SDK(2010年6月)头文件和库,使用MSVC 10(VS 2010 SP1).我重新安装了SDK以确保标头没有损坏,没有运气.

我已经连接了VS'调试器和WinDBG,以及重新启动/更新驱动程序后多次运行代码.问题每次都会发生,并且是相同的.在gflags中启用堆验证(以及大多数其他选项)似乎不提供任何更多信息,也不运行Application Verifier.两者都只报告与弹出窗口相同的错误,或者不久之后导致的段错误.

如果没有调用(返回一个常量值),程序将按预期运行.我对这里可能出现的问题没有想法.

有问题的函数是IDirect3D9::GetAdapterModeCount从D3D8到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);
Run Code Online (Sandbox Code Playgroud)

所有这些都导致检查失败.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
Run Code Online (Sandbox Code Playgroud)

该序列由调试跟踪代码记录,并且看起来是正确的并返回预期值(3个监视器等).由我自己的同一个对象(单个实例CVoodoo3D8)进行的前3个调用都成功,没有堆栈警告.第四个没有.

如果我重新排序调用,要GetAdapterModeCount在同一对象中的任何其他调用之前立即调用,则会出现相同的运行时检查失败.从测试来看,这似乎排除了一个紧接着之前的调用打破了堆栈; 调用这4个函数的4个方法都发生在不同的地方,并且GetAdapterModeCount从这个文件中的任何地方调用都会导致问题.

这带给我们不同寻常的部分.另一个class(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;
}
Run Code Online (Sandbox Code Playgroud)

GetAdapterModeCount如果允许执行到该点,则在调用之后立即发生检查失败,并且在我的方法返回时再次发生.

预处理器输出(由preprocess-to-file选项给出)具有d3d9.h正确的方法声明(from ):

virtual __declspec(nothrow) UINT __stdcall GetAdapterModeCount( UINT Adapter,D3DFORMAT Format) = 0;
Run Code Online (Sandbox Code Playgroud)

我方法的声明基本相同:

virtual __declspec(nothrow) UINT __stdcall GetAdapterModeCount(UINT Adapter);
Run Code Online (Sandbox Code Playgroud)

我的方法很难扩展,成为:

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;
}
Run Code Online (Sandbox Code Playgroud)

声明和定义中的两种方法的预处理器输出似乎都是正确的.

列出失败点的装配是:

    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  
Run Code Online (Sandbox Code Playgroud)

为了澄清,错误发生在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
Run Code Online (Sandbox Code Playgroud)

虽然我不确定地址的含义.据我所知,它不是调试构建中段错误的相同位置; 在我的方法返回后,这些都在程序中,这似乎是在我从D3D8中检索数据的一个方法中.编辑:段错误发生在稍后的调用中,我正在调试.

在这一点上,我完全不知道出了什么问题或者怎么样,而且我没有办法检查.

Ger*_*ald 5

我没有看到您正在做什么或使用生成的汇编代码有什么问题.

不过,我可以回答你的一个问题.

ecx,dword ptr [eax+8]
Run Code Online (Sandbox Code Playgroud)

这样做是将m_Object的地址移动到ecx寄存器中.+8是类中与m_Object的偏移量,这可能是正确的.

要看的东西.逐步完成汇编代码,直到达到这一点:

6423861B  call        edx  
6423861D  cmp         esi,esp
Run Code Online (Sandbox Code Playgroud)

此时检查esi和esp寄存器(在VS中将鼠标悬停在寄存器名称上).

在执行呼叫之前,ESI应该比ESP高12.通话结束后,他们应该是平等的.如果他们不是,发布他们是什么.

更新:

所以引起我注意的是你正在调用的4种方法,GetAdapterModeCountD3D8和D3D9之间只有不同的签名,而且签名不同于4个字节,这就是你的堆栈的不同之处.

m_Object是如何获得的?由于这是D3D8和D3D9之间的某种适配器,你的m_Object实际上是否可能是某个时候被转换为IDirect3D9的IDirect3D8对象?如果您以不同的方式获取D3D对象,那么这将解释错误,以及为什么它在另一个上下文中工作.