Block源码分析

939 阅读4分钟

juejin.cn/post/699877… 节中我们研究了block的一些现象,这节我们来从源码的角度分析block,看看它里面是怎么实现的。

clang看block

#include "stdio.h"

int main(){

    void(^block)(void) = ^{
        printf("这是一个block");
    };
    
     block();
    return 0;
}

用clang来查看源码

  void(*block)(void) = ((void (*)())&__main_block_impl_0(
                                                           (void *)__main_block_func_0,
                                                           &__main_block_desc_0_DATA
                                                           ));

我们看一下__main_block_impl_0

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

block中传入的第一个参数为__main_block_func_0

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

        printf("这是一个block");
    }

这是一个函数,第一个参数是函数地址,传过去一个函数 block的调用

 ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

__block_impl的结构如下

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

去掉强制转换block->FuncPtr(block)即调用__main_block_func_0函数,传参为block

block捕获变量

#include "stdio.h"
int main(){
    int a =0;
    void(^block)(void) = ^{
        printf("这是一个block%d", a);
    };
    
     block();
    return 0;
}

clang之后

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a; //1
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {//2
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy //3

        printf("这是一个block%d", a);
    }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(){
    int a =0;
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));

     ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

1.捕获了变量之后,block里面有一个变量int a 2.block调用的时候block->FuncPtr(block)传入block int a = __cself->a; 函数调用中的a 和外面的a 值相同,地址不同, 📢我们知道这是一个堆block,但是 impl.isa = &_NSConcreteStackBlock;提示它是一个栈block,我们还没有运行,那么什么时候它变成了堆block,中间做了什么呢

所以修改的话,会报错,我们知道,需要使用__block修饰变量a,才能在block里面修改a

被__block修饰

int main(){
    __block  int a  = 0;
    void(^block)(void) = ^{
        a = 5;
        printf("这是一个block%d", a);
    };
    
     block();
    return 0;
}

clang之后


struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref

        (a->__forwarding->a) = 5;
        printf("这是一个block%d", (a->__forwarding->a));
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(){
    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 0};
    
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,
                                                           &__main_block_desc_0_DATA,
                                                           (__Block_byref_a_0 *)&a,
                                                           570425344));

     ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,
                                                           &__main_block_desc_0_DATA,
                                                           (__Block_byref_a_0 *)&a,
                                                           570425344));

我们可以看到block的第三个参数为(__Block_byref_a_0 *)&a,被__block修饰之后,a变成了__Block_byref_a_0__Block_byref_a_0中可以看出

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};
 __attribute__((__blocks__(byref))) __Block_byref_a_0 a = 
 {
        (void*)0,
        (__Block_byref_a_0 *)&a,
        0,
        sizeof(__Block_byref_a_0),
        0
  };

a指向了__forvarding 从函数的调用看


 void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,
                                                           &__main_block_desc_0_DATA,
                                                           (__Block_byref_a_0 *)&a,
                                                           570425344));

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref

        (a->__forwarding->a) = 5;
        printf("这是一个block%d", (a->__forwarding->a));
    }

传入参数为block,block中传入的是(__Block_byref_a_0 *)&a a的地址,所以调用的时候__Block_byref_a_0 *a = __cself->a;里面的a和外面的a指向的是同一个a。可以被修改。

_Block_copy

截屏2021-08-29 下午5.32.59.png 如图打一个断点,我们看下汇编, 截屏2021-08-29 下午5.32.12.png 我们看到里面调用了objc_retainBlock

id objc_retainBlock(id x) {
    return (id)_Block_copy(x);
}

我们看下_Block_copy


void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock;

    if (!arg) return NULL;
    
    // The following would be better done as a switch statement
    aBlock = (struct Block_layout *)arg;
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    else {
        // Its a stack block.  Make a copy. -> heap
        struct Block_layout *result =
            (struct Block_layout *)malloc(aBlock->descriptor->size);
        if (!result) return NULL;
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
        // Resign the invoke pointer as it uses address authentication.
        result->invoke = aBlock->invoke;
#endif
        // reset refcount -- 对象 isa 联合体位于
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        _Block_call_copy_helper(result, aBlock);
        // Set isa last so memory analysis tools see a fully-initialized object.
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}

我们看到,如果block在栈上,则进行copy到堆上面,然后result->isa = _NSConcreteMallocBlock;

Block的结构

#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
    uintptr_t reserved;
    uintptr_t size;
};

#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
    // requires BLOCK_HAS_COPY_DISPOSE
    BlockCopyFunction copy;
    BlockDisposeFunction dispose;
};

#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
    // requires BLOCK_HAS_SIGNATURE
    const char *signature;
    const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};

// KC注释:Block 结构体
struct Block_layout {
    void *isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved;
    BlockInvokeFunction invoke;
    struct Block_descriptor_1 *descriptor;
    // imported variables
};

block在底层是一个结构体,我们看到Block_descriptor_3里面有signature在一些block的hook中,我们会拿到block的签名,先保存起来,在消息转发的合适时机,触发签名的调用,实现block的hook。 BLOCK_DESCRIPTOR_2BLOCK_DESCRIPTOR_3BLOCK_DESCRIPTOR_1他们的关系是怎么样的呢,我们找到他们的取值函数

static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
    if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
    uint8_t *desc = (uint8_t *)aBlock->descriptor;
    desc += sizeof(struct Block_descriptor_1);
    return (struct Block_descriptor_2 *)desc;
}

// KC注释: Block 的描述 : 签名相关
static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
    if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return NULL;
    uint8_t *desc = (uint8_t *)aBlock->descriptor;
    desc += sizeof(struct Block_descriptor_1);
    if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
        desc += sizeof(struct Block_descriptor_2);
    }
    return (struct Block_descriptor_3 *)desc;
}

Block_descriptor_2 中会判断是否有BLOCK_HAS_COPY_DISPOSE标记,然后 desc += sizeof(struct Block_descriptor_1);偏移了Block_descriptor_1的大小。同理Block_descriptor_3。可见他们的地址是连续的,来看一下这个标记


enum {
    BLOCK_DEALLOCATING =      (0x0001),  // runtime
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    BLOCK_IS_GC =             (1 << 27), // runtime
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
};