MSVC 2012 为不同的文件生成不同的 vtable 指针偏移量
MSVC 2012 generates different vtable pointer offsets for different files
假设我有 X64 版本配置
这是一个混淆的代码片段...
// 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();
};
我有单独的翻译单元来实现这些 classes。从 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 - 地址在此 table.
中似乎是正确的
问题是 - 为什么会发生这种情况,什么 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++ 针对 x64 优化编译器版本 17.00.50727.1
请注意此代码在不同 platforms/compilers.
上工作正常
我设法弄清楚问题实际上出在我在 Cl1 定义的最后这个位域上。如果我使 isFlag1 + isFlag2 成为普通布尔值,则生成的 ctor 会将指向 vtable 的指针置于偏移量零处。这些标志在 ctor 的初始化列表中初始化。通过逐行注释掉 class 的代码,我将问题缩小到这个位域。为了对此进行调查,我使用了 WinDbg,/P 编译器选项,使用提供的原始标志 + /FAs /Fa 手动编译了 cpp 单元。看来这是编译器的错误。
我设法弄清楚问题实际上出在我在 Cl1 定义的最后这个位域上。如果我使 isFlag1 + isFlag2 成为普通布尔值,则生成的 ctor 会将指向 vtable 的指针置于偏移量零处。这些标志在 ctor 的初始化列表中初始化。通过逐行注释掉 class 的代码,我将问题缩小到这个位域。为了对此进行调查,我使用了 WinDbg,/P 编译器选项,使用提供的原始标志 + /FAs /Fa 手动编译了 cpp 单元。看来这是编译器的错误。
假设我有 X64 版本配置 这是一个混淆的代码片段...
// 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();
};
我有单独的翻译单元来实现这些 classes。从 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 - 地址在此 table.
中似乎是正确的问题是 - 为什么会发生这种情况,什么 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++ 针对 x64 优化编译器版本 17.00.50727.1
请注意此代码在不同 platforms/compilers.
上工作正常我设法弄清楚问题实际上出在我在 Cl1 定义的最后这个位域上。如果我使 isFlag1 + isFlag2 成为普通布尔值,则生成的 ctor 会将指向 vtable 的指针置于偏移量零处。这些标志在 ctor 的初始化列表中初始化。通过逐行注释掉 class 的代码,我将问题缩小到这个位域。为了对此进行调查,我使用了 WinDbg,/P 编译器选项,使用提供的原始标志 + /FAs /Fa 手动编译了 cpp 单元。看来这是编译器的错误。
我设法弄清楚问题实际上出在我在 Cl1 定义的最后这个位域上。如果我使 isFlag1 + isFlag2 成为普通布尔值,则生成的 ctor 会将指向 vtable 的指针置于偏移量零处。这些标志在 ctor 的初始化列表中初始化。通过逐行注释掉 class 的代码,我将问题缩小到这个位域。为了对此进行调查,我使用了 WinDbg,/P 编译器选项,使用提供的原始标志 + /FAs /Fa 手动编译了 cpp 单元。看来这是编译器的错误。