3

我有一个简单的多态构造,只有一个纯虚函数Foo。它使用的大型项目中唯一的缺陷是该项目使用了几个全局静态来集中参数加载和事件记录(不能轻易摆脱遗留代码)。

项目信息:

  • 平台工具集:v110_xp
  • 静态库中的 MFC
  • MBCS 字符集
  • 调用约定:__cdecl
  • 禁用所有优化
  • 警告级别4,整个项目没有警告

代码:

class Base
  {
public:
  Base(){}
  virtual ~Base(void){}
  virtual void Foo(void) = 0;
  };

class Derived
  : public Base
  {
public:
  Derived(void) : Base(){}
  virtual void Foo(void) override
    {
    double a = sqrt(4.9);
    double b = -a;
  }

调用代码(没关系,到处都是相同的行为)

BOOL MainMFCApp::InitInstance()
  {
  Derived* d = new Derived();
  d->Foo();
  delete d;

  ...
  }

问题是,当在调试中运行时(未通过发布测试),当我们最终进入 functionFoo时,this指针被“损坏”:

  • this = 0xcccccccc
  • this.__vfptr = <unable to read memory>

当我深入研究输入函数的汇编代码时,我看到以下内容:

    13: 
    14: 
    15: void Derived::Foo(void)
    16:       {
015E3570 55                   push        ebp  
015E3571 8B EC                mov         ebp,esp  
015E3573 83 E4 F8             and         esp,0FFFFFFF8h  
015E3576 81 EC EC 00 00 00    sub         esp,0ECh  
015E357C 53                   push        ebx  
015E357D 56                   push        esi  
015E357E 57                   push        edi  
015E357F 51                   push        ecx  
015E3580 8D BD 14 FF FF FF    lea         edi,[ebp-0ECh]  
015E3586 B9 3B 00 00 00       mov         ecx,3Bh  
015E358B B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
015E3590 F3 AB                rep stos    dword ptr es:[edi]  
015E3592 59                   pop         ecx  
015E3593 89 8C 24 F0 00 00 00 mov         dword ptr [esp+0F0h],ecx  
    17:   double a = sqrt(4.9);
015E359A F2 0F 10 05 00 55 09 02 movsd       xmm0,mmword ptr ds:[2095500h]  
015E35A2 E8 72 D4 FD FF       call        __libm_sse2_sqrt_precise (015C0A19h)  
015E35A7 F2 0F 11 84 24 E0 00 00 00 movsd       mmword ptr [esp+0E0h],xmm0  
    18:   double b = -a;
015E35B0 F2 0F 10 84 24 E0 00 00 00 movsd       xmm0,mmword ptr [esp+0E0h]  
015E35B9 66 0F 57 05 10 55 09 02 xorpd       xmm0,xmmword ptr ds:[2095510h]  
015E35C1 F2 0F 11 84 24 D0 00 00 00 movsd       mmword ptr [esp+0D0h],xmm0  
    19:   return;
    20:       }
015E35CA 5F                   pop         edi  
015E35CB 5E                   pop         esi  
015E35CC 5B                   pop         ebx  
015E35CD 8B E5                mov         esp,ebp  
015E35CF 5D                   pop         ebp  
015E35D0 C3                   ret  
--- No source file -------------------------------------------------------------
015E35D1 CC                   int         3  
...
015E35EF CC                   int         3  

第 17 行的断点,就在进入函数体之前:使用监视窗口检查寄存器后面的对象ecx(带有强制转换为Derived*)显示ecx包含我需要的指针(指向对象),但由于某种原因,它被mov'ed to看似随机的地址[esp+0F0h]

现在真正有趣/令人惊叹的部分:当我改变时

double b = -a;

double b = -1.0 * a;

并再次编译,一切都神奇地工作。功能程序集现在已更改为:

    13: 
    14: 
    15: void Derived::Foo(void)
    16:       {
00863570 55                   push        ebp  
00863571 8B EC                mov         ebp,esp  
00863573 81 EC EC 00 00 00    sub         esp,0ECh  
00863579 53                   push        ebx  
0086357A 56                   push        esi  
0086357B 57                   push        edi  
0086357C 51                   push        ecx  
0086357D 8D BD 14 FF FF FF    lea         edi,[ebp-0ECh]  
00863583 B9 3B 00 00 00       mov         ecx,3Bh  
00863588 B8 CC CC CC CC       mov         eax,0CCCCCCCCh  
0086358D F3 AB                rep stos    dword ptr es:[edi]  
0086358F 59                   pop         ecx  
00863590 89 4D F8             mov         dword ptr [this],ecx  
    17:   double a = sqrt(4.9);
00863593 F2 0F 10 05 00 55 31 01 movsd       xmm0,mmword ptr ds:[1315500h]  
0086359B E8 79 D4 FD FF       call        __libm_sse2_sqrt_precise (0840A19h)  
008635A0 F2 0F 11 45 E8       movsd       mmword ptr [a],xmm0  
    18:   double b = -1.0 * a;
008635A5 F2 0F 10 05 10 55 31 01 movsd       xmm0,mmword ptr ds:[1315510h]  
008635AD F2 0F 59 45 E8       mulsd       xmm0,mmword ptr [a]  
008635B2 F2 0F 11 45 D8       movsd       mmword ptr [b],xmm0  
    19:   return;
    20:       }
008635B7 5F                   pop         edi  
008635B8 5E                   pop         esi  
008635B9 5B                   pop         ebx  
008635BA 81 C4 EC 00 00 00    add         esp,0ECh  
008635C0 3B EC                cmp         ebp,esp  
008635C2 E8 32 CD FC FF       call        __RTC_CheckEsp (08302F9h)  
008635C7 8B E5                mov         esp,ebp  
008635C9 5D                   pop         ebp  
008635CA C3                   ret  
--- No source file -------------------------------------------------------------
008635CB CC                   int         3  
...
008635EF CC                   int         3  

现在生成的代码很好地将寄存器中的指针移动ecxthis. 其他区别:

  • 不同的内存地址/偏移量
  • mulsd而不是xorpd否定变量
  • and esp,0FFFFFFF8h消失(??用于对齐堆栈指针esp??)
  • 更多清理(在函数体之后)??( add cmp call)

对于这两种情况,参数被推送到堆栈的装配部分是相同的:

    53:   d->Foo();
011A500B 8B 45 E0             mov         eax,dword ptr [d]  
011A500E 8B 10                mov         edx,dword ptr [eax]  
011A5010 8B F4                mov         esi,esp  
011A5012 8B 4D E0             mov         ecx,dword ptr [d]  
011A5015 8B 42 04             mov         eax,dword ptr [edx+4]  
011A5018 FF D0                call        eax  

当然,当我尝试使用最小、完整和可验证的示例来复制它时,一切都按预期进行。但在我的大项目中,它一直失败。我不确定哪些参数会影响编译,并且对汇编了解得不够多,甚至看不到那里发生了什么;因此,我在这里问是希望有人以前见过或认识到这种行为。

注意:当我删除sqrt呼叫时,它也可以再次工作。


更新:

  • 发布没有问题
  • VS2012 SP4 (v11.0.61030.00)
  • 引用成员变量时问题仍然存在(iso 没有成员引用)
  • TODO:尝试不使用全局静态变量
4

0 回答 0