【问题标题】:COM method call accidentally corrupts the stackCOM 方法调用意外损坏了堆栈
【发布时间】:2012-01-03 13:52:19
【问题描述】:

我有一段代码从 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 包装器(a graphics upgrade project for old games 的一部分)调用。更多一般信息,the full file is here

我尝试了以下所有形式的通话:

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 的等效包装类)。这些对象不是同时使用的(代码选择或其他取决于我需要的渲染过程),但每次都给出相同的行为。另一个类的代码保存在另一个文件中,这导致我怀疑预处理器存在问题(稍后会详细介绍)。

在那之后没有提供任何信息,我检查了我的代码和参数的调用约定。再一次,什么都没有曝光。代码库使用/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,程序段错误。如果我不调用GetAdapterModeCount 并简单地返回一个值,程序就会按预期执行。

另外,发布版本(无 RTC)在类似的点出现段错误,堆栈:

d3d8.dll!CEnum::EnumAdapterModes()  + 0x13b bytes   
Voodoo_DX89.dll!ClassCreate()  + 0x963 bytes

虽然我不确定地址的含义。据我所知,它与调试版本中的段错误不同。这些在我的方法返回后在程序中,这似乎是在我从 D3D8 检索数据的方法之一期间。 编辑: 段错误发生在稍后的调用中,我目前正在调试。

在这一点上,我完全不知道出了什么问题或怎么回事,并且没有东西可以检查。

【问题讨论】:

  • 如果你注释掉对gpVoodooLogger-&gt;LogMessage的调用会发生什么?
  • @MSN 没有变化,这个电话似乎工作得很好。 ESP 检查也会在调用之前发生并失败。注释掉另一个电话确实有帮助,但很重要。
  • 没有充分的理由不平衡的 ESP 值会使您的应用程序崩溃。 ESP 由功能后记恢复。我只是假设两个问题具有相同的原因,即内部程序状态损坏。
  • @HansPassant 这似乎很合理。对同一接口/对象的两次调用会出现问题,但其他调用不会出现问题。我只是希望这是我的一个愚蠢的错误。

标签: c++ com stack-smash


【解决方案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 个方法中,只有 GetAdapterModeCount 在 D3D8 和 D3D9 之间具有不同的签名,并且该签名相差 4 个字节,这就是你的堆栈。

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

【讨论】:

  • 嗯。在通话之前,我的 ESI 为 1635636,ESP 为 1635624(应该高 12 个)。但是通话后,它们分别是 1635636 和 1635632。这似乎表明它实际上以某种方式 is 调用,但相同的调用在其他代码中有效,所以我需要弄清楚这一点。
  • 不错的收获。原来是早些时候有两个void *s 需要转换为IDirect3D8*IDirect3D9*,但8 有一个错字,后来通过了。现在它在一个不同的、不那么混乱的地方崩溃了。 :D
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-07
  • 2016-03-11
  • 1970-01-01
  • 2010-12-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多