第四章——64位软件逆向技术-基本语法(下 虚函数)
虚函数
VC++实现虚函数的方式就是虚表,如果一个类至少要有一个虚函数,编译器会为这个类产生一个虚表。不同的类虚表就不同,相同的类虚表就会共享
识别构造和析构 如果在函数入口有 lea reg,off_xxxxxx, mov [reg],reg 初始化虚表,且返回值为this指针,我们就可以怀疑这是一个构造函数,同理我们发现同样的汇编我们也可以怀疑这是析构函数,可以根据顺序来区分。
有一种情况,我们写了一个虚析构函数,但是编译器生成了两个析构函数,其中一个是普通的析构函数,对象出作用于时候调用,另一个放到虚表中,在delete对象时候调用。对比两个析构函数,虚表中的析构多了一个delete this 操作。如果代码写成如下
Cvtual * Object = new Cvtual();
Object ->~Cvtual();
delete Object;
因为Object ->~Cvtual();属于多态调用所以会直接调用虚表中的析构,这时候对象就被释放了,如果在调用了delete Object,这时候我们发现,对象空间被重复释放,出现问题。为了解决这个问题,VC++编译器会在Object ->~Cvtual();时候传递参数0,代表对象不被释放,delete Object传递参数1 对象会被释放,这样就解决了上面的问题。gcc的编译器会采用在虚表中存放两个析构函数地址
总结一下虚表的特点:
- 如果一个类至少一个虚函数,这个类起码有一个虚表指针
- 不同的类虚表不同,相同类对象公用一个虚表
- 虚表指针存放对象首地址处
- 虚表地址在全局数据区
- 虚表的每个元素都指向一个类成员函数指针
- 虚表不一定以0结尾
- 虚表成员函数顺序是以类声明的顺序排列
- 虚表在构造函数中会被初始化
- 虚表在析构函数中会被赋值