内容简介:在使用由于关于 Block Private Data 的资料几乎没有,所以我完全可以当回标题党,把这篇文章的标题叫做『你真的了解 Block 么?』或者『这才是 Hook Block 的正确姿势』之类的。但想想还是算了吧,怕被大佬们嘲笑称又『改变业界』了啊。首先来看一段代码:
在使用 BlockHook Hook 所有 Block 对象时,发现有些 Block 被 Hook 后会 Crash。究其原因发现是它们骨骼惊奇,夹带了很多『私货』,不能直接 Hook!本文讲述 BlockHook 在处理这种 Block 时的技术原理,解开含有 Private Data 的 Block 的神秘面纱。
由于关于 Block Private Data 的资料几乎没有,所以我完全可以当回标题党,把这篇文章的标题叫做『你真的了解 Block 么?』或者『这才是 Hook Block 的正确姿势』之类的。但想想还是算了吧,怕被大佬们嘲笑称又『改变业界』了啊。
Block 为何会有 Private Data
首先来看一段代码:
dispatch_block_t block = dispatch_block_create(0, ^{ NSLog(@"I'm dispatch_block_t"); });
用 dispatch_block_create
创建的 Block 都很特殊,返回的 Block 包含了参数里传入的 Block。此时 dispatch_block_t
虽然表面上是一种普通的 Block,但它的构造暗藏玄机,含有 Private Data,下面会详细解读。
特殊的 invoke 函数
这种 Block 的 invoke
函数指针是固定的,函数名为 ___dispatch_block_create_block_invoke
。在 linux 系统下,函数名为 __dispatch_block_create_block_invoke
,嗯少了个下划线。这个函数的定义来自 libdispatch.dylib,也就是我们常用的 GCD。
extern "C" { // The compiler hides the name of the function it generates, and changes it if // we try to reference it directly, but the linker still sees it. extern void DISPATCH_BLOCK_SPECIAL_INVOKE(void *) #ifdef __linux__ asm("___dispatch_block_create_block_invoke"); #else asm("____dispatch_block_create_block_invoke"); #endif void (*const _dispatch_block_special_invoke)(void*) = DISPATCH_BLOCK_SPECIAL_INVOKE; }
libdispatch 会通过判断 Block 的 invoke
指针是否为 _dispatch_block_special_invoke
,来知道这个 Block 是否含有 Private Data。
DISPATCH_ALWAYS_INLINE static inline bool _dispatch_block_has_private_data(const dispatch_block_t block) { return (_dispatch_Block_invoke(block) == _dispatch_block_special_invoke); }
不幸的是, _dispatch_block_special_invoke
是私有的。在非调试场景下是无法通过 dladdr
等方式来获取它的函数名的。也就无法用类似上面的代码来判断 Block 是否含有 Private Data 了。
获取 Private Data
使用 dispatch_block_create
创建的 dispatch_block_t
只是个『壳』,真正执行的是其内部包含的 Block。再加上 GCD 所需的一些数据(queue,group,thread,priority 等),这些数据都需要作为 Private Data 追加在 Block 上。对实现 BlockHook 来说最需要关注的就是 dbpd_magic
和 dbpd_block
。
OS_OBJECT_DECL_CLASS(voucher); struct dispatch_block_private_data_s { unsigned long dbpd_magic; dispatch_block_flags_t dbpd_flags; unsigned int volatile dbpd_atomic_flags; int volatile dbpd_performed; unsigned long dbpd_priority; voucher_t dbpd_voucher; dispatch_block_t dbpd_block; dispatch_group_t dbpd_group; dispatch_queue_t dbpd_queue; mach_port_t dbpd_thread; }; typedef struct dispatch_block_private_data_s *dispatch_block_private_data_t;
既然无法用 _dispatch_block_special_invoke
来判断 Block 是否含有 Private Data,可以使用 dbpd_magic
魔数来判断。当其值为 0xD159B10C
时(DisBloc 的意思),则表明含有 Private Data。 当然这种溢出的方式同样是有风险的,但触及到 PAGEZERO 概率很低
。
#define DISPATCH_BLOCK_PRIVATE_DATA_MAGIC 0xD159B10C // 0xDISPatch_BLOCk DISPATCH_ALWAYS_INLINE static inline dispatch_block_private_data_t bh_dispatch_block_get_private_data(struct _BHBlock *block) { // Keep in sync with _dispatch_block_create implementation uint8_t *x = (uint8_t *)block; // x points to base of struct Block_layout x += sizeof(struct _BHBlock); // x points to base of captured dispatch_block_private_data_s object dispatch_block_private_data_t dbpd = (dispatch_block_private_data_t)x; if (dbpd->dbpd_magic != DISPATCH_BLOCK_PRIVATE_DATA_MAGIC) { return nil; } return dbpd; }
最后真正执行的其实是 dbpd_block
这个 Block, dispatch_block_t
只是个保存各种元数据的壳。
适配 BlockHook
虽然说 Private Data 本身并不是 Block 实现中必要的一环,它只是 GCD 对 Block 数据结构的一种『魔改』扩充。但由于 GCD 内部的一些保护机制,会在修改了 Block 的 invoke
指针后触发 crash( __builtin_trap
),所以不能直接对含有 Private Data 的 Block 进行 Hook。这就需要 BlockHook 组件做一些适配工作。
Hook 真正要执行的 Block
既然 dbpd_block
才是真正要执行的 Block,那么 Hook 的时候需要先获取 Private Data,然后对其 dbpd_block
进行 Hook:
- (BHToken *)block_hookWithMode:(BlockHookMode)mode usingBlock:(id)aspectBlock { if (!aspectBlock || ![self block_checkValid]) { return nil; } struct _BHBlock *bh_block = (__bridge void *)self; if (!_bh_Block_descriptor_3(bh_block)) { NSLog(@"Block has no signature! Required ABI.2010.3.16. %@", self); return nil; } // Handle blocks have private data. dispatch_block_private_data_t dbpd = bh_dispatch_block_get_private_data(bh_block); if (dbpd && dbpd->dbpd_block) { return [dbpd->dbpd_block block_hookWithMode:mode usingBlock:aspectBlock]; } return [[BHToken alloc] initWithBlock:self mode:mode aspectBlockBlock:aspectBlock]; }
获取 Block 当前 Hook Token
因为 Hook 的是 dbpd_block
,所以获取 Token 的时候也需要额外处理下。要在 dbpd_block
上通过 AssociatedObject 来获取 Token,而不是 dispatch_block_t
上。
- (BHToken *)block_currentHookToken { if (![self block_checkValid]) { return nil; } dispatch_block_private_data_t dbpd = bh_dispatch_block_get_private_data((__bridge struct _BHBlock *)(self)); if (dbpd && dbpd->dbpd_block) { return [dbpd->dbpd_block block_currentHookToken]; } void *invoke = [self block_currentInvokeFunction]; return objc_getAssociatedObject(self, invoke); }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
孵化Twitter
[美]尼克·比尔顿(Nick Bilton) / 欧常智、张宇、单旖 / 浙江人民出版社 / 2014-1 / 49.90元
一个在挣扎中生存的博客平台Odeo,一小撮龙蛇混杂的无政府主义者员工,经历了怎样的涅槃,摇身一变,成为纽交所最闪耀的上市企业Twitter? 一个野心勃勃的农场小男孩,一个满身纹身的“无名氏“,一个爱开玩笑的外交家,一位害羞而又充满活力的极客,这四位各有特色的创始人如何从兢兢业业、每日劳作的工程师,成为了登上杂志封面、奥普拉秀和每日秀的富裕名人?而在Twitter日益茁壮成长的过程中,他们又......一起来看看 《孵化Twitter》 这本书的介绍吧!