Visual C ++ 2015中虚拟表的程序集输出混淆

时间:2016-04-30 12:02:34

标签: visual-c++ assembly vtable

我对Visual C ++ 2015(x86)的程序集输出感到困惑。

我想知道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.asm的汇编输出vtab.cpp,并且是Foo::GetValue的错位版本。)

.model flat

extern _printf : proc
extern ?GetValue@Foo@@UAEHXZ : proc

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

.code
_Check proc
    push ebp
    mov esp, ebp

    push offset ?GetValue@Foo@@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 ??_7Foo@@6B@
CONST   SEGMENT
??_7Foo@@6B@ DD FLAT:??_R4Foo@@6B@          ; Foo::`vftable'
    DD  FLAT:?GetValue@Foo@@UAEHXZ         ;;; GetValue at offset 4
CONST   ENDS

; Function compile flags: /Odtp
;   COMDAT ??0Foo@@QAE@XZ
_TEXT   SEGMENT
_this$ = -4                     ; size = 4
??0Foo@@QAE@XZ 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 ??_7Foo@@6B@    ;;; Init ptr to virtual table
    mov eax, DWORD PTR _this$[ebp]
    mov esp, ebp
    pop ebp
    ret 0
??0Foo@@QAE@XZ ENDP                 ; Foo::Foo

感谢您的回答!

更新

@Hans Passant这似乎是一个错误。我ml /c汇编输出vtab.asm(删除了一些符号)并将其与check.obj相关联以获得exe vtab2.exe。但vtab2.exe无法正常运行。然后我修改以下代码

;   COMDAT ??_7Foo@@6B@
CONST   SEGMENT
??_7Foo@@6B@ DD FLAT:??_R4Foo@@6B@          ; Foo::`vftable'
    DD  FLAT:?GetValue@Foo@@UAEHXZ
CONST   ENDS

;   COMDAT ??_7Foo@@6B@
CONST   SEGMENT
__NOT_USED_ DD  FLAT:??_R4Foo@@6B@          ; Foo::`vftable'
??_7Foo@@6B@    DD  FLAT:?GetValue@Foo@@UAEHXZ
CONST   ENDS
再次

ml以及link获取vtab3.exe。现在vtab3.exe正确运行并生成类似于vtab.exe的输出。

1 个答案:

答案 0 :(得分: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'" (??_7Foo@@6B@)
         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  ??_R4Foo@@6B@ (const Foo::`RTTI Complete Object Locator')
 00000004  DIR32                      00000000        1F  ?GetValue@Foo@@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     | ??_7Foo@@6B@ (const Foo::`vftable')

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

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

                .new_section .rdata, "dr2"
0000    00 00 00 00                                     .long   ??_R4Foo@@6B@
0004                          ??_7Foo@@6B@:
0004    00 00 00 00                                     .long   ?GetValue@Foo@@UAEHXZ