前一篇讲了两个简单的Hook Block面试题目,主要了解一下基本的Hook Block的思路和注意的问题。实现都不算很难,属于基操,基本按照流程来就问题不大。
这里,打算对题目二
进行一个额外的扩展,将题目改为:
1 | // 2.实现下面的函数,将任意参数 block 的实现修改成打印所有入参,并调用原始实现 |
分析题目:首先,题目的本意和原本的题目二一样,就是hook block 的 invoke,然后将其所有的入参打印出来,再调用原实现。区别在于“任意Block”,这个任意block,就让我们无法对用来替换的函数有一个很合适的定义,因为我们定义的时候,根本就不知道我们即将hook的block有几个参数。
基于这个不确定性的参数,我们第一反应肯定是变参函数。如果将其定义为变参函数,是不是就解决了这个问题呢?
经过实践,效果似乎不太理想。主要原因猜测是因为变参函数的实参传入时存储的寄存器和定参函数实参传入的寄存器有所出入。仔细一想其实好像也是这么一回事,就像对象或者结构体一样,即使在运行过程中,可以用类型转换将其转换为任意结构体或者对象,但是它本质是不会变的,如果使用不当,就会产生问题。当然,这点仅仅是猜测,还未深究,总之就是变参函数这条路,我们是走不通的。
既然我们能现有的想到的方法无法如愿,那我们就只能另辟蹊径(百度google一顿搜索)了。网上也有挺多介绍hook block的示例代码,但是他们的思路都基本是用block hook block。总结一下就是“极限一换一”。怎么极限一换一呢?其实就是把目前我们遇到的问题,丢给使用者,就是让使用者实现一个block去hook 使用者想hook的block。当然,这个block是有要求的,就是要和厡block的返回值,参数类型和个数等等都一致,不然就GG。这显然不是我们最理想的解决办法。
抱着我们不可能是第一个遇到这个问题的人的信念,总算找到了我们想要的思路(其实包括实现都有)。鉴于这个代码已经封装成一个能使用的库了,直接使用显然不太符合我们的初衷,所以我们还是舍近求远,摒弃现有工具,直接对着源码理清思路自己操作。
源码里提到了另一个库libffi,这个是重点,我们遇到的问题,就靠它解决。
首先讲一下我查看源码看到的一个大致思路:
1、获取要hook的block的相关信息,例如返回值、参数列表。这些信息都存储在bkock的方法签名里。
2、通过上一步获取到的信息,利用libffi
创建一个函数模板(ffi_prep_cif()
)。
3、创建动态调用函数,并替换block中的Invoke。
4、编写替换函数,并实现调用原函数。
思路基本就是这个样子。那我们现在就来写点代码走一下流程:
首先获取block的相关信息:
1 | struct Block_layout *layout = (__bridge struct Block_layout *)block; |
创建函数模板:
1 | ffi_type **args = malloc(sizeof(ffi_type *)*[m_signature numberOfArguments]); |
创建并绑定动态调用的函数:
1 | // _closure 定义的是全局变量 ffi_closure *_closure; |
替换block的invoke:
1 | // 修改内存属性 |
还有最后一步,也是最关键的一步,实现替换的函数:
1 | void replace_bloke2_2(ffi_cif *cif, void *ret, void **args, void *userdata) { |
最后测试代码:
1 | // test2 |
log如下:
1 | ...... |