获取Block的参数和返回值

这里以某音乐的 -[NetStatusManager showAlertViewWhileWifiOnlyWithCloseBlock:] 函数为例子。

找到 stackBlock 对象

(lldb) x/s $x1
0x1043ddc93: "showAlertViewWhileWifiOnlyWithCloseBlock:"
(lldb) po $x2
<__NSStackBlock__: 0x16fdf6668>

(lldb) 

0x16fdf6668block 函数的首地址。

找出 block 的函数体地址(函数实现)

根据以上的内存模型,只要找到 invoke 的这个函数指针的地址,它指向的是这个 Block 的实现。 在 64 位系统上,指针类型是 8 字节,int4 字节。

Member Size
void *isa 8 bytes
int flags 4 bytes
int reserved 4 bytes
void (*invoke)(void *, …) 8 bytes

因此 invoke 函数指针的地址就是第 16 个字节之后,通过 lldbmemory 命令来打印出指定地址的内存。我们已经得到了 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::EmitBlockLiteralbuildGlobalBlock 方法,可以看到每个 Blockflags 成员都是被默认设置了 BLOCK_HAS_SIGNATURE。因此,我们可以推断,所有使用 Clang 编译的代码中的 Block 都是有签名的。 找出 signature 的地址,还需要确认这个 Block 是否拥有 copy_helperdisponse_helper 这两个可选的函数指针。由于 ((0xc2000000 & (1 << 25)) != 0) ,因此可以确认这个 Block 有这个两个函数指针。 signature 的地址是在 descriptor 下偏移两个 unsigned long int2 个指针后的地址,即 32 个字节,long64 位是占 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 的参数符号,返回值 vvoid,说明该 Block 没有返回值,Block 接收1个参数,第一个是 block (就是当前 Block 的引用即 ^ ),由于没有第二个参数,所以函数还原如下: -[NetStatusManager showAlertViewWhileWifiOnlyWithCloseBlock:(void(^)(void))]

参考资料

PREVIOUS反汇编 Block 的内部逻辑实现
NEXTLLDB手动砸壳