编译器将此指针移动到错误的地址

Compiler mov'es this pointer to wrong address

提问人:Gunther Struyf 提问时间:2/24/2017 最后编辑:Gunther Struyf 更新时间:2/24/2017 访问量:336

问:

我有一个简单的多态结构,有一个纯虚函数。 在使用它的大型项目中,唯一的缺陷是该项目使用几个全局静态数据进行集中参数加载和事件记录(无法轻松摆脱遗留代码)。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;

  ...
  }

问题是,当在调试中运行时(未使用版本进行测试),并且当我们最终进入函数内部时,指针已“损坏”:Foothis

  • 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 行的断点,就在进入函数体之前:使用监视窗口检查寄存器后面的对象(强制转换为)显示包含我需要的指针(指向对象),但由于某种原因,它被“ed 到看似随机的地址”。ecxDerived*ecxmov[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:尝试不使用全局静力学
C++ 程序集 msvc12

评论

0赞 edtheprogrammerguy 2/24/2017
想知道在调试器中是否无效,因为您没有引用当前对象中的任何内容。换句话说,也许编译器/调试器没有正确放入任何内容,因为它没有被使用?如果引用类变量,例如,在 ?我已经看到编译器在未被引用时会做这样的事情。thisthisFoo()this
0赞 Gunther Struyf 2/24/2017
@edtheprogrammerguy优化关闭,在我的完整代码中,我引用了成员变量,这就是我注意到它的方式。无论如何,堆(?正确?)都会损坏,变量不是我分配给它们的变量:例如在窗口中并且是垃圾而不是。刚刚向这个 MCVE 添加了一个成员变量并对其进行了测试:仍然没有,数据 = 垃圾。localsab2.214this
0赞 Margaret Bloom 2/24/2017
这一切都归结为神秘的.如果第一个代码段中缺少它,则:a) 因此 和 (the ) 将表示相对于帧指针的相同偏移量 b) 可以使用 ( 是破坏性的,因此堆栈指针将使用 a 从其保存的值恢复。这个技巧是一种常见的方法,当它翻转一个位时,用符号位(如FP)否定数字。通过使用,您要求对(优化未知)编译器进行不同的操作。and esp,0FFFFFFF8hebp = esp + 0f0hmov [esp+0f0h], ecxmov [ebp-08h], ecxmov [this], ecxadd/cmp/callandmovebpxor-1.0 * a
3赞 M.M 2/24/2017
您可能在程序中的其他位置有未定义的行为。也许是静态初始化顺序问题,因为你提到了全局静力学
1赞 Hans Passant 2/24/2017
VS2012 有第一个开始支持自动矢量化的 C++ 编译器版本。Margaret 指出的堆栈对齐是它的一个非常明显的副作用。缺少 FPU 指令也是如此,所有浮点数学都是通过 SSE 完成的。大变化,并非完全没有问题。确保您已安装所有更新,它们很重要。保留 VS 版本也很重要,很多 C++1x 都在旋转。大锤解决方案是 /arch 来禁用 SSE codegen。

答: 暂无答案