首页 > 技术文章 > ios Block参数和返回值类型获取

HugJun 2021-10-14 14:14 原文

原博客:http://www.swiftyper.com/2016/12/16/debuging-objective-c-blocks-in-lldb/
注:本篇文章都是在 64 位系统下进行分析,如果是 32 位系统,整型与指针类型的大小都是与 64 位不一致的,请自行进行修改。

Block 的内存结构

在 LLVM 文档中,可以看到 Block 的实现规范,其中最关键的地方是对于 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 在 Objective-C 当中也是一个对象。我们重点要关注的就是 void (*invode)(void *, ...); 和 descriptor 中的 const char *signature,前者指向了 Block 具体实现的地址,后者是表示 Block 函数签名的字符串。

 

 通过分析界面以及 class-dump 出来头文件就能分析出某个函数的参数为block类型,主要对block中返回值和入参进行分析,方便后期的hook,例:

- (void)requestCorpse:(CDUnknownBlockType)arg1;

   

 通过hook 该函数debug可以看到block函数的具体地址

 

在 64 位系统上,指针类型的大小是 8 个字节,而 int 是 4 个字节,long int 8个字节

因此,invoke 函数指针的地址就是在第 16 个字节之后。我们可以通过 lldb 的 memory 命令来打印出指定地址的内存,我们上面已经得到了 block 的地址,现在就打印出它的内存内容:

(lldb) memory read --size 8 --format x 0x16db93bb8
0x16db93bb8: 0x00000001f81c4870 0x00000000c0000000
0x16db93bc8: 0x000000010497cc34 0x0000000109f49750
0x16db93bd8: 0x4277c7d786884399 0x0000000000000000
0x16db93be8: 0x40ea4cd7c8a9a6a0 0x0000000281eecc60

如前所述,函数指针的地址是在第 16 个字节之后,并占用 8 个字节,所以可以得到函数的地址是0x000000010497cc34

void *isa; 指针8个字节:0x00000001f81c4870
int flags; int reserved; int4个字节,2个int8个:0x00000000c0000000
(*invoke)(void *, ...); 指针8个所以该地址:0x000000010497cc34
struct Block_descriptor_1{...}*descriptor 0x0000000109f49750

 

找出 Block 的函数签名

         要找出 Block 的函数签名,需要通过 descriptor 结构体中的 signature 成员,然后通过它得到一个 NSMethodSignature 对象。

首先,需要找到 descriptor 结构体。这个结构体在 Block 中是通过指针持有的,它的位置正好在 invoke 成员后面,占用 8 个字节。可以从上面的内存打印中看到 descriptor 指针的地址是 0x0000000109f49750

找到地址,并打印出它的字符串内容:       ios中第9位大部分时候为1,第8位为0,其他的容易超出内存

(lldb) memory read --size 8 --format x 0x0000000109f49750
0x109f49750: 0x0000000000000000 0x0000000000000028
0x109f49760: 0x000000010ce7a034 0x000000010d9cad29
0x109f49770: 0x00000001f81c49b0 0x0000000050000000
0x109f49780: 0x00000001022d1498 0x0000000109eee450
(lldb) p (char *)0x000000010ce7a034 //一般来说为第二行第一个,或第三行第一个,具体可看下文扩展知识
(char *) $2 = 0x000000010ce7a034 "v16@?0@\"NSDictionary\"8"

拿到函数签名后,只是我们需要通过 NSMethodSignature 找出它的参数和返回类型:

(lldb) po [NSMethodSignature signatureWithObjCTypes:"v16@?0@\"NSDictionary\"8"]
<NSMethodSignature: 0x8ce27f0dea645e47>
    number of arguments = 2
    frame size = 224
    is special struct return? NO
    return value: -------- -------- -------- --------
        type encoding (v) 'v'
        flags {}
        modifiers {}
        frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
        memory {offset = 0, size = 0}
    argument 0: -------- -------- -------- --------
        type encoding (@) '@?'
        flags {isObject, isBlock}
        modifiers {}
        frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
        memory {offset = 0, size = 8}
    argument 1: -------- -------- -------- --------
        type encoding (@) '@"NSDictionary"'
        flags {isObject}
        modifiers {}
        frame {offset = 8, offset adjust = 0, size = 8, size adjust = 0}
        memory {offset = 0, size = 8}
            class 'NSDictionary'

注意,字符串中的双引号需要对其进行转义。

 对我们最有用的 type encoding 字段,这些符号对应的解释可以参考 Type Encoding 官方文档

 

所以,总结来讲就是:这个方法没有返回值 void类型,它接受2个参数,第一个是 block (即我们自己的 block 的引用)"@?" 代表本身所以hook中不需要写,第二个是一个 NSDictionary 对象。

callBack:(void (^)(NSDictionary *)arg1;

 

推荐阅读