2016-04-30 46 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)沒有問題,這是重要的輸出。除了微不足道的情況外,彙編文件的內容總是不正確的,這是設計的。基本上彙編文件不能準確地表示編譯器的預期輸出。 –