C++中的虚函数调用原理的反汇编实例分析(2)

C++中的虚函数调用原理的反汇编实例分析(2)

write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie

因为昨天的第一节中我其实感觉并没有太透彻的理解其原理,也对VC6的实现是否会继续抱有怀疑态度,所以今天特意用VS2005编译并分析了一下,只能说,从反汇编的角度来看都可以看出微软的进步实在是很大的,有钱嘛:)呵呵,也许Stanley B.Lippman的功劳不是白费的吧。速度上我是没有好好的去测试了,从逻辑上看,可读性都强了很多(呵呵,汇编代码要可读性干什么。。。。。)

为了可读性。。。。我个人就麻烦一点了,以后汇编代码也不是全黑出镜了。。。。我还是用gvim给大家上上色吧。。。。

示例程序:

#include

#include

#include

class CTestThisPointer

{

public:

CTestThisPointer(int ai):mi(ai) { }

virtual int Add(int ai)

{

mi += ai;

return mi;

}

private:

int mi;

};

int main()

{

CTestThisPointer loTest(10);

CTestThisPointer *lp = &loTest;

printf("%d/n",lp->Add(5));

printf("%s/n",typeid(lp).name());

return 0;

}

和在第一节中的源代码是一样的,只是这次我用VS2005最大速度优化release编译

反汇编:

主函数:

1 .text:00401010; int __cdecl main(int argc, const char **argv, const char *envp)
2 .text:00401010_main procnear ; CODE XREF: __tmainCRTStartup+10A^Yp
3 .text:00401010
4 .text:00401010var_8 = dwordptr-8
5 .text:00401010var_4 = dwordptr-4
6 .text:00401010argc= dwordptr4
7 .text:00401010argv= dwordptr8
8 .text:00401010envp= dwordptr0Ch
9 .text:00401010
10 .text:00401010 sub esp, 8;
此处为临时变量var_8,var_4预留空间,但是因为直接使用esp
11 .text:00401010 ;
所以我感觉后面的的堆栈有点混乱,比如后面的esp+10h,
12 .text:00401010 ;
这里有个有意思的优化在于5竟然是只有2个字节的使用,
13 .text:00401010 ;
所以esp-8以后,push esi(保存esi)然后又-4=0ch,然后
14 .text:00401010 ;
因为push 5(add参数)然后-2=010h,所以以后的临时变量
15 .text:00401010 ;
不得不先+10h以获取正确的地址,看到下面的汇编代码
16 .text:00401010 ;
应该就好理解了
17 .text:00401010 ;
18 .text:00401013 pushesi
19 .text:00401014 push5 ;
这是Add函数的参数了,这里是先push参数,然后再正确为
20 .text:00401014 ; CTestThisPointer
赋值(从构造函数优化而来),再调用
21 .text:00401014 ; Add
函数,中间断开了,但是从栈的走向还是可以看出
22 .text:00401014 ;
此处实际是Add函数的参数
23 .text:00401016 lea ecx, [esp+10h+var_8] ;
此处也是Add函数的参数,即this指针了
24 .text:0040101A mov [esp+10h+var_8], offsetconstCTestThisPointer::`vftable'
25 .text:00401022 mov [esp+10h+var_4], 0Ah;
构造函数没有调用,直接优化为赋值操作
26 .text:0040102A callds:constCTestThisPointer::`vftable' ;
27 .text:0040102A ;
一个虚函数的调用还是解释call this所在的地址就好了
28 .text:00401030 mov esi, ds:__imp__printf
29 .text:00401036 pusheax
30 .text:00401037 pushoffsetaD ; "%d/n"
31 .text:0040103C callesi; __imp__printf
32 .text:0040103E add esp, 8;
add esp,8为调整printf函数的栈平衡使用
33 .text:00401041 pushoffset__type_info_root_node;
此处不明,可能需要多个RTTI对象才能知道
34 .text:00401041 ;
因为就命名上来推测,可能是维护了一个RTTI对象的链表
35 .text:00401041 ;
而因为元素只有一个,所以没有办法肯定了
36 .text:00401046 mov ecx, offsetCTestThisPointer* `RTTITypeDescriptor' ;
此处相当于构建一个type_info的类,当时由于优化,没有
37 .text:00401046 ;
使用栈来创建局部变量和使用构造函数等,直接将type_info
38 .text:00401046 ;
的虚表赋值给ecx了,ecxthiscall调用约定中就是this
39 .text:00401046 ;
指针传递的参数啊,下面就调用了此对象的name成员函数
40 .text:0040104B callds:type_info::name(__type_info_node*)
41 .text:00401051 pusheax
42 .text:00401052 pushoffsetaS ; "%s/n"
43 .text:00401057 callesi; __imp__printf
44 .text:00401059 add esp, 8;
add esp,8也为调整printf函数的栈平衡使用
45 .text:0040105C xor eax, eax
46 .text:0040105E pop esi
47 .text:0040105F add esp, 8;
此处才退出函数,CTestThisPointer结束生命周期,用
48 .text:0040105F ; add esp,8
清空临时变量
49 .text:00401062 retn;
对了,才注意到main函数遵循的也是__cdecl调用约定。。。
50 .text:00401062_main endp;
其实自然啦,因为main函数的参数个数也是不定的

虚表:

1 .rdata:00402114 ddoffsetconstCTestThisPointer::`RTTICompleteObjectLocator' ;
2 .rdata:00402114 ;
这就是CTestThisPointerRTTI信息
3 .rdata:00402114 ;
正好在虚表前,由编译器静态分配,但是动态获取
4 .rdata:00402114 ; this
指针的指向还是vtbl的第一个虚函数,此例中
5 .rdata:00402114 ;
即为Add
6 .rdata:00402118constCTestThisPointer::`vftable' ddoffsetCTestThisPointer__Add
7 .rdata:00402118 ; DATA XREF: _main+A^Xo
8 .rdata:00402118 ; _main+1A^Xr

而虚表的前4个字节就是一个指针,指向了表示正确类型的type_info类的子类。

到这里,一切就慢慢符合《inside C++ Object》中描述的虚表了,呵呵,有了lippman,微软总算走上正轨了,慢慢的。。。充满技术性天才的可怜borland也走入了末途。

继续,例2:

#include

#include

#include

class CTestThisPointer

{

public:

CTestThisPointer(int ai):mi(ai) { }

virtual int Add(int ai)

{

mi += ai;

return mi;

}

// 纯废的析构函数

virtual ~CTestThisPointer() { mi = 0;}

private:

int mi;

};

// 无继承关系,仅为分析RTTI

class CTestBase

{

public:

CTestBase(int ai):mi(ai) { }

virtual int Add(int ai)

{

mi += ai;

mi += ai;

return mi;

}

// 纯废的析构函数

~CTestBase() { mi = 0;}

private:

int mi;

};

int main()

{

CTestThisPointer loTest(10);

CTestThisPointer *lp = &loTest;

printf("%d/n",lp->Add(5));

printf("%s/n",typeid(lp).name());

CTestBase loTestBase(20);

CTestBase* lpTestBase = &loTestBase;

printf("%s/n",typeid(CTestBase).name());

printf("%s/n",typeid(loTestBase).name());

printf("%s/n",typeid(lpTestBase).name());

return 0;

}

反汇编代码:

不重复注释重复内容

主程序:

.text:00401070; int __cdecl main(int argc, const char **argv, const char *envp)
.text:00401070_main procnear ; CODE XREF: __tmainCRTStartup+10A^Yp
.text:00401070
.text:00401070var_1C= dwordptr-1Ch
.text:00401070var_18= dwordptr-18h
.text:00401070var_14= dwordptr-14h
.text:00401070var_10= dwordptr-10h
.text:00401070var_C = dwordptr-0Ch
.text:00401070var_4 = dwordptr-4
.text:00401070argc= dwordptr4
.text:00401070argv= dwordptr8
.text:00401070envp= dwordptr0Ch
.text:00401070
.text:00401070 push0FFFFFFFFh
.text:00401072 pushoffsetloc_401A50;
因为程序足够大了。。。。MS自动的加入了SEH异常处理
.text:00401077 mov eax, largefs:0; SEH
TEB(thread Environment Block)总是在FS:0
.text:0040107D pusheax
.text:0040107E sub esp, 10h
.text:00401081 pushesi
.text:00401082 pushedi
.text:00401083 mov eax, __security_cookie
.text:00401088 xor eax, esp
.text:0040108A pusheax
.text:0040108B lea eax, [esp+28h+var_C]
.text:0040108F mov largefs:0, eax;
之前的代码都是在初始化TEB结构。。。。。。。
.text:00401095 mov [esp+28h+var_1C], offsetconstCTestThisPointer::`vftable'
.text:0040109D mov [esp+28h+var_18], 0Ah
.text:004010A5 push5
.text:004010A7 lea ecx, [esp+2Ch+var_1C]
.text:004010AB mov [esp+2Ch+var_4], 0
.text:004010B3 callds:constCTestThisPointer::`vftable'
.text:004010B9 mov esi, ds:__imp__printf
.text:004010BF pusheax
.text:004010C0 pushoffsetaD ; "%d/n"
.text:004010C5 callesi; __imp__printf
.text:004010C7 mov edi, ds:type_info::name(__type_info_node*)
.text:004010CD add esp, 8
.text:004010D0 pushoffset__type_info_root_node
.text:004010D5 mov ecx, offsetCTestThisPointer* `RTTITypeDescriptor'
.text:004010DA calledi; type_info::name(__type_info_node *)
.text:004010DC pusheax
.text:004010DD pushoffsetaS ; "%s/n"
.text:004010E2 callesi; __imp__printf
.text:004010E4 add esp, 8
.text:004010E7 mov [esp+28h+var_14], offsetconstCTestBase::`vftable'
.text:004010EF mov [esp+28h+var_10], 14h
.text:004010F7 pushoffset__type_info_root_node
.text:004010FC mov ecx, offsetCTestBase`RTTITypeDescriptor'
.text:00401101 mov byteptr[esp+2Ch+var_4], 1
.text:00401106 calledi; type_info::name(__type_info_node *)
.text:00401108 pusheax
.text:00401109 pushoffsetaS ; "%s/n"
.text:0040110E callesi; __imp__printf
.text:00401110 add esp, 8
.text:00401113 pushoffset__type_info_root_node
.text:00401118 mov ecx, offsetCTestBase`RTTITypeDescriptor'
.text:0040111D calledi; type_info::name(__type_info_node *)
.text:0040111F pusheax
.text:00401120 pushoffsetaS ; "%s/n"
.text:00401125 callesi; __imp__printf
.text:00401127 add esp, 8
.text:0040112A pushoffset__type_info_root_node
.text:0040112F mov ecx, offsetCTestBase* `RTTITypeDescriptor'
.text:00401134 calledi; type_info::name(__type_info_node *)
.text:00401136 pusheax
.text:00401137 pushoffsetaS ; "%s/n"
.text:0040113C callesi; __imp__printf
.text:0040113E add esp, 8
.text:00401141 xor eax, eax
.text:00401143 mov ecx, [esp+28h+var_C]
.text:00401147 mov largefs:0, ecx
.text:0040114E pop ecx
.text:0040114F pop edi
.text:00401150 pop esi
.text:00401151 add esp, 1Ch
.text:00401154 retn
.text:00401154_main endp
.text:00401154
.text:00401154; ---------------------------------------------------------------------------

贴出来仅仅是为了完整性,其实已经没有什么新意了,无非就是多一些,唯一有价值提起的就是SEH机制的加入,这又是另外一个话题了,在此不详述了,可以查阅相关书籍,推荐的有《windows核心编程》和《加密与解密》相关章节,虽然内容都并不是很多。

虚表。。。这才是符合主题的部分:

.rdata:00402124 ddoffsetconstCTestThisPointer::`RTTICompleteObjectLocator'
.rdata:00402128constCTestThisPointer::`vftable' ddoffsetCTestThisPointer__Add
.rdata:00402128 ; DATA XREF: CTestThisPointer___CTestThisPointer^Xo
.rdata:00402128 ; CTestThisPointer___scalar_deleting_destructor_+8^Xo ...
.rdata:0040212C ddoffsetCTestThisPointer___scalar_deleting_destructor_;
.rdata:0040212C ;
哈雷卤鸭。。。。。好吃吗?-_-!果然虚表果然扩张了,
.rdata:0040212C ;
顺序排列的函数分别是Add,destructor
.rdata:00402130 ddoffsetconstCTestBase::`RTTICompleteObjectLocator'
.rdata:00402134constCTestBase::`vftable' ddoffsetCTestBase__Add
.rdata:00402134 ; DATA XREF: CTestBase___CTestBase^Xo
.rdata:00402134 ; _main+77^Xo
.rdata:00402134 ;
.rdata:00402134 ;
此处也可以看出来,在虚表前的一个4字节结构,的确是
.rdata:00402134 ; RTTI
使用的。。。。只是this指针直接指向的是虚表

以下是__type_info_node的链表。。。。。。。。。,没有上色了。。。

.rdata:00402274 dd offset CTestBase::`RTTI Class Hierarchy Descriptor'

.rdata:00402278 const CTestThisPointer::`RTTI Complete Object Locator' db 0

.rdata:00402278 ; DATA XREF: .rdata:00402124o

.rdata:00402279 db 0

.rdata:0040227A db 0

.rdata:0040227B db 0

.rdata:0040227C db 0

.rdata:0040227D db 0

.rdata:0040227E db 0

.rdata:0040227F db 0

.rdata:00402280 db 0

.rdata:00402281 db 0

.rdata:00402282 db 0

.rdata:00402283 db 0

.rdata:00402284 dd offset CTestThisPointer `RTTI Type Descriptor'

.rdata:00402288 dd offset CTestThisPointer::`RTTI Class Hierarchy Descriptor'

.rdata:0040228C CTestThisPointer::`RTTI Class Hierarchy Descriptor' db 0

.rdata:0040228C ; DATA XREF: .rdata:00402288o

.rdata:0040228C ; .rdata:004022BCo

.rdata:0040228D db 0

.rdata:0040228E db 0

.rdata:0040228F db 0

.rdata:00402290 db 0

.rdata:00402291 db 0

.rdata:00402292 db 0

.rdata:00402293 db 0

.rdata:00402294 db 1

.rdata:00402295 db 0

.rdata:00402296 db 0

.rdata:00402297 db 0

.rdata:00402298 dd offset CTestThisPointer::`RTTI Base Class Array'

.rdata:0040229C CTestThisPointer::`RTTI Base Class Array' dd offset CTestThisPointer::`RTTI Base Class Descriptor at (0,-1,0,64)'

.rdata:0040229C ; DATA XREF: .rdata:00402298o

__type_info_node的链表。。。。可以在VS2005的头文件中找到依据:

struct __type_info_node {

void *memPtr;

__type_info_node* next;

};

extern __type_info_node __type_info_root_node;

呵呵,不就是这个吗?

最后,在VS2005的头文件中可以看到type_info类的声明和反汇编代码上看到的完全一致。。。。

class type_info {

public:

virtual ~type_info();

_CRTIMP_PURE bool __CLR_OR_THIS_CALL operator==(const type_info& rhs) const;

_CRTIMP_PURE bool __CLR_OR_THIS_CALL operator!=(const type_info& rhs) const;

_CRTIMP_PURE int __CLR_OR_THIS_CALL before(const type_info& rhs) const;

_CRTIMP_PURE const char* __CLR_OR_THIS_CALL name(__type_info_node* __ptype_info_node = &__type_info_root_node) const;

_CRTIMP_PURE const char* __CLR_OR_THIS_CALL raw_name() const;

private:

void *_m_data;

char _m_d_name[1];

__CLR_OR_THIS_CALL type_info(const type_info& rhs);

type_info& __CLR_OR_THIS_CALL operator=(const type_info& rhs);

_CRTIMP_PURE static const char *__CLRCALL_OR_CDECL _Name_base(const type_info *,__type_info_node* __ptype_info_node);

_CRTIMP_PURE static void __CLRCALL_OR_CDECL _Type_info_dtor(type_info *);

};

至此。。。。C++中虚函数调用的机制差不多可以知道了。。。。顺面还搞定了RTTI....加上继承不过就是换个虚表的问题了。虚表的结构又不会变。。。。。不深究了,生命是有限的。。。。。

write by 九天雁翎(JTianLing) -- blog.csdn.net/vagrxie

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值