概述:虚函数的相关问题整理及逆向分析,以及如何通过数组指定调用虚函数。
相关文章推荐:
环境说明:
基本说明
- 虚函数表属于类,类的所有对象共享这个类的虚函数表。
意思就是创建的所有类对象指向的虚函数表都是同一个地址。
- 继承状态下的虚函数表内存
- 没有重写时,继续使用父类的元素地址
- 重写后,使用当前类的元素地址
- 派生类函数中多出来的虚函数的访问(基类指针指向派生类成员)
以上说明在文中都有 Demo 。
代码演示
类的大小
这块内容比较简单,写个代码简单跑下即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class Base { int m_ch; }
class Base { int m_val; }
class Base { virtual void FunA(); virtual void FunB(); }
class Base { char m_ch; int m_val; }
class BaseA : public Base {
}
|
子类和父类数据对齐的情况
1 2 3 4 5 6 7 8 9
| class Base { char m_ch; };
class BaseA : public Base { int m_val; };
|
数据成员和虚函数对齐的情况
1 2 3 4 5 6 7 8 9 10 11 12 13
|
class Base { char m_ch; virtual void func(); virtual void FunB(); };
class BaseA : public Base { int m_val; };
|
子类有额外的虚函数时的大小
1 2 3 4 5 6 7 8 9 10 11 12 13
| class Base { char m_ch; virtual void funcA() = 0; virtual void funcB(); };
class BaseA : public Base { int m_val; virtual void funcC(); };
|
通过指针访问虚函数表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| #include <iostream>
using namespace std;
class Base { public: virtual void funcA() { cout << "Base::funcA" << endl; }; virtual void funcB(){ cout << "Base::funcB" << endl; }; virtual void funcC(){ cout << "Base::funcC" << endl; }; };
using U8 = long long; using FunCall = void(*)(); int main() { Base obj; U8* objAddr = (U8*)&obj; U8* objRef = (U8*)*objAddr;
((FunCall)objRef[0])(); ((FunCall)objRef[1])(); ((FunCall)objRef[2])();
return 0; }
|
输出如下所示:
1 2 3
| Base::funcA Base::funcB Base::funcC
|
关于 RPPI :
调试的时候看到的类指针是这样的,如下所示:
名称 |
值 |
类型 |
__vfptr |
0x00007ff74a8dbcf8 {VirtualFun.exe!void(* Base::`vftable’[4])()} {0x00007ff74a8d14ab {VirtualFun.exe!Base::funcA(void)}, …} |
void * * |
[0x00000000] |
0x00007ff74a8d14ab {VirtualFun.exe!Base::funcA(void)} |
void * |
[0x00000001] |
0x00007ff74a8d1212 {VirtualFun.exe!Base::funcB(void)} |
void * |
[0x00000002] |
0x00007ff74a8d11cc {VirtualFun.exe!Base::funcC(void)} |
void * |
可以看到类指针指向的位置不是 3个,而是 4个。这就涉及到了 RTTI 的知识了。 |
|
|
RTTI (Run Time Type Identification) 即通过运行时类型识别,程序能够使用基类的指针或引用来检查着这些指针或引用所指的对象的实际派生类型。 |
|
|
虚函数表的地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| #include <iostream> using namespace std; class Base { public: virtual void funcA() { cout << "Base::funcA" << endl; }; virtual void funcB(){ cout << "Base::funcB" << endl; }; virtual void funcC(){ cout << "Base::funcC" << endl; }; };
class BaseA : public Base { virtual void funcB() { cout << "BaseA::funcB" << endl; } };
using U8 = long long; using FunCall = void(*)(); int main() { Base obj; BaseA objA; U8* objAddr = (U8*)&obj; U8* objRef = (U8*)*objAddr; for(int i = 0; i < 3; i++) { printf("%p ", objRef[i]); } cout << endl; U8* objAddrA = (U8*)&objA; U8* objRefA = (U8*)*objAddrA; for(int i = 0; i < 3; i++) { printf("%p ", objRefA[i]); } return 0; }
|
输出如下所示:
1 2
| 0x5b2da1042388 0x5b2da10423c6 0x5b2da1042404 0x5b2da1042388 0x5b2da1042442 0x5b2da1042404
|
可以看到 funcA
和 funcC
的地址是一样的。FuncB
的地址发生了变化,这是因为在派生类中重写了 funcB
函数。
基类指针访问派生类成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| #include <iostream>
using namespace std; class Base {
public: virtual void funcA() { cout << "Base::funcA" << endl; };
virtual void funcB() { cout << "Base::funcB" << endl; };
virtual void funcC() { cout << "Base::funcC" << endl; };
};
class BaseA : public Base { public: virtual void funcB() { cout << "BaseA::funcB" << endl; }
virtual void funcD() { cout << "BaseA::funcD" << endl; } };
using U8 = long long; using FunCall = void(*)(); int main() { { Base* obj = new BaseA; U8* objAddr = (U8*)obj; U8* objRef = (U8*)*objAddr; ((FunCall)objRef[2])(); ((FunCall)objRef[3])();
}
return 0; }
|