SafetyHook
PROJECT_SOURCE_ANALYSIS
项目定位
SafetyHook 是一个面向 Windows/Linux x86 与 x86_64 的 C++23 过程 hook 库。它的核心目标不是做一个“大而全”的 DBI/跟踪框架,而是在运行时把指定函数入口、函数中间指令位置或对象虚表方法安全地改写到用户提供的目标逻辑上。
从源码结构看,项目的主线可以概括为:
InlineHook:函数入口级别的代码 patch 与 trampoline 构造,是最核心的实现。MidHook:在任意已知指令地址插入 hook,保存寄存器上下文后回调用户函数,但底层仍复用InlineHook完成 patch。VmtHook/VmHook:虚表复制与虚方法槽替换,不改写代码段。Allocator/Allocation:为 trampoline、stub、克隆 vtable 提供可执行内存,并尽量满足 x64 近跳转距离限制。os.*.cpp:封装虚拟内存、页保护和 patch 期间的线程/IP 修正策略。
证据来源
本次阅读依据当前工作树中的实际文件,而不是只依赖 README 或旧印象。重点查看了:
README.md:项目能力、依赖和最小 inline hook 用法。CMakeLists.txt、src/CMakeLists.txt、test/CMakeLists.txt、vcpkg.json:构建选项、C++23 约束、Zydis/GTest/Xbyak 依赖。include/safetyhook.hpp与include/safetyhook/*.hpp:公开 API、兼容别名、调用约定、错误类型、上下文结构。src/easy.cpp、src/inline_hook.cpp、src/mid_hook.cpp、src/vmt_hook.cpp、src/allocator.cpp、src/os.windows.cpp、src/os.linux.cpp、src/utility.cpp:核心实现。example/*.cpp:最小用法、多 hook、mid hook、VMT hook、DLL hook、线程场景。test/*.cpp:实际行为规格,覆盖多重 hook、短分支、RIP-relative relocation、无近距离内存 fallback、寄存器/XMM 修改、VMT RTTI 和 cross-cast。
顶层目录地图
| 路径 | 阅读价值 | 说明 |
|---|---|---|
include/ |
高 | public API 和类型边界。阅读时先建立用户视角,再进入实现。 |
src/ |
最高 | hook/trampoline/allocator/OS 抽象的实现主体。 |
example/ |
高 | 展示 API 的最小可用姿势,尤其是 create_inline、create_mid、create_vmt。 |
test/ |
高 | 行为规格,比 README 更能说明边界条件。 |
module/ |
中 | C++ module 导出层,理解 API 后再看即可。 |
docs/ |
低到中 | Doxygen 配置,不是核心逻辑。 |
cmake/ |
中 | 打包和依赖入口,源码机制阅读时可后置。 |
amalgamate.py |
中 | 单文件分发脚本,理解核心源码后再读。 |
阅读路线
阶段 1:项目入口和最小用法
要解决的问题:这个库让调用者怎么表达“我要 hook 哪里、跳到哪里、如何调用原函数”?
先看:
README.mdexample/minimal.cppexample/midhook.cppexample/vmthook.cppexample/dll.cpp
关键观察:
SafetyHookInline g_add_hook{}的生命周期非常重要,detour 函数通过g_add_hook.call<T>()进入 trampoline。SAFETYHOOK_NOINLINE防止示例目标函数被内联,否则函数入口可能不存在或不稳定。create_inline是入口函数 hook;create_mid是某条指令边界上的上下文 hook;create_vmt/create_vm是虚表 hook。
连接到下一阶段:这些简单 API 都在 include/safetyhook/easy.hpp 声明,在 src/easy.cpp 中转到更完整的 InlineHook::create、MidHook::create、VmtHook::create。
阶段 2:public API 与核心抽象
要解决的问题:用户可见的类型如何组织?哪些对象负责生命周期?
先看:
include/safetyhook.hppinclude/safetyhook/easy.hppinclude/safetyhook/inline_hook.hppinclude/safetyhook/mid_hook.hppinclude/safetyhook/vmt_hook.hppinclude/safetyhook/context.hpp
关键结构:
InlineHook是 move-only RAII 对象,析构时会destroy(),reset()会恢复目标函数并释放 trampoline。InlineHook::Error明确区分解码失败、短跳转、IP-relative 越界、内存保护失败和空间不足。MidHook保存InlineHook m_hook和一段m_stub,说明它不是独立 patch 引擎。Context64/Context32是 mid hook 用户回调能看到和修改的寄存器快照。VmtHook管整张克隆后的虚表,VmHook管单个虚方法槽位。
连接到下一阶段:API 只给出对象边界,真正的复杂性在 src/inline_hook.cpp 的 trampoline 与指令 relocation。
阶段 3:主流程和生命周期
要解决的问题:从创建 hook 到启用、禁用、销毁,真实调用链是什么?
主调用链:
safetyhook::create_inline
-> InlineHook::create
-> InlineHook::setup
-> InlineHook::e9_hook
-> InlineHook::ff_hook (x64 fallback)
-> InlineHook::enable
-> trap_threads
-> 写入目标函数入口跳转
禁用/销毁链:
InlineHook::reset / ~InlineHook
-> InlineHook::destroy
-> InlineHook::disable
-> trap_threads
-> 复制 m_original_bytes 回 target
-> m_trampoline.free
读法建议:
- 先看
src/easy.cpp,确认 easy API 只是吞掉错误并返回空对象。 - 看
InlineHook::create的StartDisabled分支,理解“先构造 trampoline,后选择是否 patch”的生命周期。 - 看
enable()和disable(),确认 patch 写入都经过trap_threads。 - 回到
e9_hook(),理解 trampoline 是怎样生成的。
阶段 4:底层机制专题
InlineHook 与 trampoline
src/inline_hook.cpp 的核心是 e9_hook():
- 解码目标函数入口处足够覆盖一个
JmpE9的原始指令。 - 把这些指令复制进 trampoline。
- 对 IP-relative displacement、relative immediate、短条件跳转和短无条件跳转做 relocation。
- 在 trampoline 尾部追加两段跳转:一段跳回原函数剩余部分,一段作为目标函数入口 patch 跳去 detour。
- 在
enable()中把原函数入口改写为跳到 trampoline 中的 destination 跳转段。
x64 下如果不能分配到 E9 rel32 所需的近距离 trampoline,会尝试 ff_hook():
- 原函数入口写
FF 25 [rip+disp32]形式的绝对间接跳转。 - trampoline 自身用绝对间接跳转回原函数剩余部分。
- 因为这种 fallback 常意味着 trampoline 不在 +/-2GB 范围内,所以不支持被搬运指令中存在 IP-relative 指令。
Allocator
Allocator 的职责是分配可执行内存,并尽量靠近目标地址:
Allocator::global()是默认共享池。Allocator::create()创建独立池。allocate_near()会先复用已有m_memory的 freelist,再通过vm_query搜索附近空闲页。internal_allocate_near()将分配大小至少按 2 字节对齐,用于兼容 Itanium ABI 中 member function pointer 对虚方法指针低位的假设。
阅读时要注意:可以共享的是 Allocator,不是一个 InlineHook 对象。一个 InlineHook 对象对应一个 target/destination pair。
Windows patch 安全策略
src/os.windows.cpp 中的 trap_threads() 是 SafetyHook 名字里 “Safety” 的关键之一:
- 建立
TrapManager和 VEH。 - 记录 from/to 地址区间。
- 修改 from/to 页保护,让 patch 写入能够进行。
- 如果其他线程在 patch 窗口访问 from 区间,异常处理器会用
fix_ip()把线程 IP 从旧位置修正到对应的新位置。 virtual_protect_mutex避免不同线程同时在同一页附近做保护切换。
它不是简单枚举线程并暂停所有线程,而是用页保护和异常处理器缩短危险窗口。
Linux patch 策略
src/os.linux.cpp 实现同一组 OS API,但 trap_threads() 只是在 patch 前后切换 VM_ACCESS_RWX,没有 Windows 那套 VEH/IP 修正。阅读文档或移植时要把平台差异明确分开。
MidHook
src/mid_hook.cpp 内置了一段按平台/架构区分的机器码 stub:
- stub 保存通用寄存器、flags 和 XMM 寄存器。
- 构造
Context并调用用户MidHookFn。 - 恢复上下文后跳回
InlineHook生成的 trampoline。 MidHook::setup()先分配 stub,再用InlineHook::create(..., StartDisabled)把目标地址导向 stub。
所以 create_mid 的语义是“在指定指令点观察/修改上下文”,不是“普通函数入口 detour 的另一个名字”。
VMT Hook
src/vmt_hook.cpp 不 patch code:
VmtHook::create(object)读取对象 vptr。- 根据
is_executable(*vm)统计虚方法数量。 - 分配并复制一整张新 vtable,同时带上 ABI-specific
VMT_HEADER。 - 把对象 vptr 指向新表的函数区。
hook_method(index, fn)替换新表中的某个槽位并返回VmHook,VmHook负责单槽恢复。
测试中的 VMTHookPreservesDynamicCastWithCrossCast 说明 VMT_HEADER 不是装饰,它直接关系到 RTTI 和 cross-cast 是否安全。
阶段 5:examples/tests 如何反向说明真实用法
高价值 examples:
example/minimal.cpp:最小 inline hook,展示call<int>和 RAII reset。example/midhook.cpp:寻找RET指令并 hook,展示ctx.rax/eax修改返回值。example/vmthook.cpp:展示 MSVC/Itanium ABI 下虚方法 index 差异。example/dll.cpp:DLL 场景下用stdcall调原 Windows API。example/threadsafe.cpp:演示 active function hook/unhook 的用法背景。
高价值 tests:
test/inline_hook.cpp:多重 hook、短跳转、enable/disable、活动函数 hook/unhook。test/inline_hook.x86_64.cpp:RIP-relative operand 和无近距离内存 fallback。test/mid_hook.cpp:整数寄存器、XMM 寄存器、enable/disable。test/vmt_hook.cpp:单对象、多对象、remove、RTTI、cross-cast。test/allocator.cpp:freelist 复用。
核心模块详解
Public umbrella
include/safetyhook.hpp 聚合 easy API、inline/mid/OS/VMT 头,并提供全局兼容别名:
SafetyHookInline = safetyhook::InlineHookSafetyHookMid = safetyhook::MidHookSafetyHookVmt = safetyhook::VmtHookSafetyHookVm = safetyhook::VmHookSafetyHookContext = safetyhook::Context
旧别名 SafetyInlineHook 和 SafetyMidHook 被标记为 deprecated。
Easy API
src/easy.cpp 的三个函数都遵循同一模式:
- 调完整错误返回版本。
- 成功则 move 出 hook。
- 失败则返回默认构造的空 hook。
这适合示例和不想处理错误的调用方,但深入排错时应直接用 InlineHook::create / MidHook::create / VmtHook::create。
InlineHook
InlineHook 维护:
m_target:原函数入口或被 hook 地址。m_destination:用户 detour。m_trampoline:可执行 trampoline 内存。m_original_bytes:被覆盖的原始字节。m_trampoline_size:trampoline 总大小。m_mutex:保护 enable/disable/call 之间的生命周期。m_enabled和m_type:当前 hook 状态和 patch 形式。
call() 系列会加锁并经 trampoline 调原函数;unsafe_call() 系列不加锁,适合对象生命周期稳定、不会并发卸载的场景。
MidHook
MidHook 的对象状态:
m_hook:内部InlineHook,负责把目标地址导向 stub。m_stub:保存寄存器、调用用户回调、恢复寄存器、跳 trampoline 的机器码。m_destination:用户的void(Context&)回调。
这里的关键阅读点是 MidHook::setup():先把 destination 和 trampoline 地址写入 stub 尾部,再让 InlineHook patch 到 stub。
VmtHook / VmHook
VmtHook 维护对象到原 vtable 的映射,支持把同一张新 vtable 应用到多个对象。VmHook 只知道一个槽位的原值、新值、槽位地址和保持新 vtable 存活的 shared allocation。
读法要点:
VmtHook::reset()恢复所有仍可写且仍指向新 vtable 的对象。- 如果对象已经释放,
vm_is_writable检查会让 reset 跳过,从而避免继续写坏地址。 VmHook::reset()只恢复单个方法槽,不恢复对象 vptr。
OS 与 Utility
os.hpp 定义跨平台虚拟内存和 patch 安全接口。utility.hpp/cpp 提供:
store():按字节写入结构或指针。align_up/align_down:页边界和分配粒度对齐。UnprotectMemory/unprotect():RAII 形式恢复页保护。
当前 src/inline_hook.cpp 的 patch 主要走 trap_threads,UnprotectMemory 更像通用辅助能力。
public API 与实现映射
| Public API | 声明 | 实现 | 阅读重点 |
|---|---|---|---|
safetyhook::create_inline |
include/safetyhook/easy.hpp |
src/easy.cpp -> src/inline_hook.cpp |
easy API 如何转入完整错误模型。 |
InlineHook::create |
include/safetyhook/inline_hook.hpp |
src/inline_hook.cpp |
setup、StartDisabled、enable。 |
InlineHook::call / unsafe_call |
include/safetyhook/inline_hook.hpp |
header-only | 加锁和不加锁的生命周期差异。 |
safetyhook::create_mid |
include/safetyhook/easy.hpp |
src/easy.cpp -> src/mid_hook.cpp |
stub + 内部 InlineHook。 |
MidHook::create |
include/safetyhook/mid_hook.hpp |
src/mid_hook.cpp |
stub 写入 destination/trampoline。 |
safetyhook::create_vmt |
include/safetyhook/easy.hpp |
src/easy.cpp -> src/vmt_hook.cpp |
克隆 vtable 并替换对象 vptr。 |
VmtHook::hook_method |
include/safetyhook/vmt_hook.hpp |
header-only | 槽位替换,返回 VmHook。 |
Allocator::allocate_near |
include/safetyhook/allocator.hpp |
src/allocator.cpp |
近距离可执行内存搜索和 freelist 复用。 |
trap_threads |
include/safetyhook/os.hpp |
src/os.windows.cpp / src/os.linux.cpp |
Windows 与 Linux patch 安全差异。 |
初始化与生命周期
InlineHook 生命周期
- 默认构造为空。
create()设置m_target/m_destination。setup()创建 trampoline 并保存原始字节。- 默认立即
enable();StartDisabled则延后 patch。 call()通过 trampoline 调原函数。disable()恢复m_original_bytes。reset()或析构触发destroy(),最终释放 trampoline。
MidHook 生命周期
create()分配 stub。- 把用户
MidHookFn写入 stub 固定位置。 - 内部创建
InlineHook,把目标地址跳到 stub。 - 把
InlineHooktrampoline 地址写入 stub。 - enable/disable/reset 全部委托或间接委托给内部
InlineHook。
VmtHook 生命周期
- 读取对象 vptr 并保存原始 vtable。
- 统计虚方法表长度并克隆表头和函数指针。
- 对象 vptr 指向新表。
hook_method()修改新表槽位。VmHook::reset()可恢复单个槽位。VmtHook::reset()恢复所有对象 vptr。
关键调用链
InlineHook create/enable
create_inline(target, destination)
src/easy.cpp
-> InlineHook::create(target, destination, flags)
-> InlineHook::create(Allocator::global(), ...)
-> hook.setup(allocator, target, destination)
-> e9_hook(allocator)
-> decode original instructions
-> allocate_near(...)
-> relocate instructions into trampoline
-> emit trampoline epilogue
-> ff_hook(allocator) when x64 e9 path fails
-> hook.enable() unless StartDisabled
-> trap_threads(...)
-> emit_jmp_e9 or emit_jmp_ff at target
MidHook create
create_mid(target, destination_fn)
-> MidHook::create
-> MidHook::setup
-> allocator->allocate(asm_data.size())
-> copy asm_data into m_stub
-> store destination_fn in stub tail
-> InlineHook::create(allocator, target, m_stub.data(), StartDisabled)
-> store m_hook.trampoline().data() in stub tail
-> enable unless StartDisabled
VmtHook create
create_vmt(object)
-> VmtHook::create
-> original_vmt = *object_vptr
-> count executable virtual method entries
-> allocate cloned table
-> memcpy(original_vmt - VMT_HEADER, size)
-> object_vptr = &new_vmt[VMT_HEADER]
关键数据结构
| 类型 | 文件 | 作用 |
|---|---|---|
InlineHook |
include/safetyhook/inline_hook.hpp |
函数入口 patch 的 RAII 句柄。 |
InlineHook::Error |
include/safetyhook/inline_hook.hpp |
指令解码、relocation、内存保护和空间不足的错误模型。 |
Allocation |
include/safetyhook/allocator.hpp |
一段由 Allocator 管理的可执行内存。 |
Allocator::Memory / FreeNode |
src/allocator.cpp |
分配池和空闲块链表。 |
Context64 / Context32 |
include/safetyhook/context.hpp |
MidHook 回调可读写的寄存器上下文。 |
TrapInfo |
src/os.windows.cpp |
Windows patch 窗口中 from/to 地址映射。 |
TrapManager |
src/os.windows.cpp |
VEH 管理器,负责异常时修正 IP。 |
VmHook |
include/safetyhook/vmt_hook.hpp |
单个虚方法槽位 hook。 |
VmtHook |
include/safetyhook/vmt_hook.hpp |
整张克隆 vtable 和对象集合。 |
逆向/底层机制专题
指令 relocation
SafetyHook 不只是把原始字节搬到 trampoline。e9_hook() 会识别:
- RIP/IP-relative displacement。
- relative immediate 分支/调用。
- short conditional branch。
- short unconditional branch。
对短分支,它会把短跳转拓宽成 near branch,并处理目标地址落在被搬运原始指令块内部的情况。
原函数调用
InlineHook::original<T>() 只是把 trampoline 地址 cast 成函数指针,不执行调用。真正执行的是:
call()/ccall()/thiscall()/stdcall()/fastcall()unsafe_call()系列
带锁版本会通过 m_mutex 与 enable/disable/reset 协调;unsafe 版本跳过锁,读者应把它和 hook 对象生命周期绑定起来理解。
多重 hook
test/inline_hook.cpp 的 FunctionHookedMultipleTimes 说明同一目标可以被多次 hook。后安装的 hook 会先接到调用,再通过它自己的 trampoline 进入前一层 hook,形成后进先出的链式效果。
VMT 与 ABI
VMT_HEADER 在 MSVC ABI 下为 1,在 Itanium ABI 下为 2。它让克隆 vtable 保留 RTTI 和 offset-to-top 等非函数项,否则 dynamic_cast、cross-cast 和析构相关行为可能崩溃或返回错误指针。
examples/tests 阅读价值
建议把 examples 当“入口”,tests 当“边界规格”:
example/minimal.cpp配合test/inline_hook.cpp::FunctionWithMultipleArgsHooked理解 inline hook。example/midhook.cpp配合test/mid_hook.cpp理解Context修改参数/寄存器。example/vmthook.cpp配合test/vmt_hook.cpp理解 ABI index 和 RTTI。example/dll.cpp配合InlineHook::stdcall理解 Windows API hook 的调用约定。test/inline_hook.x86_64.cpp是 x64 relocation/fallback 的必读边界。
第三方依赖边界
- Zydis 是指令解码依赖,SafetyHook 使用它判断指令长度、relative 属性、branch 类型和 displacement/imm offset。
- GTest 只用于测试。
- Xbyak 只用于测试中动态生成机器码,帮助覆盖短分支、RIP-relative、远距离地址等编译器不容易稳定生成的情况。
- CPM/vcpkg/CMake 是依赖与构建管理,不是 hook 机制本身。
本次不重注释第三方依赖或 generated/amalgamated 输出。
核心文件注释计划
第一批:public API 与入口文件
include/safetyhook.hppinclude/safetyhook/easy.hppinclude/safetyhook/inline_hook.hppinclude/safetyhook/mid_hook.hppinclude/safetyhook/vmt_hook.hppinclude/safetyhook/context.hppinclude/safetyhook/allocator.hppinclude/safetyhook/os.hpp
第二批:主实现
src/easy.cppsrc/inline_hook.cppsrc/allocator.cppsrc/os.windows.cppsrc/os.linux.cppsrc/mid_hook.cppsrc/vmt_hook.cppsrc/utility.cpp
第三批:入口样例和行为规格索引
example/minimal.cppexample/midhook.cppexample/vmthook.cppexample/dll.cpptest/inline_hook.cpptest/inline_hook.x86_64.cpptest/mid_hook.cpptest/vmt_hook.cpp
第四批:模块导出、剩余示例和测试支撑文件
module/safetyhook.cppmexample/module.cpptest/allocator.cpptest/main.cpptest/vmt_targets.hpptest/vmt_targets.cpp
第五批:MidHook 汇编 stub 源文件
src/mid_hook.x86_64-windows.asmsrc/mid_hook.x86_64-linux.asmsrc/mid_hook.x86_32.asm
有意跳过的文件:
LICENSE:许可证文本,不插入阅读注释。vcpkg.json:依赖声明极短,已在文档说明 Zydis 依赖。CMakeLists.txt、src/CMakeLists.txt、test/CMakeLists.txt、example/CMakeLists.txt、module/CMakeLists.txt、docs/CMakeLists.txt、cmake/*:构建入口已经在文档中说明;为避免把源码阅读注释扩散到构建配置,本轮不改。docs/Doxyfile.in:Doxygen 配置,不是机制源码。amalgamate.py与cmake/amalgamation/:分发/打包层,核心 hook 机制读完后可单独做 packaging 阅读。
已完成注释批次
第一批:public API 与入口文件
- 已注释:
include/safetyhook.hpp、include/safetyhook/easy.hpp、include/safetyhook/inline_hook.hpp、include/safetyhook/mid_hook.hpp、include/safetyhook/vmt_hook.hpp、include/safetyhook/context.hpp、include/safetyhook/allocator.hpp、include/safetyhook/os.hpp、include/safetyhook/utility.hpp、include/safetyhook/common.hpp。 - 注释重点:umbrella header 边界、easy API 的错误吞掉策略、
InlineHookRAII 生命周期、call()与unsafe_call()差异、MidHook的 Context 语义、VMT ABI 表头、Allocator 近距离分配和 OS 抽象。
第二批:主实现
- 已注释:
src/easy.cpp、src/inline_hook.cpp、src/allocator.cpp、src/os.windows.cpp、src/os.linux.cpp、src/mid_hook.cpp、src/vmt_hook.cpp、src/utility.cpp。 - 注释重点:
e9_hook两轮扫描、relative/short branch relocation、trampoline epilogue、x64ff_hookfallback、enable()/disable()的trap_threads包裹、freelist 复用、Windows VEH/IP 修正、Linux 页保护差异、MidHook stub 地址槽和 VMT 恢复保护。
第三批:入口样例和行为规格索引
- 已注释:
example/minimal.cpp、example/midhook.cpp、example/vmthook.cpp、example/dll.cpp、example/multiple.cpp、example/threadsafe.cpp、test/inline_hook.cpp、test/inline_hook.x86_64.cpp、test/mid_hook.cpp、test/vmt_hook.cpp。 - 注释重点:最小 inline hook 生命周期、多重 hook 链、MidHook 指令点语义、VMT hook 不改代码段、DLL 调用约定、活动线程 patch 背景、短分支/RIP-relative/fallback 行为规格、XMM 修改和 VMT cross-cast 安全。
第四批:模块导出、剩余示例和测试支撑文件
- 已注释:
module/safetyhook.cppm、example/module.cpp、test/allocator.cpp、test/main.cpp、test/vmt_targets.hpp、test/vmt_targets.cpp。 - 注释重点:C++ module 只是 public API 的再导出镜像、宏仍需通过头文件 include、Allocator freelist 复用测试、GTest 入口边界、VMT 测试目标防止反虚拟化、cross-cast 测试依赖 ABI vtable 表头。
第五批:MidHook 汇编 stub 源文件
- 已注释:
src/mid_hook.x86_64-windows.asm、src/mid_hook.x86_64-linux.asm、src/mid_hook.x86_32.asm。 - 注释重点:stub 与
src/mid_hook.cpp中asm_data的关系、保存/恢复寄存器和 XMM 的 Context 布局、Windows x64rcx与 Linux x64rdi调用约定差异、32-bit 栈上传参方式、destination/trampoline运行时地址槽位。
未决问题和下一步
- 核心阅读路线中的源码注释批次已经完成;后续如果继续深入,建议进入“逐函数精读”而不是继续扩大注释面。
src/inline_hook.cpp的短分支 relocation 细节仍然最值得逐行复读,尤其是 trampoline 内目标地址的处理。src/os.windows.cpp的TrapManager与trap_threads()是理解“patch 期间如何保护其它线程”的重点。src/mid_hook.cpp的asm_data已与三份mid_hook.*.asm建立阅读对应关系;下一步可用反汇编或脚本验证数组字节和汇编源是否逐字节一致。src/vmt_hook.cpp已配合test/vmt_targets.hpp/cpp标出 MSVC/Itanium ABI 表头边界;后续可单独画 vtable 内存布局图。

浙公网安备 33010602011771号