2016-04-30 39 views
3

我对Visual C++ 2015(x86)的汇编输出感到困惑。Visual C++ 2015中虚拟表的汇编输出的混淆

我想知道VC中的虚拟表格布局,所以我用虚拟函数编写下面的简单类。

#include <stdio.h> 

struct Foo 
{ 
    virtual int GetValue() 
    { 
     uintptr_t vtbl = *(uintptr_t *)this; 
     uintptr_t slot0 = ((uintptr_t *)vtbl)[0]; 
     uintptr_t slot1 = ((uintptr_t *)vtbl)[1]; 

     printf("vtbl = 0x%08X\n", vtbl); 
     printf(" [0] = 0x%08X\n", slot0); 
     printf(" [1] = 0x%08X\n", slot1); 

     return 0xA11BABA; 
    } 
}; 

extern "C" void Check(); 

int main() 
{ 
    Foo *pFoo = new Foo; 
    int x = pFoo->GetValue(); 
    printf("x = 0x%08X\n", x); 
    printf("\n"); 
    Check(); 
} 

,并检查布局,我实现了一个汇编函数(神奇的名字来自于汇编输出vtab.asmvtab.cpp,并且是Foo::GetValue错位的版本)。

.model flat 

extern _printf : proc 
extern [email protected]@@UAEHXZ : proc 

.const 
FUNC_ADDR db "Address of Foo::GetValue = 0x%08X", 10, 0 

.code 
_Check proc 
    push ebp 
    mov esp, ebp 

    push offset [email protected]@@UAEHXZ 
    push offset FUNC_ADDR 
    call _printf 
    add esp, 8 

    pop ebp 
    ret 
_Check endp 
end 

然后,我编译并运行。

ml /c check.asm 
cl /Fa vtab.cpp check.obj 
vtab 

并在我的电脑上得到以下输出。

vtbl = 0x00FF2174 
    [0] = 0x00FE1300 
    [1] = 0x6C627476 
x = 0x0A11BABA 

Address of Foo::GetValue = 0x00FE1300 

它清楚地示出了虚拟函数GetValue是在偏移的虚拟表的0。但vtab.cpp的汇编输出似乎暗示GetValue位于偏移4处(请参阅以下注释,以三个分号开头)。

; COMDAT [email protected]@[email protected] 
CONST SEGMENT 
[email protected]@[email protected] DD FLAT:[email protected]@[email protected]   ; Foo::`vftable' 
    DD FLAT:[email protected]@@UAEHXZ   ;;; GetValue at offset 4 
CONST ENDS 

; Function compile flags: /Odtp 
; COMDAT [email protected]@[email protected] 
_TEXT SEGMENT 
_this$ = -4      ; size = 4 
[email protected]@[email protected] PROC     ; Foo::Foo, COMDAT 
; _this$ = ecx 
    push ebp 
    mov ebp, esp 
    push ecx 
    mov DWORD PTR _this$[ebp], ecx 
    mov eax, DWORD PTR _this$[ebp] 
    mov DWORD PTR [eax], OFFSET [email protected]@[email protected] ;;; Init ptr to virtual table 
    mov eax, DWORD PTR _this$[ebp] 
    mov esp, ebp 
    pop ebp 
    ret 0 
[email protected]@[email protected] ENDP     ; Foo::Foo 

感谢您的回答!

更新

@Hans帕桑特这似乎是一个错误。 I ml /c程序集输出vtab.asm(带有几个符号删除)并将其与check.obj链接以获得exe vtab2.exe。但vtab2.exe将无法​​正常运行。然后我修改下面的代码

; COMDAT [email protected]@[email protected] 
CONST SEGMENT 
[email protected]@[email protected] DD FLAT:[email protected]@[email protected]   ; Foo::`vftable' 
    DD FLAT:[email protected]@@UAEHXZ 
CONST ENDS 

; COMDAT [email protected]@[email protected] 
CONST SEGMENT 
__NOT_USED_ DD FLAT:[email protected]@[email protected]   ; Foo::`vftable' 
[email protected]@[email protected] DD FLAT:[email protected]@@UAEHXZ 
CONST ENDS 

mllink再次得到vtab3.exe。现在vtab3.exe正确运行并产生类似于vtab.exe的输出。

+0

不,v表只有一个条目。 Just Foo :: GetValue()。 ?_ R4Foo @@ 6B @的条目不属于v表,它是RTTI对象定位器。用/ GR-编译,使其更加明显。和/ d1reportAllClassLayout让编译器给你更多关于布局的信息。 –

+0

我明白只有Foo :: GetValue()在v表中。我的困惑是为什么'mov DWORD PTR [eax],OFFSET ?? _7Foo @@ 6B @'不是'mov DWORD PTR [eax],OFFSET ?? _7Foo @@ 6B @ + 4'。无论如何,RTTI对象从标签'?? _Fu @@ 6B @'开始,然后是Foo :: GetValue()。 –

+0

“GetValue”的汇编代码是什么? – Jester

回答

2

我不认为微软会考虑这个错误。是的,程序集输出在vtable的第二个元素上应该有vtable符号,以便RTTI条目出现在表的偏移量-4处。但是该表也应该位于COMDAT部分,但是在汇编输出(; COMDAT)中只有一条注释表明了这一点。这是因为虽然PECOFF目标文件格式支持COMDAT部分,但汇编程序(MASM,调用为ml)不支持。编译器无法生成实际上与其创建的对象文件的内容相对应的程序集文件。

或者换句话说,装配输出不打算组装。这只是为了提供信息。即使应用了修复程序,程序集输出也不会生成编译器执行的同一个目标文件。如果你在一个更现实的项目中使用Foo来处理多个目标文件,那么在链接时会出现多个定义错误。如果你想看到编译器的实际输出,你需要查看目标文件。

例如,如果您使用dumpbin /all vtab.obj并经过它的输出,你会看到类似这样的:

SECTION HEADER #C 
    .rdata name 
... 
40301040 flags 
     Initialized Data 
     COMDAT; sym= "const Foo::`vftable'" ([email protected]@[email protected]) 
     4 byte align 
     Read Only 

RAW DATA #C 
    00000000: 00 00 00 00 00 00 00 00       ........ 

RELOCATIONS #C 
               Symbol Symbol 
Offset Type    Applied To   Index  Name 
-------- ---------------- ----------------- -------- ------ 
00000000 DIR32      00000000  34 [email protected]@[email protected] (const Foo::`RTTI Complete Object Locator') 
00000004 DIR32      00000000  1F [email protected]@@UAEHXZ (public: virtual int __thiscall Foo::GetValue(void)) 

... 

COFF SYMBOL TABLE 
... 
026 00000000 SECTC notype  Static  | .rdata 
    Section length 8, #relocs 2, #linenums 0, checksum  0, selection 6 (pick largest) 
028 00000004 SECTC notype  External  | [email protected]@[email protected] (const Foo::`vftable') 

这并不容易理解,但给出所有关于虚函数表的实际布局的信息。 vtable的符号[email protected]@[email protected] (const Foo::`vftable')位于SECTC的偏移00000004或节号0xC。部分#C长度为8个字节,并且具有RTTI定位符和Foo::GetValue的重定位,该部分应用于该部分的偏移量0000000000000004。所以你可以看到,在目标文件中,vtable符号实际上指向包含指向第一个虚拟方法的指针的条目。

Open Watcom有一个实用程序,它可以以类似汇编的方式向您显示目标文件的内容,但显然不是MASM使用的语法。正在运行wdis t279.obj显示:

   .new_section .rdata, "dr2" 
0000 00 00 00 00          .long [email protected]@[email protected] 
0004       [email protected]@[email protected]: 
0004 00 00 00 00          .long [email protected]@@UAEHXZ 
+0

我同意程序集的输出并不是要重新组合,但我相信它在某种程度上应该是正确的。输出可以帮助程序员理解编译器的内部工作原理。组装不正确只会导致混淆,这违背了我们的目的。我不熟悉目标文件,但我不认为该部分很重要。无论该部分是什么,相对偏移量都是相同的,问题仍然存在。 –

+0

这可能是一个简单的错误。编译器的内部结构是正确的,但是当输出时出错了,程序员要么在错误的地方输出v-table标签(应该指向第一个虚拟函数,但是指向RTTI对象),要么v-表指针的初始化是错误的(应该是'mov [ecx],offset VTableLabel + 4',但输出'mov [ecx],偏移VTableLabel')。 –

+0

@HuaidongXiong你认为编译器汇编输出的目的是什么都没有关系。重要的是微软认为其目的是什么。如果你愿意,你可以将它报告为一个错误,但不要屏住呼吸等待它们修复它。编译器生成的目标文件(.OBJ)没有问题,这是重要的输出。除了微不足道的情况外,汇编文件的内容总是不正确的,这是设计的。基本上汇编文件不能准确地表示编译器的预期输出。 –