【问题标题】:MSVC 2012 generates different vtable pointer offsets for different filesMSVC 2012 为不同的文件生成不同的 vtable 指针偏移量
【发布时间】:2016-08-06 14:06:57
【问题描述】:

假设我有 X64 版本配置 这是一个混淆代码sn-p...

// Hdr1.h
// Dozen of includes

class Cls1
{
public:
    Cls1();
    virtual void bar();
// ...
protected:
// about 7 fields where some of them are of complex template type.
bool isFlag1 : 1;
bool isFlag2 : 1;
};


// Hdr2
// Dozens of includes
class Cls2
{
public:
    // ...
    void foo();
};

我有单独的翻译单元来实现这些类。从 foo 说我尝试访问 Cls1::bar 的虚拟方法,但我遇到了崩溃(访问冲突)。

void Cls2::foo()
{
   //...
   Cls1 * pCls1 = // somehow I get this goddamn pointer

   pCls1->bar(); // Here I crash
}

从反汇编中我看到 Cls1::Cls1 将 vtable ptr 放置在偏移量 8 处,直到它的最开始。从 Cls2::foo 的反汇编中,我看到它从偏移量零获取指向 vtable 的指针。调试器也无法正确查看此 vtable。如果我在偏移量 8 处手动获取 vtable - 此表中的地址似乎是正确的。

问题是 - 为什么会发生这种情况,什么 pragma 会导致这种情况或其他任何情况?两个翻译单元的编译标志相同。

下面我添加一点反汇编: 这是我在代码中遇到的正常情况:

 Module1!CSomeOkClass::CreateObjInstance:
 sub     rsp,28h
 mov     edx,4                                  ; own inlined operator new
 lea     ecx,[rdx+34h]                          ; own inlined operator new
 call    OwnMemoryRoutines!OwnMalloc (someAddr) ; own inlined operator new
 xor     edx,edx
 test    rax,rax
 je      Module1!CSomeOkClass::CreateObjInstance+0x40 (someAddr)
 **lea     rcx,[Module1!CSomeOkClass::`vftable' (someAddr)] ; Inlined CSomeOkClass::CSomeOkClass < vtable ptr**
 mov     qword ptr [rax+8],rdx                  ; Inlined CSomeOkClass::CSomeOkClass
 mov     qword ptr [rax+10h],rdx                    ; Inlined CSomeOkClass::CSomeOkClass
 mov     qword ptr [rax+18h],rdx                    ; Inlined CSomeOkClass::CSomeOkClass
 mov     byte ptr [rax+20h],dl                  ; Inlined CSomeOkClass::CSomeOkClass
 mov     qword ptr [rax+28h],rdx                    ; Inlined CSomeOkClass::CSomeOkClass
 **mov     qword ptr [rax],rcx                  ; Inlined CSomeOkClass::CSomeOkClass < offset zero**

现在让我们看看我为 Cls1::Cls1 准备了什么:

 Module1!Cls1::Cls1:
 mov     qword ptr [rsp+8],rbx
 push    rdi
 sub     rsp,20h
 **lea     rax,[Module1!Cls1::`vftable' (someAddress)]  ; vtable address**
 mov     rbx,rdx
 mov     rdi,rcx
 **mov     qword ptr [rcx+8],rax                ; Places at offset 8**

我向你保证,Cls2 期望指向 vtable 的指针位于偏移量零处。

编译选项有: /nologo /WX /W3 /MD /c /Zc:wchar_t /Zc:forScope /Zm192 /bigobj /d2Zi+ /Zi /Oi /GS- /GF /Oy- /fp:fast /Gm- /Ox /Gy /Ob2 / GR-/Os

我注意到 Cls1::Cls1 大量使用从内部函数内联的 SSE 指令。

编译器版本: Microsoft (R) C/C++ 优化编译器版本 17.00.50727.1 for x64

请注意这段代码在不同的平台/编译器上都可以正常工作。

我设法弄清楚问题实际上在于我在 Cl1 定义的最后的这个位域。如果我将 isFlag1 + isFlag2 设为普通布尔值,则生成的 ctor 会将指向 vtable 的指针置于偏移量零处。这些标志在 ctor 的初始化列表中初始化。通过逐行注释掉类的代码,我将问题缩小到这个位域。为了调查这一点,我使用了 WinDbg、/P 编译器选项,使用提供的原始标志 + /FAs /Fa 手动编译了 cpp 单元。看来是编译器的bug。

【问题讨论】:

  • 这就是你的全部代码吗?你是在任何地方实现Cls1::bar 还是在pCls1 应该指向的派生类中覆盖它?
  • 那么......你是如何得到那个该死的指针的?
  • @txtechhelp 是的,我确实在派生自 Cls1 的不同类中覆盖它,并且指针实际上指向它。但这并不重要,因为基类及其后代指针都指向偏移量为零的 vtable。
  • @iwolf 我用自定义的 new 运算符调用构造它,比如 new ClsDescendant(...),然后我将它放入集合中,指针在某个时刻从集合中取出并传递到 Cls2。指针是正确的,因为我在 operator new 和 ClsDescendant::ClsDescendant 返回之后立即检查了指针。返回时,代码期望指向 vtable 的指针位于偏移量 0 处,但正如我之前提到的,基类和后代都将指针置于偏移量 8 处。
  • 正如@iwolf 所说,您需要发布最少量的实际重现问题的代码。使用您发布的确切代码,无法说明为什么您会在无效指针引用之外发生崩溃。 vtable 与您的崩溃无关,因为您再次拥有无效的指针引用; access violation 的错误通常意味着您正在尝试访问指向您无权访问的内存区域的指针(即无效的指针引用)

标签: c++ visual-studio-2012 visual-c++ vtable disassembly


【解决方案1】:

我设法弄清楚问题实际上在于我在 Cl1 定义的最后的这个位域。如果我将 isFlag1 + isFlag2 设为普通布尔值,则生成的 ctor 会将指向 vtable 的指针置于偏移量零处。这些标志在 ctor 的初始化列表中初始化。通过逐行注释掉类的代码,我将问题缩小到这个位域。为了调查这一点,我使用了 WinDbg、/P 编译器选项,使用提供的原始标志 + /FAs /Fa 手动编译了 cpp 单元。看来是编译器的bug。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-08-02
    • 1970-01-01
    • 2020-07-11
    • 2020-04-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多