2016-04-30 16 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.cppのアセンブリ出力vtab.asmから来ている、と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のアセンブリ出力は、オフセット4にあるGetValueを暗示しているようです(次のコメントは3セミコロンから始まります)。

; 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 

vtab3.exeを取得するために再び

; 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が実行され、vtab.exeのような出力が生成されます。

+0

いいえ、vテーブルには1つのエントリしかありません。 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 ?? _ 7F @ @ 6B @ + 4'でない理由です。とにかく、RTTIオブジェクトは '?? _ 7Foo @@ 6B @'というラベルで始まり、その後にFoo :: GetValue()が続きます。 –

+0

'GetValue'のアセンブリコードは何ですか? – Jester

答えて

2

Microsoftはこれをバグとはみなさないと思います。はい。アセンブリの出力にはvtableの2番目の要素に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の実際のレイアウトに関するすべての情報が与えられています。 vtableのシンボル、[email protected]@[email protected] (const Foo::`vftable')は、00000004SECTCまたはセクション番号0xCにあります。セクション#Cは8バイト長であり、セクションのオフセット00000000および00000004に適用されるRTTIロケータおよびFoo::GetValueの再配置を持ちます。したがって、オブジェクトファイルで、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

これは簡単なバグかもしれません。コンパイラの内部構造は正しいですが、出力時に何かが間違っていると、プログラマは間違った場所(最初の仮想関数を指すはずですがRTTIオブジェクトを誤ってポイントする必要があります)またはv-テーブルポインタの初期化が間違っています( 'mov [ecx]、offset VTableLabel + 4')。代わりに' mov [ecx]、offset VTableLabel'を出力してください。 –

+0

@HuaidongXiongコンパイラのアセンブリ出力の目的が何であるかは関係ありません。重要なのは、マイクロソフトがその目的を考えるものです。もしあなたが望むなら、これをバグとして報告することができますが、あなたの息を止めてそれを修正するのを待ってはいけません。コンパイラが生成するオブジェクトファイル(.OBJ)に何も問題はありません。これは重要な出力です。単純な場合を除いて、アセンブリファイルの内容は常に正しくありません。これは設計によるものです。基本的に、アセンブリファイルはコンパイラの意図した出力を正確に表すことができません。 –