C++反汇编-虚函数
学无止尽,积土成山,积水成渊-《C++反汇编与逆向分析技术揭秘》 读书笔记
在C++中,使用关键字virtual声明为虚函数。
- 虚函数地址表(虚表)
- 定义:当类中定义有虚函数时,编译器会把该类中所有虚函数的首地址保存在一张地址表中,即虚函数地址表。
- 虚表信息在编译后被链接到执行文件中,因此所获得的虚表地址是一个固定的地址。
- 虚表中虚函数的地址排列顺序依据虚函数在类中的声明顺序而定。
- 虚表指针
- 同时编译器还会在类的每个对象添加一个隐藏数据成员,称为虚表指针,保存着虚表的首地址,用于记录和查找虚函数。
- 虚表指针的初始化是通过编译器在构造函数中插入代码实现的。由于必须初始化虚表指针,编译器会提供默认的构造函数。
- 虚函数调用过程
- 虚表间接寻址访问:
使用对象的指针或引用调用虚函数。根据对象的首地址,取出相应的虚表指针,在虚表查找对应的虚函数的首地址,并调用执行。 - 直接调用访问:
使用对象调用虚函数,和调用普通成员函数一样。
虚函数的识别:
- 类中隐式定义一个数据成员
- 数据成员在首地址处,占4字节
- 构造函数初始化该数据成员为某个数组的首地址
- 地址属于数据区,相对固定的地址
- 数组的成员是函数指针
- 函数被调用方式是thiscall
构造函数与析构函数都会将虚表指针设置为当前对象所属类中的虚表地址。
- 构造函数中是完成虚表指针的初始化,此时虚表指针并没有指向虚表函数。
- 执行析构函数时,其对象的虚表指针已经指向某个虚表首地址。虚函数是在还原虚表指针,让其指向自身的虚表首地址,防止在析构函数中调用虚函数时取到非自身虚表。
示例C++源码
1 #include <iostream>
2 using namespace std;
3
4 class CVirtual {
5 public:
6 virtual int GetNumber() { return m_nNumber; }
7 virtual void SetNumber(int nNumber) { m_nNumber = nNumber;}
8 ~CVirtual(){ printf("~CVirtual!"); }
9 private:
10 int m_nNumber;
11
12
13 };
14
15 int main()
16 {
17 CVirtual myVirtual ,*pVirtual;
18 pVirtual = &myVirtual;
19 pVirtual->SetNumber(10);
20 printf("%d\r\n", pVirtual->GetNumber());
21 return 0;
2 using namespace std;
3
4 class CVirtual {
5 public:
6 virtual int GetNumber() { return m_nNumber; }
7 virtual void SetNumber(int nNumber) { m_nNumber = nNumber;}
8 ~CVirtual(){ printf("~CVirtual!"); }
9 private:
10 int m_nNumber;
11
12
13 };
14
15 int main()
16 {
17 CVirtual myVirtual ,*pVirtual;
18 pVirtual = &myVirtual;
19 pVirtual->SetNumber(10);
20 printf("%d\r\n", pVirtual->GetNumber());
21 return 0;
汇编代码
1.构造函数
mov [ebp-8], ecx ;=>保存this指针 mov eax, [ebp-8] ;=>eax获得this指针 mov dword ptr [eax], offset ??_7CVirtual@@6B@ ; const CVirtual::`vftable' ;=>虚表指针初始化
2.析构函数
mov [ebp-4], ecx ;=>保存this指针 mov eax, [ebp-4] ;=>eax获得this指针 mov dword ptr [eax], offset ??_7CVirtual@@6B@ ; const CVirtual::`vftable' =>虚表指针重置 push offset aCvirtual ; "~CVirtual!"
3.虚函数调用
pVirtual->SetNumber(10);
push 0Ah ;=>参数10压栈 mov eax, [ebp-18h] ;=>eax为this指针 mov edx, [eax] ;=>edx为虚表指针 mov ecx, [ebp-18h] ;=>ecx传递this指针 mov eax, [edx+4] ;=>虚函数SetNumber的地址=虚表+offset 4 call eax