题外话:该贴是我的第一帖,也是对最近学习c++的一点点学习总结。有不足之处望各位大大指正。
c++多重继承时,创建对象后内存数据可能含有如下几类数据:
1、类的成员数据
2、指向虚表的首地址(4字节)
3、指向跳表的首地址(4字节)
//代码开始
//例子代码如下:VC6.0环境下编译
class A
{
public:
virtual funA()
{
}
int m_A;
};
class B
{
public:
virtual funB()
{
}
int m_B;
};
class C
{
public:
virtual funC()
{
}
int m_C;
};
class D:virtual public A,public B,public C
{
public:
virtual funD()
{
}
int m_D;
};
//部分虚继承 内存分布
int main(int argc, char* argv[])
{
D theD ;
int nSizeA = sizeof(A); // 8字节
int nSizeB = sizeof(B); // 8字节
int nSizeC = sizeof(C); // 8字节
int nSizeD = sizeof(D); // 32字节 为什么D类只多了一个int成员数据,却8字节呢?
// 答案在下面(就是因为D类多了一个指向跳表的指针)
return 0;//此处下断点
}
//代码结束
目标:分析对象 theD 的内存格式。
开始分析:
1、在main函数里面,return语句前面下断点。
2、Watch 窗口获取如下数据
+ &theD 0x0012ff60
+ (B*)&theD 0x0012ff60
+ (C*)&theD 0x0012ff68
+ (A*)&theD 0x0012ff78
+ &theD.m_A 0x0012ff7c
+ &theD.m_B 0x0012ff64
+ &theD.m_C 0x0012ff6c
+ &theD.m_D 0x0012ff74
3、对以上获取的数据,根据地址大小进行排序。(也称该步骤为找空白)
&theD 0x0012ff60
(B*)&theD 0x0012ff60
&theD.m_B 0x0012ff64
(C*)&theD 0x0012ff68
&theD.m_C 0x0012ff6c
????????? 0x0012ff70
&theD.m_D 0x0012ff74
(A*)&theD 0x0012ff78
&theD.m_A 0x0012ff7c
4、根据排序后发现0x0012ff70 地址处,空白(非我们的数据成员,但却在里面占用了空间),其实
就是 跳表 的首地址。
在内存中查看0x0012ff70地址的数据是 0x00422030 即跳表的首地址
5、以上地址的对应数据如下:
&theD 0x0012ff60 0x00422024
(B*)&theD 0x0012ff60 0x00422024 //B类首地址
&theD.m_B 0x0012ff64 CCCCCCCC //因为数据未初始化
(C*)&theD 0x0012ff68 0x00422020 //C类首地址
&theD.m_C 0x0012ff6c CCCCCCCC
????????? 0x0012ff70 0x00422030 //跳表首地址
&theD.m_D 0x0012ff74 CCCCCCCC
(A*)&theD 0x0012ff78 0x0042201c //A类首地址
&theD.m_A 0x0012ff7c CCCCCCCC
6、分析虚表指针指向的内容:
(一)0x00422024 虚表分析
00422024 1E 10 40 00 //即 0x0040101E
00422028 2D 10 40 00 //即 0x0040102D
打开反汇编窗口 ctrl+G
到该地址:0x0040101E
@ILT+25(?funB@B@@UAEHXZ):
0040101E jmp B::funB (004011b0)
到该地址:0x0040102D
@ILT+40(?funD@D@@UAEHXZ):
0040102D jmp D::funD (00401210)
(二)0x00422020 虚表分析
00422020 28 10 40 00 //即 0x00401028
打开反汇编窗口 ctrl+G 到该地址:0x00401028
@ILT+35(?funC@C@@UAEHXZ):
00401028 jmp C::funC (004011e0)
(三)0x0042201c 虚表分析
0042201C 05 10 40 00 //即 0x00401005
打开反汇编窗口 ctrl+G 到该地址:0x00401005
@ILT+0(?funA@A@@UAEHXZ):
00401005 jmp A::funA (00401180)
7、分析跳表指针指向的内容:
????????? 0x0012ff70 0x00422030 //跳表首地址
内存中查看 0x00422030 数据如下:
00422030 F0 FF FF FF //即 -0x10 负数 且发现0x0012ff70 - 0x10 = 0x0012ff60 ,B类首地址(B*)&theD
00422034 08 00 00 00 //即 0x8 正数 且发现0x0012ff70 + 0x8 = 0x0012ff78 ,A类首地址(A*)&theD
8、根据以上的数据分析可以很容易得出D类对象的内存布局:
vtableB = {B::funB, D::funD}
B::m_B;
vtableC = C::funC
C:m_C;
虚基类偏移表 = { vtableB = -0x10 , vtableA = 0x08}
D:m_D;
vtableA = A::funA
A:m_A;
内存如此分布的原因(个人理解的观点),为了使基类指针指向子类对象时
,其内存的结构是和基类对象一样的。这样的话,就可以在首地址加上便宜去直接访问数据成员。
[课程]FART 脱壳王!加量不加价!FART作者讲授!