🎨 颜色标记说明

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

返回:全书总览 · 上一章:虚函数 · 下一章:异常处理

基类子对象

派生类对象通常内含一个或多个完整的基类子对象。访问控制只影响源码编译检查,不会把基类的私有成员从派生对象内存中删除。

单继承的概念布局:

1
2
3
4
5
Derived object
+0x00 Base subobject
Base fields
Base vfptr(如有)
+0x20 Derived fields

Derived* 转为主基类 Base* 时,地址可能不变;转到位于非零偏移的其他基类时,需要调整指针。

构造与析构顺序

1
2
构造:基类 -> 派生类成员 -> 派生类函数体
析构:派生类函数体 -> 派生类成员 -> 基类

多重继承时,普通基类通常按声明顺序构造,逆序析构。虚基类由最派生类负责初始化,顺序还需结合 ABI 与源码声明。

成员访问与基类偏移

如果某成员函数把 this + constant 传给另一组函数,这个常量可能是基类子对象偏移:

1
2
lea rcx, [rdi+20h]
call Base2_Method

连续出现相同的 +20h 调整,并且目标函数都按自己的零偏移访问字段,是恢复第二基类的重要证据。

多重继承

多重继承对象可能具有:

  • 多个基类数据区域。
  • 多个虚表指针。
  • 多组虚函数表。
  • 调整 this 后再进入真正实现的跳板函数。

概念布局:

1
2
3
4
Derived
+0x00 BaseA subobject -> vtable A
+0x20 BaseB subobject -> vtable B
+0x38 Derived fields

通过 BaseB* 调用派生类覆盖函数时,编译器可能先把 thisBaseB 子对象地址调回完整对象地址,再跳转到实际函数。这类短函数常称为 adjustor thunk

菱形继承与虚继承

菱形继承中,两条继承路径可能带来两个共同祖先副本。虚继承用于共享一个虚基类子对象,但会增加运行时定位复杂度。

MSVC 常见实现可能使用虚基表指针和虚基表来计算虚基类偏移;其他 ABI 的实现方式不同。分析时应关注“从对象读取表项,再把表项作为偏移加到对象地址”的模式。

不能把某个编译器版本的多重继承布局当成 C++ 标准。先确定架构、编译器和 ABI,再解释每个隐藏字段。

覆盖、隐藏与重载

机器码层面应区分:

  • 覆盖:派生类替换虚表同一槽位的实现。
  • 隐藏:名称查找规则导致基类同名函数在源码中不可见,未必涉及虚表。
  • 重载:同一作用域中参数列表不同的多个函数,通常生成不同符号和函数体。

逆向恢复时最可靠的是槽位关系、调用原型和 this 调整,而不是源码术语猜测。

继承关系恢复流程

  1. 收集候选构造函数中的虚表写入。
  2. 按对象偏移把虚表分组。
  3. 对比各虚表槽位,寻找同槽位覆盖关系。
  4. 查找固定 this 调整和跳板函数。
  5. 汇总基类构造、析构调用顺序。
  6. 根据成员偏移画出候选对象布局。
  7. 使用动态对象实例验证各子对象地址。

草图模板

1
2
3
4
5
6
7
8
9
10
11
12
完整对象地址:
对象总大小:

offset 0x00:主基类 / vfptr
offset 0x??:主基类字段
offset 0x??:第二基类 / vfptr
offset 0x??:派生类字段

构造顺序:
析构顺序:
this 调整:
虚基类证据:

学习检查清单

检查项 状态
能解释派生对象为什么包含基类子对象 待复盘
能根据固定偏移识别非首基类 待复盘
能说明多重继承为何可能有多个虚表指针 待复盘
能识别用于调整 this 的短跳板函数 待复盘
能说明虚继承解决的问题与增加的复杂度 待复盘