作者:菜蕻的薇笑2602929033 | 来源:互联网 | 2023-10-11 12:42
原博客:http:www.swiftyper.com20161216debuging-objective-c-blocks-in-lldb注:本篇文章都是在64位系统下进行分析,如
原博客: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"]
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;
本文章仅供学习参考,如有版权侵犯,请联系作者修改,转载请注明出处!