原博客: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;