获取Block的参数和返回值
这里以某音乐的 -[NetStatusManager showAlertViewWhileWifiOnlyWithCloseBlock:] 函数为例子。
找到 stackBlock 对象
(lldb) x/s $x1
0x1043ddc93: "showAlertViewWhileWifiOnlyWithCloseBlock:"
(lldb) po $x2
<__NSStackBlock__: 0x16fdf6668>
(lldb)
0x16fdf6668 是 block 函数的首地址。
找出 block 的函数体地址(函数实现)
根据以上的内存模型,只要找到 invoke 的这个函数指针的地址,它指向的是这个 Block 的实现。
在 64 位系统上,指针类型是 8 字节,int 是 4 字节。
| Member | Size |
|---|---|
| void *isa | 8 bytes |
| int flags | 4 bytes |
| int reserved | 4 bytes |
| void (*invoke)(void *, …) | 8 bytes |
因此 invoke 函数指针的地址就是第 16 个字节之后,通过 lldb 的 memory 命令来打印出指定地址的内存。我们已经得到了 Block 的地址,现在打印它的内存:
(lldb) memory read --size 8 --format x 0x16fdf6668
0x16fdf6668: 0x000000019c3f9088 0x00000000c2000000
0x16fdf6678: 0x0000000102cb0f50 0x00000001034e5578
0x16fdf6688: 0x000000017015c250 0x0000000125cf3400
0x16fdf6698: 0x0000000125cf3400 0x0000000000000000
(lldb)
所以函数体地址为:0x0000000102cb0f50
有了函数体地址,对这个地址进行反汇编:
(lldb) disassemble --start-address 0x0000000102cb0f50
QQMusic`SUPERSOUND2::SS2EffectT<SUPERSOUND2::DFXBASE::DfxAmbience, SUPERSOUND2::DFXBASE::DfxAmbience::PARAM>::deleter:
0x102cb0f50 <+20916396>: stp x20, x19, [sp, #-0x20]!
0x102cb0f54 <+20916400>: stp x29, x30, [sp, #0x10]
0x102cb0f58 <+20916404>: add x29, sp, #0x10 ; =0x10
0x102cb0f5c <+20916408>: mov x19, x0
0x102cb0f60 <+20916412>: add x0, x19, #0x28 ; =0x28
0x102cb0f64 <+20916416>: bl 0x1030b77b0 ; symbol stub for: objc_loadWeakRetained
0x102cb0f68 <+20916420>: mov x20, x0
0x102cb0f6c <+20916424>: ldr x2, [x19, #0x20]
(lldb)
也可以直接在 lldb 中下断点
(lldb) br set -a 0x0000000102cb0f50
Breakpoint 3: where = QQMusic`SUPERSOUND2::SS2EffectT<SUPERSOUND2::DFXBASE::DfxAmbience, SUPERSOUND2::DFXBASE::DfxAmbience::PARAM>::deleter(void*) + 20916396, address = 0x0000000102cb0f50
(lldb)
再次运行函数,就可以进到回调 Block 函数内部了。
block 的函数签名
要找出 Block 的函数签名,需要通过 Block_descriptor 结构体中的 signature 成员,然后通过它得到一个 NSMethodSignature 对象。
首先要找到 Blick_descriptor 结构体,这个结构体的位置在 invoke 成员的后面,占用8个字节。可以从上面打印的 Block 内存地址找到 0x00000001034e5578。
下面,就可以通过 Blick_descriptor 找到 signature 了。由于并不是每个 Block 都有函数签名的,所以需要通过 flags 与 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),
};
再次使用 memory 命令打印出 flags 的值:
(lldb) memory read --size 4 --format x 0x16fdf6668
0x16fdf6668: 0x9c3f9088 0x00000001 0xc2000000 0x00000000
0x16fdf6678: 0x02cb0f50 0x00000001 0x034e5578 0x00000001
(lldb)
0xc2000000 就是 flags 的值,由于 ((0xc2000000) & (1<<30) != 0),因此可以确定这个 Block 是有签名的。虽然不是每个 Block 都有函数签名,但是我们可以在 Clang 源码中 CodeGenFunction::EmitBlockLiteral 与 buildGlobalBlock 方法,可以看到每个 Block 的 flags 成员都是被默认设置了 BLOCK_HAS_SIGNATURE。因此,我们可以推断,所有使用 Clang 编译的代码中的 Block 都是有签名的。
找出 signature 的地址,还需要确认这个 Block 是否拥有 copy_helper 和 disponse_helper 这两个可选的函数指针。由于 ((0xc2000000 & (1 << 25)) != 0) ,因此可以确认这个 Block 有这个两个函数指针。
signature 的地址是在 descriptor 下偏移两个 unsigned long int 和 2 个指针后的地址,即 32 个字节,long 在 64 位是占 8 个字节,下面找出 signature 的地址并且打印出它的字符串内容:
(lldb) memory read --size 8 --format x 0x00000001034e5578
0x1034e5578: 0x0000000000000000 0x0000000000000030
0x1034e5588: 0x0000000102cb0f8c 0x0000000102cb0fb8
0x1034e5598: 0x00000001042416f9 0x0000000000000101
0x1034e55a8: 0x0000000000000000 0x0000000000000028
(lldb) po (char *)0x00000001042416f9
"v8@?0"
(lldb)
"v8@?0" 是函数签名,需要通过 NSMethodSignature 找出它的参数类型:
(lldb) po [NSMethodSignature signatureWithObjCTypes:"v8@?0"]
<NSMethodSignature: 0x170a72fc0>
number of arguments = 1
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}
(lldb)
以上中的 type encoding 字段,就是对应的 Block 的参数符号,返回值 v 即 void,说明该 Block 没有返回值,Block 接收1个参数,第一个是 block (就是当前 Block 的引用即 ^ ),由于没有第二个参数,所以函数还原如下:
-[NetStatusManager showAlertViewWhileWifiOnlyWithCloseBlock:(void(^)(void))]