【问题标题】:Inline assembly error only in release仅在发布时出现内联汇编错误
【发布时间】:2013-04-19 09:20:35
【问题描述】:

我有一个程序,它的功能只能通过内联汇编实现。

此函数用于调用我们可变参数(数字和类型)的其他函数。

我所有的程序都在调试模式下运行良好,但是当我在发布版中测试它时,我有这个错误:

SomeThing.exe: 0xC00000FD: Stack overflow (parameters: 0x00000001, 0x0A4B2FFC).

这是我的汇编函数:

__declspec( naked ) void Player_dummyFunction( dvrFunction* iFunc )
{
    __asm push ebp  
    __asm mov ebp,esp  
    __asm sub esp,0C0h  
    __asm push ebx  
    __asm push esi  
    __asm push edi  
    __asm lea edi,[ebp-0C0h]  
    __asm mov ecx,30h  
    __asm mov eax,0CCCCCCCCh  
    __asm rep stos dword ptr es:[edi]  

    /* Need the number of params. */
    __asm mov ecx, dword ptr [iFunc]            /* Use the calling convention of VC */
    __asm call dvrFunction::GetParamsNumber     /* this->m_ParamsData.size() */

    __asm mov edx, eax              /* Save the return value */

    __asm cmp edx, 0                /* Condition to know if the GL function has params (edx == 0) */
    __asm jz body                   /* Jump to the body label if the previous condition is true */

push_loop:
    /* Push the parameters in the reverse order on the stack */
    __asm mov ecx, dword ptr [iFunc]                /* Another use of the calling convention */
    __asm push edx
    __asm call dvrFunction::GetParamsAddress        /* this->m_ParamsData[i]->GetAddress() */

    __asm push [eax]                /* Push the dereferenced address (the value) on the stack */

    /* edx is automatically decremented by GetParamsAddress */
    __asm cmp edx, 0                /* Is edx == 0 ? */
    __asm jnz push_loop             /* If no, go back push_loop label */

body:
    __asm mov ecx, dword ptr [iFunc]                    /* Use the thiscall convention */
    __asm call dvrFunction::GetCName                    /* Call GetCName to have a const char* */

    __asm push eax                                      /* Push the name into the stack */
    __asm lea ecx, g_PlatBuiltin                        /* Use another convention for the structs */

    __asm call [ecx]g_PlatBuiltin.wglGetProcAddress     /* Call the real wglGetProcAddress to have the pointer to the GL function */
    __asm mov ecx, eax                                  /* Save the result -> TODO Is this operation needed ? */

    __asm call ecx                                      /* Call the original open GL function */

    __asm pop edi  
    __asm pop esi  
    __asm pop ebx  
    __asm add esp,0C0h  
    __asm cmp ebp,esp  
    //__asm call __RTC_CheckEsp (0125114Fh)  
    __asm mov esp,ebp  
    __asm pop ebp

    __asm ret                                           /* Call the ret asm command */
}

【问题讨论】:

  • 由于优化,程序可能不再有帧指针。堆栈布局的其他更改也可能发生。我建议你让编译器处理堆栈(使用参数和局部变量),并且只在汇编程序中做最小的需要的东西(如果需要的话!)。与 C++ 中的汇编函数相比,您是否实际测量过汇编函数的性能?优化和不优化?
  • 我没有看代码但是,你使用什么编译器以及优化参数是什么(在发布和调试模式下)?
  • @JoachimPileborg 谢谢,也许我可以让编译器做序言和结语,但其他的东西必须在汇编中完成......
  • @Sharpie 我正在开发 VS2012。调试中没有优化。 /O2/Ob2 正在发布中。
  • 我很确定帧指针不是问题。即使在没有帧指针的情况下编译代码,“想要”这样做的函数也可以通过压入帧指针寄存器(上面代码中的 EBP)来使用帧指针。我认为“必须在汇编程序中完成”的原因是您不知道参数的数量?

标签: c++ assembly stack-overflow inline-assembly


【解决方案1】:

这并不是一个真正的答案,但是 cmets 中的代码变得非常混乱,而汇编代码更糟糕,因为它比 C/C++ 更多地依赖换行符来格式化。

__asm lea edi,[ebp-0C0h]  
__asm mov ecx,30h  
__asm mov eax,0CCCCCCCCh  
__asm rep stos dword ptr es:[edi]  

这是“调试模式”代码,你真的不应该在发布模式下这样做。事实上,我很确定,您的功能根本不需要大多数 Epilog 和 prolog 代码——它只是“调试”的东西。

现在可能是一个答案: 我感觉崩溃是由GetParamsNumber 在非调试模式下返回的数字比在调试模式下更大。我对此完全不确定,这对我来说最有意义。或者GetParamsNumber 可能返回的是负面的东西?

您的代码也依赖于edx 未被GetParamsAddress 更改,这可能不是真的吗?特别是,它可能会根据优化模式而改变。

【讨论】:

  • 这是正确的答案,我刚刚看到我的 edx 在调用 GetParamsAddress 后只是假的。我通过在调用函数后添加 pop edx 解决了这个问题。我知道不需要 Epilog 和 prolog,但实际上我添加了它,因为我正在探索可以解决该问题的所有可能性 :-) 但是谢谢你的帮助。
  • 为了提高效率,您可以尝试使用“被调用者保存”的寄存器,例如 EDI 或 ESI。 [但是你确实需要在代码开始时推送该寄存器 - 但它确实将代码保存在循环中]
  • 感谢您的建议。我会这样做的。
【解决方案2】:

尝试在版本中添加 /Oy-

并检查其他优化选项:msdn

【讨论】:

  • 刚做了,但我有同样的问题。通过添加一些优化选项(/O1)进行调试,我也遇到了同样的问题。但在调试模式下,它指向 GetParamsAddress 之后的行:push [eax].
  • 您可以尝试逐行调试(使用 /O1)以查看此时 eax 中的内容.. 并尝试确定它是否是正确的值。 (调试程序集总是很有趣-_-)
【解决方案3】:

我的猜测是流水线会在 ret 指令之后运行到一些代码中。我看到您的堆栈非常细致地恢复,超出了正常需求。 (事实上​​,如果其中一些函数破坏了 ebp 怎么办?评论 __asm mov esp,ebp ?)我认为应该尝试各种组合:

  • 在最后的 ret 之后添加 16 个 nops。
  • 删除esp 恢复。 // __asm mov esp,ebp
  • 用降低堆栈指针包围代码,这样如果一些代码弹出太多,顶部不会被破坏:(特别是保存在这里)sub esp,1000,代码在这里,mov esp,ebp(假设你有堆栈恢复原位)。
  • 移动堆栈恢复内容__asm push ebp
    __asm mov ebp,esp
    ,稍后移动__asm mov esp,ebp
    __asm pop ebp
    /inside/您的寄存器恢复内容。如果您的代码中的堆栈确实损坏了,您的恢复代码不会像现在一样保存您的 esi、edi 和 ebx 寄存器。
  • 注释掉您的函数调用,看看是哪个函数破坏了它,然后逐步了解如何恢复之前的状态。

【讨论】:

  • 我很确定情况并非如此。 x86 是一种一致性架构,这意味着依赖于其他指令的指令将停止,直到其依赖关系完成。您可以按照在 x86 中产生正确结果的任何顺序编写任何您喜欢的内容,并且它会按照您的预期执行 [取决于处理器勘误表,但我 100% 确定这不是处理器勘误表]。越看越觉得这是“被其他人使用后的使用寄存器”。
猜你喜欢
  • 1970-01-01
  • 2010-10-23
  • 2020-05-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多