获取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))]