反汇编 Block 的内部逻辑实现

一下是汇编中 block 在汇编中的体现。

__text:00000001002BF6EC                 BL              _objc_release
__text:00000001002BF6F0                 ADRP            X0, #aComKujiangLoad@PAGE ; "com.kujiang.loadQueue"
__text:00000001002BF6F4                 ADD             X0, X0, #aComKujiangLoad@PAGEOFF ; "com.kujiang.loadQueue"
__text:00000001002BF6F8                 MOV             X1, #0  ; attr
__text:00000001002BF6FC                 BL              _dispatch_queue_create
__text:00000001002BF700                 MOV             X19, X0
__text:00000001002BF704                 ADRP            X8, #__NSConcreteStackBlock_ptr@PAGE
__text:00000001002BF708                 LDR             X8, [X8,#__NSConcreteStackBlock_ptr@PAGEOFF]
__text:00000001002BF70C                 STUR            X8, [X29,#var_80]
__text:00000001002BF710                 ADRP            X8, #qword_100BE3268@PAGE
__text:00000001002BF714                 LDR             D0, [X8,#qword_100BE3268@PAGEOFF]
__text:00000001002BF718                 STUR            D0, [X29,#var_78]
__text:00000001002BF71C                 ADR             X8, sub_1002BF908
__text:00000001002BF720                 NOP
__text:00000001002BF724                 STUR            X8, [X29,#var_70]
__text:00000001002BF728                 ADRP            X8, #unk_100DC0718@PAGE
__text:00000001002BF72C                 ADD             X8, X8, #unk_100DC0718@PAGEOFF
__text:00000001002BF730                 STUR            X8, [X29,#var_68]
__text:00000001002BF734                 MOV             X0, X20
__text:00000001002BF738                 BL              _objc_retain
__text:00000001002BF73C                 MOV             X21, X0
__text:00000001002BF740                 STUR            X21, [X29,#var_60]
__text:00000001002BF744                 MOV             X0, X28
__text:00000001002BF748                 BL              _objc_retain
__text:00000001002BF74C                 STUR            X0, [X29,#var_58]
__text:00000001002BF750                 SUB             X1, X29, #-var_80
__text:00000001002BF754                 MOV             X0, X19
__text:00000001002BF758                 BL              _dispatch_async

从以上汇编代码可以看出 dispatch_asynce(x0,x1);

  • x0x19 也就是上面的返回值,即 dispatch_queue_t *queue
  • x1x29block

所以重点分析在block,其中 sub_1002BF908 跳转到 block 的内部实现。

struct block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables.*/变量入口,第一个参数入口
};

下面让我进入 sub_1002BF908 看看汇编实现:

__text:00000001002BF908                 STP             X22, X21, [SP,#-0x10+var_20]!
__text:00000001002BF90C                 STP             X20, X19, [SP,#0x20+var_10]
__text:00000001002BF910                 STP             X29, X30, [SP,#0x20+var_s0]
__text:00000001002BF914                 ADD             X29, SP, #0x20
__text:00000001002BF918                 MOV             X19, X0
__text:00000001002BF91C                 ADRP            X8, #classRef_FastCoder@PAGE
__text:00000001002BF920                 LDR             X0, [X8,#classRef_FastCoder@PAGEOFF] ; void *
__text:00000001002BF924                 LDR             X2, [X19,#0x20]
__text:00000001002BF928                 ADRP            X8, #selRef_dataWithRootObject_@PAGE
__text:00000001002BF92C                 LDR             X1, [X8,#selRef_dataWithRootObject_@PAGEOFF] ; char *
__text:00000001002BF930                 BL              _objc_msgSend
__text:00000001002BF934                 MOV             X29, X29
__text:00000001002BF938                 BL              _objc_retainAutoreleasedReturnValue
__text:00000001002BF93C                 MOV             X20, X0
__text:00000001002BF940                 ADRP            X8, #selRef_gzippedData@PAGE
__text:00000001002BF944                 LDR             X1, [X8,#selRef_gzippedData@PAGEOFF] ; char *
__text:00000001002BF948                 BL              _objc_msgSend
__text:00000001002BF94C                 MOV             X29, X29
__text:00000001002BF950                 BL              _objc_retainAutoreleasedReturnValue
__text:00000001002BF954                 MOV             X21, X0
__text:00000001002BF958                 LDR             X2, [X19,#0x28]
__text:00000001002BF95C                 ADRP            X8, #selRef_writeToFile_atomically_@PAGE
__text:00000001002BF960                 LDR             X1, [X8,#selRef_writeToFile_atomically_@PAGEOFF] ; char *
__text:00000001002BF964                 MOV             W3, #1
__text:00000001002BF968                 BL              _objc_msgSend
__text:00000001002BF96C                 MOV             X0, X21
__text:00000001002BF970                 BL              _objc_release
__text:00000001002BF974                 MOV             X0, X20
__text:00000001002BF978                 LDP             X29, X30, [SP,#0x20+var_s0]
__text:00000001002BF97C                 LDP             X20, X19, [SP,#0x20+var_10]
__text:00000001002BF980                 LDP             X22, X21, [SP+0x20+var_20],#0x30
__text:00000001002BF984                 B               _objc_release

上面的汇编是 block 的内部实现逻辑,以下分析以上汇编实现:

// Block 引用的第一个外部参数,具体参数要看调用 Block 处的上下文
id x2 = [X19,#0x20];
id data = [FastCoder dataWithRootObject:x2];

id gzippedData = [data gzippedData];

// Block 引用的第二个外部参数
NSString *filePath = [X19,#0x28];
[gzippedData writeToFile:filePath atomically:YES];

分析: x19 来自 x0,方法入口时候的 x0 指的是调用这个函数的第一个参数,这里是 block 结构体地址,两次取 x19,[X19,#0x20][X19,#0x28],分别是取引用 block 引用的第一个,第二个外部参数。

下面是往 block 内部赋值的汇编和分析:

_text:00000001002BF704                 ADRP            X8, #__NSConcreteStackBlock_ptr@PAGE
__text:00000001002BF708                 LDR             X8, [X8,#__NSConcreteStackBlock_ptr@PAGEOFF]
__text:00000001002BF70C                 STUR            X8, [X29,#var_80]
__text:00000001002BF710                 ADRP            X8, #qword_100BE3268@PAGE
__text:00000001002BF714                 LDR             D0, [X8,#qword_100BE3268@PAGEOFF]
__text:00000001002BF718                 STUR            D0, [X29,#var_78]
__text:00000001002BF71C                 ADR             X8, sub_1002BF908
__text:00000001002BF720                 NOP
__text:00000001002BF724                 STUR            X8, [X29,#var_70]
__text:00000001002BF728                 ADRP            X8, #unk_100DC0718@PAGE
__text:00000001002BF72C                 ADD             X8, X8, #unk_100DC0718@PAGEOFF
__text:00000001002BF730                 STUR            X8, [X29,#var_68]
__text:00000001002BF734                 MOV             X0, X20
__text:00000001002BF738                 BL              _objc_retain
__text:00000001002BF73C                 MOV             X21, X0
__text:00000001002BF740                 STUR            X21, [X29,#var_60]
__text:00000001002BF744                 MOV             X0, X28
__text:00000001002BF748                 BL              _objc_retain
__text:00000001002BF74C                 STUR            X0, [X29,#var_58]
__text:00000001002BF750                 SUB             X1, X29, #-var_80
__text:00000001002BF754                 MOV             X0, X19
__text:00000001002BF758                 BL              _dispatch_async

这里是构造 blockblock 首地址是 var_80,所以加 0x20 就是 var_60,所以只要看往 var_60 怎么赋值,就能找到第一个引用参数,以此类推。

_text:00000001002BF704                 ADRP            X8, #__NSConcreteStackBlock_ptr@PAGE
__text:00000001002BF708                 LDR             X8, [X8,#__NSConcreteStackBlock_ptr@PAGEOFF]
__text:00000001002BF70C                 STUR            X8, [X29,#var_80]

var_60 的值来自 x21x21 来自 x0,x0 来自 x20,所以 x20 就是第一个外部参数.然后网上找,找到赋值处。

__text:00000001002BF734                 MOV             X0, X20
__text:00000001002BF738                 BL              _objc_retain
__text:00000001002BF73C                 MOV             X21, X0
__text:00000001002BF740                 STUR            X21, [X29,#var_60]

block 的首地址是 var_80,对应的地址是 __text:00000001002BF70C ,也就是 x19 的地址,所以 [X19,#0x28] 对应的地址应该是 var_58,然后 分析 x0,x0来自 x28,也就是 [X19,#0x28] 中的 0x28,然后选中 x28,看下 mov 赋值:

__text:00000001002BF744                 MOV             X0, X28
__text:00000001002BF748                 BL              _objc_retain
__text:00000001002BF74C                 STUR            X0, [X29,#var_58]

image.png

这两处,就是第1、2个参数,点中这里的 X20X28寄存器, 看看上面是哪里给这两个寄存器赋值的;

======================================================

Block 结构

struct Block_literal_1 {
    void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor_1 {
    unsigned long int reserved;         // NULL
        unsigned long int size;         // sizeof(struct Block_literal_1)
        // optional helper functions
        void (*copy_helper)(void *dst, void *src);     // IFF (1<<25)
        void (*dispose_helper)(void *src);             // IFF (1<<25)
        // required ABI.2010.3.16
        const char *signature;                         // IFF (1<<30)
    } *descriptor;
    // imported variables
};

名词解释:

  • isa : 指针,代表 Block 的类型,也说明了 Block 是一个对象
  • flags : 标志位,表示 Block 的附加信息,实现 Block 的相关代码会用到
  • reserved : 保留的一个变量
  • void (*invoke)(void *, ...) : 函数指针,指向的是 block 具体的实现地址!
  • Block_descriptor_1 : 表示 block 的一些描述信息:如 copydispose 方法,Block 函数签名的字符串
    • reserved : 保留字段
    • size : block 的大小
    • copy_helperdispose_helper__block 修饰外部变量使用,基本上配合使用
    • signature :Block 函数签名的字符串
  • imported variables : 这里就是捕获过来的外部变量,Block 内能够访问外部的变量,就是因为这个参数的存在,也是变量的入口

并不是每一个 Block 都有 const char *signature Block 函数签名的字符串的

flags

enum {
    // Set to true on blocks that have captures (and thus are not true
    // global blocks) but are known not to escape for various other
    // reasons. For backward compatiblity with old runtimes, whenever
    // BLOCK_IS_NOESCAPE is set, BLOCK_IS_GLOBAL is set too. Copying a
    // non-escaping block returns the original block and releasing such a
    // block is a no-op, which is exactly how global blocks are handled.
    BLOCK_IS_NOESCAPE      =  (1 << 23),

    BLOCK_HAS_COPY_DISPOSE =  (1 << 25),
    BLOCK_HAS_CTOR =          (1 << 26), // helpers have C++ code
    BLOCK_IS_GLOBAL =         (1 << 28),
    BLOCK_HAS_STRET =         (1 << 29), // IFF BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE =     (1 << 30),
};

在10.6.ABI 中,(1 « 29) 通常被设置并且总是在运行的时候被忽略的,它只是一个过渡的标记,在转换(过渡)之后没有被删除。此位现在于(1 « 30)配对使用,并表示为对(3«30),用于以下有效位设置组合及其含义:

switch (flags & (3<<29)) {
  case (0<<29):      10.6.ABI, no signature field available
  case (1<<29):      10.6.ABI, no signature field available
  case (2<<29): ABI.2010.3.16, regular calling convention, presence of signature field
  case (3<<29): ABI.2010.3.16, stret calling convention, presence of signature field,
}

10.6.ABI 的讨论

Block 可能出现在本地堆栈内存中创建结构的函数中,可以作为全局或者静态局部变量的 Block 变量在初始化表达式中出现。 当分析 Block 表达式的时候,Block 的堆栈结构初始化如下:

  • static 修饰的 Block descriptor 的声明和初始化如下:
    • invoke 函数指针设置成一个函数,该函数将作为 Block 的第一个参数,将其余参数(如果有)作为 block 的其他参数,并一起执行.<有异议,并没有看到这个,可能我的机器不是32位>
    • size 字段设置为 Block 结构的大小(size)
    • 如果 Block 需要 copy_helperdispose_helper 函数指针,可以将他们设置为各种的辅助函数.
  • 创建一个堆栈(全局)Block 数据结构,并初始化如下:
    • isa 字段设置为 _NSConcreteStackBlock 的地址,那么它是在 libSystem 中没有初始化的 Block,如果是一个静态或者文件级别的 Block,那么 isa 的地址为 _NSConcreteGlobalBlock
    • flags 字段被设置为 0,除非有变量被导入到 Block,这些变量需要帮助程序进行 Block_copy()Block_release() 操作,在这种情况下,设置 (1<<25) 标志位(flags).

例子:

^ { printf("hello world\n"); }

64 位机器转换如下

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;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
 printf("hello world\n"); }

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)};

参考资料

Block-ABI-Apple

PREVIOUSCharles出现Unknown的一种解决方法
NEXT获取Block的参数和返回值