揭开 BlockHook 底层原理:libffi 闭包如何偷梁换柱替换 Block 的 invoke 指针

📅 2026/8/18 16:19:16
揭开 BlockHook 底层原理:libffi 闭包如何偷梁换柱替换 Block 的 invoke 指针
揭开 BlockHook 底层原理libffi 闭包如何偷梁换柱替换 Block 的 invoke 指针【免费下载链接】BlockHookHook Objective-C blocks. A powerful AOP tool.项目地址: https://gitcode.com/gh_mirrors/bl/BlockHookBlockHook 是一个 Hook Objective-C Block 的 AOP 工具它基于 libffi 实现支持 Before、Instead、After、Dead 四种 Hook 模式还能让你自由修改参数与返回值。很多同学会用它但说不清原理本文将层层拆解 BlockHook 底层原理看它如何用 libffi 闭包偷梁换柱把 Block 的 invoke 指针替换成自己的函数从而在不改动源码的情况下接管一次调用。一、BlockHook 是什么Hook Objective-C Block 的 AOP 利器在 Objective-C 里我们习惯了用 Method Swizzling 去 Hook 方法但面对 Block 却常常束手无策。BlockHook 的出现填补了这个空白它让我们能像 Hook 方法一样 Hook Block在 Block 执行前插入逻辑Before、完全接管执行Instead、执行后观察结果After甚至还能感知 Block 何时被释放Dead。围绕这一套能力它提供了三个核心类负责 Hook 入口的NSObject (BlockHook)分类BlockHook.m、保存 Hook 状态与上下文的BHTokenBHToken.m、以及让开发者读写参数和返回值的BHInvocationBHInvocation.m。二、先看 Block 的内存布局invoke 指针藏在哪里要 Hook 一个 Block第一步是搞清楚它的内存结构。在 BHHelper.h 中BlockHook 复刻了 Block 的布局typedef struct BHBlock { void *isa; volatile int32_t flags; int32_t reserved; BHBlockInvokeFunction invoke; // 真正的执行入口 BHBlockDescriptor1 *descriptor; } BHBlock;关键就在第三个字段invoke它是指向真正执行函数的指针。我们在代码里写block(3, 5)最终都会跳转到这个函数指针上执行。所以Hook 的思路非常直接把invoke换成我们自己的函数就等于劫持了 Block 的全部调用——这就是标题里偷梁换柱的对象。三、libffi 闭包是什么能动态生成可调用函数的黑科技直接把invoke换成一个普通 C 函数行不行不行因为我们不知道 Block 的类型签名参数几个、各是什么类型、返回值是什么无法预先写出匹配的 C 函数。这时就要请出 libffi。libffi 的闭包closure能在运行时动态生成一段真正的可调用代码ffi_closure_alloc分配一块可执行内存作为函数指针ffi_prep_closure_loc绑定参数布局与回调函数。之后这段内存就可以被当作一个普通函数指针来调用调用发生时 libffi 会自动把参数打包好转交给我们的回调。这正是 BlockHook 底层原理中最核心的一块拼图用 libffi 闭包生成一个长相和原 Block 完全一致的函数指针作为替换invoke的备胎。四、偷梁换柱三步走BlockHook 替换 invoke 指针的完整流程在 BHToken.m 的初始化方法里完整流程清晰可见第一步解析 Block 签名搭建 ffi_cifBlock 的描述符里藏着签名signatureBlockHook 通过_bh_Block_descriptor_3取出它再用_prepCIF把签名逐字符翻译成 libffi 认识的参数类型数组与返回类型最终ffi_prep_cif生成一份调用契约ffi_cif。这样 libffi 闭包就完全对齐了原 Block 的参数与返回值。第二步创建 libffi 闭包得到新函数指针_closure ffi_closure_alloc(sizeof(ffi_closure), _replacementInvoke); ffi_prep_closure_loc(_closure, _cif, BHFFIClosureFunc, (__bridge void *)(self), _replacementInvoke);ffi_closure_alloc分配闭包内存并把入口地址写入_replacementInvokeffi_prep_closure_loc把它和回调BHFFIClosureFunc绑定。从此_replacementInvoke就是一个与 Block 签名完全兼容的替身函数。第三步ReplaceBlockInvoke 原子替换 invoke 指针self.originInvoke block-invoke; // 先保存原指针 ReplaceBlockInvoke(block, _replacementInvoke); // 再换成替身先备份原指针到originInvoke再调用 BHHelper.m 中的ReplaceBlockInvoke完成替换。由于invoke可能位于只读内存页函数内部会用vm_protect临时开启写权限、写入新指针、再恢复原权限整个过程有锁保护保证线程安全。至此偷梁换柱完成外部调用者完全无感但每一次调用都已落入 BlockHook 的掌中。五、替换之后发生了什么BHFFIClosureFunc 的分发逻辑替换完成后真正的幕后黑手是回调函数BHFFIClosureFunc。它收到 libffi 打包好的参数后会创建一个BHInvocation把参数、返回值地址装进去然后按顺序分发若模式包含Before先执行你的前置 Hook若模式包含Instead则调用你的接管逻辑否则通过ffi_call调用保存的originInvoke执行原 Block若模式包含After最后执行后置 Hook此时你还能通过setReturnValue偷改返回值。借助BHInvocation的getArgument/setArgument/getReturnValue/setReturnValue接口BHInvocation.h你可以实现改参、改返回值、甚至完全替换实现这就是 BlockHook 强大 AOP 能力的来源。六、如何还原BHToken 与移除 Hook 的秘密每个 Hook 都会返回一个BHToken令牌调用[token remove]即可撤销 Hook。移除时BlockHook 会沿着令牌链表找到当前链头再把block-invoke恢复成之前保存的originInvoke并用objc_setAssociatedObject清理关联对象。多次 Hook 一个 Block 时令牌会串成一条链移除中间某个令牌也能安全地把缺口焊死不留后患。七、Block 销毁也能感知Dead 模式与 BHDealloc 的配合最后还有一个彩蛋BlockHook 在 Hook 时会创建一个BHDealloc对象并通过关联对象挂在 Block 上BHDealloc.m。当 Block 被释放时这个关联对象也随之 dealloc从而触发Dead模式回调——让你能追踪 Block 的完整生命周期这在调试内存问题、统计闭包泄漏时非常实用。八、写在最后BlockHook 底层原理速览回顾全文BlockHook 底层原理可以浓缩为四句话 Block 的invoke指针就是调用入口替换它即完成 Hook libffi 闭包能按签名动态生成兼容的函数指针充当替身 先备份originInvoke再用ReplaceBlockInvoke完成替换BHFFIClosureFunc负责分发各模式 移除时把指针还原、Block 释放时触发 Dead闭环完整。对底层原理感兴趣的同学可以直接克隆源码通读git clone https://gitcode.com/gh_mirrors/bl/BlockHook重点看BlockHook/BHHelper.m与BlockHook/BHToken.m两个文件相信你会有更深的理解。【免费下载链接】BlockHookHook Objective-C blocks. A powerful AOP tool.项目地址: https://gitcode.com/gh_mirrors/bl/BlockHook创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考