🎨 颜色标记说明

  • 粉色:特别重要、主线必掌握、优先复盘的内容。
  • 蓝色:核心概念、术语、分类名。
  • 橙色:需要关注的边界、易错点、注意事项。
  • 绿色:解释类内容、帮助理解知识点。

返回:全书总览 · 上一章:数组与指针 · 下一章:构造与析构

反汇编中的结构体与类

源码层面,structclass 的默认访问权限不同;生成机器码后,publicprivateprotected 通常不再形成运行时边界。反汇编主要看到:

  • 一块对象内存。
  • 固定偏移的数据成员。
  • 接收对象地址的成员函数。
  • 可能存在的虚表、基类子对象和编译器辅助数据。

因此恢复类型时更适合先写“候选结构体”,再逐步确认它是否对应 C++ 类。

对象内存布局

没有继承和虚函数时,对象通常按成员声明顺序安排,但会受内存对齐影响:

1
2
3
4
5
struct Record {
char flag; // offset 0
int value; // 常见 offset 4
short code; // 常见 offset 8
};

中间空出的字节是填充,结构体末尾也可能补齐到对齐边界。

成员顺序、填充、空基类优化和对象布局都与编译器 ABI 有关。不要把某一次 VS 构建的 sizeof 当成跨平台标准。

从偏移恢复成员

若多个函数都基于同一对象地址访问固定偏移:

1
2
mov eax, dword ptr [rcx+8]
mov byte ptr [rcx+0Ch], 1

可以暂时恢复为:

1
2
3
4
5
struct Candidate {
unsigned char unknown_00[8];
int field_08;
bool field_0C;
};

不要急于给字段取业务名称。先按 field_08field_0C 记录,等字符串、API、比较逻辑或数据流提供语义证据后再重命名。

this 指针

非静态成员函数具有隐藏的对象参数 this

  • 32 位 MSVC 的传统 __thiscall 常用 ECX 传递 this
  • Windows x64 统一调用约定下,第一个整数/指针参数使用 RCX,成员函数的 this 也通常位于 RCX
  • 其他编译器、平台和显式调用约定可能不同。

识别 this 的证据:

  1. 调用点把某对象地址放入首参数位置。
  2. 函数内反复以该地址为基址访问固定偏移。
  3. 同一批函数共享相容的字段布局。

静态成员

静态数据成员属于类,但不属于某个对象实例。机器码中通常像全局变量一样通过固定模块地址访问,因此不会计入对象大小。

静态成员函数没有 this 参数,在机器码层面更接近普通函数。它是否属于某个类,往往依赖符号、调用上下文或业务关系判断。

空类与成员函数

普通成员函数代码不存放在每个对象中。只有数据成员和实现对象模型所需的隐藏字段占据对象空间。

标准要求不同完整对象具有可区分地址,所以空类对象通常至少占 1 Byte;编译器也可能对空基类应用优化。

对象作为参数

对象按值传递时,ABI 可能:

  • 直接用寄存器传递小型平凡对象。
  • 把对象副本放入栈参数区域。
  • 传入临时副本地址。
  • 调用拷贝或移动构造函数。

大型或非平凡对象的表现与编译器、架构和优化选项密切相关。应同时分析调用点与被调函数,不能仅凭某一次复制下结论。

对象作为返回值

小型平凡对象可能直接通过寄存器返回;较大或非平凡对象常通过隐藏的返回缓冲区完成:

1
2
3
调用者准备结果空间
把结果空间地址作为隐藏参数传入
被调用函数在该空间构造结果

现代编译器还可能应用返回值优化,省去看似必要的临时对象与拷贝。

类型恢复工作流

1
2
3
4
5
6
7
1. 选定候选对象地址
2. 汇总所有固定偏移访问
3. 标记每个偏移的访问宽度
4. 区分读、写和取地址操作
5. 根据调用关系聚类成员函数
6. 补充对齐与未知字段
7. 用动态调试验证对象实例

学习检查清单

检查项 状态
能从固定偏移访问恢复候选成员 待复盘
能解释对象内存对齐与填充 待复盘
能识别 x86 与 Windows x64 下常见的 this 传递 待复盘
能说明静态成员为什么不计入对象大小 待复盘
能识别隐藏返回缓冲区的可能性 待复盘