BlockHook with Private Data

栏目: IOS · 发布时间: 5年前

内容简介:在使用由于关于 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_magicdbpd_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);
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

来自圣经的证明

来自圣经的证明

M.Aigner、G.M.Ziegler / 世界图书出版公司 / 2006-7 / 39.00元

作为一门历史悠久的学问,数学有她自身的文化和美学,就像文学和艺术一样。一方面,数学家们在努力开拓新领域、解决老问题;另一方面他们也在不断地从不同的角度反复学习、理解和欣赏前辈们的工作。的确,数学中有许多不仅值得反复推敲理解,更值得细心品味和欣赏的杰作。有些定理的证明不仅想法奇特、构思精巧,作为一个整体更是天衣无缝。难怪,西方有些虔诚的数学家将这类杰作比喻为上帝的创造。 本书已被译成8种文字。......一起来看看 《来自圣经的证明》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

随机密码生成器
随机密码生成器

多种字符组合密码

SHA 加密
SHA 加密

SHA 加密工具