基础知识
关于block原理的文章已经有很多,这里就没必要再复述一遍。只列出一些和主题密切相关的知识点。
block是什么?
block就是block
block是一个NSBlock对象。
block是一个指针,指向下面这个结构体。
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
void (*invoke)(void *, ...);
struct Block_descriptor_1 *descriptor;
// imported variables
};
调用block的时候发生了什么
block内的代码会被编译成一个C函数。block的invoke指针,指向这个函数,调用block等于调用这个函数。
怎么hook一个block
hook一个block需要两步。
1 让block调用的时候不去调用原本的函数,而是调用自己的函数。
2 自己的函数再去调用block原本的函数。
第一步很简单,只需要把block的invoke指针指向自己的函数就可以了。
第二步是这篇文章的重点,接下来逐一讲述。
block和普通对象一样,支持消息转发
如果我们把block的invoke指针指向_objc_msgForward。调用block,会触发消息转发。我们会在forwardInvocation:中拿到一个参数已经被放好了的NSInvocation对象。走到这里,形式已经一片大好。
当NSInvocation对象的target为block时,调用其invoke方法,会发生什么?
使用hopper观察[NSInvocation invoke]的实现。得到如下结果:
大概解释一下这个方法的流程,invoke最终通过invoking完成调用。在调用前,其第一个参数 $r15 会被赋值为objc_msgSend或objc_msgSend_stret。接着,遍历target的superClass,查看是否继承自NSBlock。如果继承自NSBlock,就把target的首地址+0x10赋值给 $r15。而block的首地址+16就是block的invoke指针。由此不难看出,NSInvocation本身是便完美支持target作为block使用。
有了NSInvocation的支持,就可以安心的通过invoke去调用block。
还需要一个假block
最后,整体的流程大概是这样:
在准备hook一个block时,我们需要先copy这个block,并让block持有copy结果。这里copy的主要的目的是复制外部变量。
设置block的invoke指针为_objc_msgForward,调用block触发消息转发。
在forwardInvocation:中执行完自己的逻辑后,将invocation的target设置为刚刚copy的block,执行invoke。完成Hook。
PS: 在FishBind中,hookBlock被作为其中的一个功能点。有兴趣的朋友可以去看相关的具体实现。