1、课程目标
- 掌握构造函数的汇编特征
- 理解析构函数的调用时机
- 学会识别各种构造方式
- 掌握RAII模式的识别
2、名词解释
| 术语 |
英文 |
说明 |
| 构造函数 |
Constructor |
对象创建时自动调用 |
| 析构函数 |
Destructor |
对象销毁时自动调用 |
| 拷贝构造 |
Copy Constructor |
用已有对象初始化新对象 |
| 移动构造 |
Move Constructor |
C++11移动语义 |
| 栈展开 |
Stack Unwinding |
异常时析构栈上对象 |
3、使用工具
| 工具 |
用途 |
| IDA Pro |
识别构造/析构函数 |
| x64dbg |
观察构造过程 |
4、技术原理
4.1、构造函数特征
| 特征 |
说明 |
| 返回this |
通常返回对象指针 |
| 初始化vptr |
有虚函数的类会写入vtable地址 |
| 初始化成员 |
设置成员变量的初始值 |
| 调用基类构造 |
继承时先构造基类 |
4.2、析构函数特征
| 特征 |
说明 |
| 析构顺序 |
与构造相反 |
| 释放资源 |
调用delete/free |
| 清理vptr |
在调用基类析构前 |
5、代码实现
5.1、示例1:基本构造函数
class Simple {
public:
int a, b;
Simple() : a(0), b(0) { }
Simple(int x, int y) : a(x), b(y) { }
};
// 默认构造函数反汇编:
// Simple::Simple()
// push ebp
// mov ebp, esp
// mov eax, ecx ; this
// mov dword ptr [eax], 0 ; this->a = 0
// mov dword ptr [eax+4], 0 ; this->b = 0
// ; 返回this指针(在EAX中)
// pop ebp
// ret
// 带参构造函数:
// Simple::Simple(int, int)
// mov eax, ecx
// mov edx, [esp+4] ; x
// mov [eax], edx ; this->a = x
// mov edx, [esp+8] ; y
// mov [eax+4], edx ; this->b = y
// ret 8
5.2、示例2:有虚函数的构造
class Base {
public:
int value;
virtual void Func() { }
Base() : value(0) { }
};
// Base::Base() 反汇编:
// mov eax, ecx
// mov dword ptr [eax], offset vtable_Base ; 写入vptr
// mov dword ptr [eax+4], 0 ; this->value = 0
// ret
// 特征: 第一个操作是写入vtable地址
5.3、示例3:继承的构造顺序
class Parent {
public:
int parentVal;
Parent() : parentVal(100) { }
};
class Child : public Parent {
public:
int childVal;
Child() : childVal(200) { }
};
// Child::Child() 反汇编:
// push ebp
// mov ebp, esp
// push ecx ; 保存this
// mov ecx, [ebp-4] ; this
// call Parent::Parent ; 先构造基类
// mov ecx, [ebp-4]
// mov dword ptr [ecx+4], 200 ; this->childVal = 200
// mov eax, ecx ; 返回this
// mov esp, ebp
// pop ebp
// ret
// 特征: 在初始化自己的成员前调用基类构造
5.4、示例4:析构函数
class Resource {
private:
void* data;
public:
Resource() {
data = malloc(1024);
}
~Resource() {
free(data);
}
};
// Resource::~Resource() 反汇编:
// push ebp
// mov ebp, esp
// mov eax, [ecx+4] ; this->data
// push eax
// call free
// add esp, 4
// pop ebp
// ret
// 析构函数特征:
// 1. 释放资源(free, delete, CloseHandle等)
// 2. 如果有虚函数,可能重写vptr
// 3. 最后调用基类析构
5.5、示例5:堆对象创建
Resource* CreateResource() {
return new Resource();
}
void DeleteResource(Resource* p) {
delete p;
}
// CreateResource 反汇编:
// push 8 ; sizeof(Resource)
// call operator new ; 分配内存
// add esp, 4
// test eax, eax
// jz failed
// mov ecx, eax ; this = 分配的内存
// call Resource::Resource ; 调用构造函数
// ret
// DeleteResource 反汇编:
// mov eax, [ebp+8] ; p
// test eax, eax
// jz skip_delete
// mov ecx, eax
// call Resource::~Resource ; 调用析构函数
// push eax
// call operator delete ; 释放内存
// add esp, 4
// skip_delete:
// ret
5.6、示例6:识别构造函数的技巧
/*
识别构造函数的特征:
1. 调用时机:
- new之后立即调用
- 函数开头对局部变量的初始化
2. 函数特征:
- ECX = this
- 写入vtable地址(有虚函数时)
- 初始化成员变量
- 可能调用基类构造函数
- EAX返回this指针
3. 在IDA中:
- 找到vtable的交叉引用
- 查找 operator new 之后的调用
- 查找写入地址指针的操作
4. 识别析构函数:
- delete之前调用
- 包含资源释放操作
- 可能调用基类析构
- 函数结束前对局部对象的清理
*/
5.7、示例7:栈对象的生命周期
void Function() {
Resource res; // 构造
// ...使用res...
} // 自动析构
// 反汇编:
// push ebp
// mov ebp, esp
// sub esp, 8 ; 为Resource分配栈空间
// lea ecx, [ebp-8] ; this = &res
// call Resource::Resource
//
// ... 函数体 ...
//
// lea ecx, [ebp-8] ; this = &res
// call Resource::~Resource ; 返回前析构
// mov esp, ebp
// pop ebp
// ret
6、课后作业
- 基础练习:编写有构造/析构的类,观察汇编
- 继承分析:分析继承关系中构造/析构顺序
- 资源管理:识别RAII模式的析构调用
- 实战还原:从反汇编识别构造/析构函数