使用Windbg分析C++虚函数表原理

前言

要想学好C++,就得熟悉C++对象模型。如果能利用好调试工具,比如windbg、GDB等,就能够更快速的掌握C++对象模型原理。本系列文章是通过windbg来深入分析C++对象原理,以便更好的理解C++相关知识点。
说明:为了实现方便,所有源码会采用了C++11的语法,编译环境为VS2017,默认编译选项为Win32。

检测虚函数表指针大小

根据C++对象的知识可知,存在虚函数的类对象实例会多出1个一个指向虚函数表的指针,下面就先用代码来测试一下,其中类CA不存在虚函数,类CB存在虚函数。

#include <iostream>
using namespace std;

class CA {
public:
    void Fun1() {}
    void Fun2() {}
    void Fun3() {}
    void Fun4() {}
    int a = 1;
};

class CB {
public:
    virtual void Fun1() {}
    virtual void Fun2() {}
    void Fun3() {}
    void Fun4() {}
    int b = 2;
};

int main() {
    CA* pa = new CA();
    pa->Fun1();
    pa->Fun2();
    pa->Fun3();
    pa->Fun4();

    CB* pb = new CB();
    pb->Fun1();
    pb->Fun2();
    pb->Fun3();
    pb->Fun4();

    cout << "Size of Pointer : " << sizeof(void *) << endl;
    cout << "Size of CA : " << sizeof(*pa) << endl;
    cout << "Size of CB : " << sizeof(*pb) << endl;

    delete pa;
    pa = nullptr;
    delete pb;
    pb = nullptr;
    return 0;
}

在win32下的运行结果如下:

Size of Pointer : 4
Size of CA : 4
Size of CB : 8

从运行结果来看,CB的实例比CA的实例多出了4个字节,即一个指针的大小。通过VS的基本查看变量功能,我们无法看到虚函数表指针的值,也就看不到虚函数表的内容,需要用更专业的软件来看。下面,我们就用windbg来分析下虚函数表的具体内容。

使用Windbg分析虚函数表

在使用windbg之前,先编译上面的代码,生成了exe文件。本文的示例代码生成的是sample1.exe,后续用到的模块名称都是sample1。

1. 使用windbg打开exe

打开windbg,可Ctrl+E找到sample1.exe所在位置,把exe打开。打开之后,进程会暂停在进入main函数之前。这时候,我们可以设置断点,之后再运行。

2. 设置断点

设置断点需要知道要断点的代码的运行地址,这个地址可以通过x命令来模糊查询,比如我这次操作就是先查找main函数的地址,然后在main的入口处打断点,运行后,windbg会自动显示代码,就可以在左侧代码中通过界面来设置断点了。我把断点打在了pb创建之后的下一行代码上。
查找main函数的命令如下。其中类似0:000>开头的,是我输入的命令,其他的是输入命令后的运行结果。

0:000> X sample1!main*
*** WARNING: Unable to verify checksum for sample1.exe
00a25fd0          sample1!main (void)
00a22bf0          sample1!mainCRTStartup (void)
0:000> bp 00a25fd0
0:000> g
Breakpoint 0 hit
eax=10104750 ebx=003d3000 ecx=00000001 edx=00a2c73c esi=00a212b7 edi=00a212b7
eip=00a25fd0 esp=0053fec8 ebp=0053fed8 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
sample1!main:
00a25fd0 55              push    ebp

在pb创建之后设置好断点之后,再次输入g命令,程序会继续暂停。

3. 对比类实例的值

在当前的程序中,已经创建了pa和pb,可以通过dt命令来看下两个变量的详细信息,从下面的结果可以看出,pb比pa多了一个虚函数表指针。

0:000> dt pa
Local var @ 0x53fec0 Type CA*
0x0006a6a8 
   +0x000 a                : 0n1
0:000> dt pb
Local var @ 0x53febc Type CB*
0x00065c70 
   +0x000 __VFN_table : 0x00a29b7c 
   +0x004 b                : 0n2

4. 查看虚函数表内容

从上面的结果看出,pb的虚函数表的指针是0x00a29b7c,可通过dt查看pb的虚函数表内容。从dt的输出结果看,没有看到完整的信息。因此,可以通过dd命令查看虚函数表的内存,在用dt命令查看对应内存的信息。通过结果看出,虚函数表里的两个函数,就是pb的两个虚函数Fun1和Fun2。

0:000> dt 0x00a29b7c 
CB::`vftable'
[3] 0x00a21307 
 void  sample1!ILT+770(?Fun1CBUAEXXZ)+0( void )
0:000> dd 0x00a29b7c 
00a29b7c  00a21307 00a2130c 00000000 00000000
00a29b8c  00000000 00000000 00a2a8b8 00a210dc
00a29b9c  00000000 00a2a910 00a21159 00a21294
00a29bac  00000000 6e6b6e55 206e776f 65637865
00a29bbc  6f697470 0000006e 00000000 00a2a968
00a29bcc  00a21131 00a21294 00000000 20646162
00a29bdc  6f6c6c61 69746163 00006e6f 00000000
00a29bec  00a2a9c4 00a21203 00a21294 00000000
0:000> dt 00a21307 
ILT+770(?Fun1CBUAEXXZ)
Symbol  not found.
0:000> dt 00a2130c 
ILT+775(?Fun2CBUAEXXZ)
Symbol  not found.

5. 进一步分析虚函数表

x命令可以查询程序中的符号的信息,下面我使用x查询了CA和CB相关的符号信息。其中,CB的虚函数表指针,跟pb中的虚函数表地址是一样的,说明所有类实例共用一个虚函数表,这个也可以通过再创建一个类对象来验证。

0:000> x sample1!CA::*
00a22260          sample1!CA::Fun1 (void)
00a21ca0          sample1!CA::Fun2 (void)
00a21850          sample1!CA::Fun3 (void)
00a21830          sample1!CA::Fun4 (void)
00a21e10          sample1!CA::CA (void)
0:000> x sample1!CB::*
00a29b7c          sample1!CB::`vftable' = <function> *[3]
00a21ef0          sample1!CB::Fun3 (void)
00a21cf0          sample1!CB::Fun2 (void)
00a21800          sample1!CB::Fun1 (void)
00a21cc0          sample1!CB::Fun4 (void)
00a21e40          sample1!CB::CB (void)
00a2a830          sample1!CB::`RTTI Base Class Array' = <no type information>
00a2a81c          sample1!CB::`RTTI Class Hierarchy Descriptor' = <no type information>
00a2a838          sample1!CB::`RTTI Base Class Descriptor at (0,-1,0,64)' = <no type information>
00a2a804          sample1!CB::`RTTI Complete Object Locator' = <no type information>

通过上面的结果看出,CB::Fun1的地址是00a21800,跟pb的虚函数表里的00a21307地址不相同的。然而,我通过uf命令,发现这反汇编出来的函数是一样的。这又是怎么回事呢?先来看看反汇编的结果。

0:000> uf 0xa21307
sample1!CB::Fun1 [c:\bingo\github\samples\cplusplus\inside-object\sample2\main.cpp @ 15]:
   15 00a21800 55              push    ebp
   15 00a21801 8bec            mov     ebp,esp
   15 00a21803 51              push    ecx
   15 00a21804 c745fccccccccc  mov     dword ptr [ebp-4],0CCCCCCCCh
   15 00a2180b 894dfc          mov     dword ptr [ebp-4],ecx
   15 00a2180e 8be5            mov     esp,ebp
   15 00a21810 5d              pop     ebp
   15 00a21811 c3              ret

sample1!ILT+770(?Fun1CBUAEXXZ):
00a21307 e9f4040000      jmp     sample1!CB::Fun1 (00a21800)  Branch

从反汇编的结果来看,00a21800是CB::Fun1的入口地址,而0xa21307指向的值是跳转到CB::Fun1函数的命令,也就是说,虚函数表保存的是一个跳转命令。

小结

通过windbg工具辅助,把很多VS不能显示信息展示出来,能够了解更多C++虚函数表的实现细节,对掌握C++的相关原理很有帮助。后续,我还会用windbg分析更多的实例。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容

  • 前言 windbg是windows平台下C++开发调试的工具,常用于分析软件崩溃,也是学习C++开发的利器。win...
    程序员bingo阅读 540评论 0 0
  • 原创:神秘编程神秘编程今天 问题抛出:为什么要使用多态?如果子类定义了与父类中原型相同的函数会发生什么? 多态:同...
    编程小兔崽阅读 549评论 0 0
  • layout: wikititle: Windbg调试笔记categories: Debugdescription...
    超哥__阅读 19,418评论 0 17
  • 指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; ...
    朱森阅读 3,440评论 3 44
  • 你死我活的剧情(四) 【都市】看见你的世界 (34) 周一开盘,威廉集团股市大跌,资产严重缩水,股东们纷纷抱怨,而...
    十一里shiyili阅读 99评论 0 0