OC的反汇编

案例1:

OC的方法调用

打开Person.h,写入以下代码:

```

import

@interface Person : NSObject

@property(nonatomic,copy)NSString *name;

@property(nonatomic,assign)int age;

+(instancetype)person;

@end

  1. > 打开`Person.m`,写入以下代码:
  2. >

import “Person.h”

@implementation Person

+(instancetype)person{ return [[Person alloc] init]; }

@end

  1. > 打开`ViewController.m`,写入以下代码:
  2. >

import “ViewController.h”

import “Person.h”

@implementation ViewController

  • (void)viewDidLoad { // [super viewDidLoad]; Person *p = [Person person]; }

@end

  1. > 真机运行项目,来到`viewDidLoad`方法<br />
  2. ![](https://upload-images.jianshu.io/upload_images/9297953-26dfe9a383b25a84.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  3. > - `objc_msgSend`函数有两个默认参数,`id`类型的`self``SEL`类型的`_cmd`
  4. > - 两个参数分别对应`x0``x1`
  5. >
  6. ---
  7. > 查看`x0`寄存器
  8. >

0x1046de46c <+20>: adrp x8, 2 0x1046de470 <+24>: add x8, x8, #0xec8 ; =0xec8 0x1046de474 <+28>: ldr x0, [x8]

  1. > - 使用`adrp + add`指令,计算地址为`0x1046e0ec8`,赋值`x8`
  2. > - `x8`进行寻址,写入`x0`
  3. > - `x0`对应`id`类型参数,`id`类型本质上是结构体指针,占`8字节`
  4. > 查看内存中的数据
  5. >

x 0x1046e0ec8

0x1046e0ec8: 38 0f 6e 04 01 00 00 00 b0 0f 6e 04 01 00 00 00 8.n…….n….. 0x1046e0ed8: 08 00 00 00 10 00 00 00 10 00 00 00 00 00 00 00 …………….

  1. > - 读取`8字节`数据,小端模式,从右往左读取`0x01046e0f38`
  2. > 打印`x0`
  3. >

po 0x01046e0f38

Person

  1. > - 打印的`Person`是类对象
  2. >
  3. ---
  4. > 查看`x1`寄存器
  5. >

0x1046de478 <+32>: adrp x8, 2 0x1046de47c <+36>: add x8, x8, #0xeb0 ; =0xeb0 0x1046de480 <+40>: ldr x1, [x8]

  1. > - 使用`adrp + add`指令,计算地址为`0x1046e0eb0`,赋值`x8`
  2. > - `x8`进行寻址,写入`x1`
  3. > - `x1`对应`SEL`类型参数,占`8字节`
  4. > 查看内存中的数据
  5. >

x 0x1046e0eb0

0x1046e0eb0: 10 c8 88 e2 01 00 00 00 18 f6 15 e3 01 00 00 00 ……………. 0x1046e0ec0: 98 88 fd e2 01 00 00 00 38 0f 6e 04 01 00 00 00 ……..8.n…..

  1. > - 读取`8字节`数据,小端模式,从右往左读取`0x01e288c810`
  2. > 打印`x1`
  3. >

po (SEL)0x01e288c810

“person”

  1. > - 打印的`"person"`是方法名称
  2. >
  3. ---
  4. > 当汇编代码中出现`objc_msgSend`,表示调用了一个`OC`方法,读取`x0``x1`便可知道调用了哪个对象的哪个方法
  5. > 案例2
  6. > `alloc``init`函数
  7. > 使用`iOS12.2`系统调试,来到`Person`对象的`person`方法<br />
  8. ![](https://upload-images.jianshu.io/upload_images/9297953-a3f6c249c2bf16cd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  9. > - 调用`objc_alloc_init`函数
  10. > `iOS12.2`开始,系统优化`alloc``init`函数,不再使用消息发送(`objc_msgSend`),直接调用优化后的`objc_alloc_init`函数
  11. >
  12. ---
  13. > 使用`iOS12.1`系统调试<br />
  14. ![](https://upload-images.jianshu.io/upload_images/9297953-48140aadcc2ad847.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  15. > - 分别调用`objc_alloc`函数和`objc_msgSend`函数
  16. > 调用`objc_msgSend`函数,实际上就是在调用`init`函数<br />
  17. ![](https://upload-images.jianshu.io/upload_images/9297953-a8d38de1aae0bc69.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  18. > - 调用`objc_msgSend`函数,但这里没有对`x0`进行赋值。这说明在上一个调用函数`objc_alloc`中返回的`x0`,正是`objc_msgSend`函数将要使用的参数
  19. > `iOS10.0`开始,系统优化`alloc`函数,直接调用优化后的`objc_alloc`函数。但对于`init`函数,依然使用`objc_msgSend`进行消息发送
  20. >
  21. ---
  22. > 使用`iOS9.1`系统调试
  23. > `alloc``init`函数,调用两次`objc_msgSend`进行消息发送
  24. > 案例3
  25. > `objc_storeStrong`函数
  26. > `Person`初始化完毕,`x0`返回一个实例对象。之后会调用`objc_storeStrong`函数<br />
  27. ![](https://upload-images.jianshu.io/upload_images/9297953-e5c7a658587d3660.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  28. > - `OC`中,使用`strong`修饰的对象,都会调用此函数
  29. > - `viewDidLoad`方法中,定义的局部变量`p`,就是一个强引用
  30. > - 调用`objc_storeStrong`函数,并不一定会让对象的引用计数`+1`,也可能调用后直接销毁。例如:局部变量`p`,它没有被外部代码使用,出栈后就会销毁
  31. >
  32. ---
  33. > 来到`objc`源码
  34. > 打开`NSObject.mm`文件,找到`objc_storeStrong`函数的实现
  35. >

void objc_storeStrong(id location, id obj) { id prev = location; if (obj == prev) { return; } objc_retain(obj); *location = obj; objc_release(prev); }

  1. > - 参数`location`,二级指针,指向对象指针的指针
  2. > - 参数`obj`,对象指针
  3. > - 局部变量`prev`,将`location`指向的对象指针赋值给`prev`
  4. > - 判断对象相等,直接`return`
  5. > - 如果不等,当前对象`obj`引用计数`+1`
  6. > - `location`指向`obj`
  7. > - `location`指向的老对象释放
  8. > `objc_storeStrong`函数的目的,对一个`strong`修饰的对象`retain`,对老对象`release`,等价于以下代码:
  9. >

Person *p = p1; p = p2;

  1. > - `p2`赋值给`p``p2`引用计数`+1``p1`释放
  2. >
  3. ---
  4. > 回到汇编代码
  5. > 查看`location`参数
  6. >

0x10058a3ac <+56>: add x8, sp, #0x8 ; =0x8 0x10058a3b0 <+60>: str x0, [sp, #0x8] 0x10058a3b4 <+64>: mov x0, x8

  1. > - `sp + #0x8`地址写入`x8`
  2. > - `x0`,即:局部变量`p`,入栈到`sp + #0x8`
  3. > - `x8`写入`x0`,此时`x0`是指向局部变量`p`的指针地址
  4. > 查看`obj`参数
  5. >

0x10058a3b8 <+68>: mov x8, #0x0 0x10058a3bc <+72>: mov x1, x8

  1. > - `#0x0`,即:`nil`,写入`x8`
  2. > - `x8`写入`x1`,此时`x1``nil`
  3. > 此刻调用`objc_storeStrong`函数触发的逻辑:
  4. > - `prev`被赋值为局部变量`p`
  5. > - 判断对象不相等
  6. > - 调用`objc_retain`函数,传入`nil`
  7. > - `location`指向`nil`
  8. > - 调用`objc_release`函数,传入局部变量`p`,将其释放
  9. #####工具反汇编
  10. > 真实场景下,对`Mach-O`进行分析,只能使用静态分析,无法使用`lldb`动态调试。所以要学会运用工具进行反汇编
  11. > 案例1
  12. > 打开`ViewController.m`,写入以下代码:
  13. >

import “ViewController.h”

import “Person.h”

@implementation ViewController

  • (void)viewDidLoad { // [super viewDidLoad]; Person *p = [Person person]; p.name = @”Zang”; p.age = 18; }

@end

  1. > 使用真机编译项目,将`Mach-O`文件拖到`Hopper`中,找到`viewDidLoad`函数<br />
  2. ![](https://upload-images.jianshu.io/upload_images/9297953-b419bdd27b392758.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  3. > - `Hopper`可以解析出方法、属性等名称,作为注释标记给开发者
  4. > - 对于`bl`指令,也将跳转地址解析为对应的函数名称
  5. > 案例2
  6. > `Mach-O`中,找到`Hopper`转义出的注释
  7. > 找到`objc_cls_ref_Person``Mach-O`中的位置
  8. >

0000000100006350 adrp x8, #0x10000c000 0000000100006354 add x8, x8, #0xd58 ; >objc_cls_ref_Person

  1. > - 使用`adrp + add`指令,计算地址为`0x10000cd58`,赋值`x8`
  2. > 打开`Mach-O`,找到`0x10000cd58`<br />
  3. ![](https://upload-images.jianshu.io/upload_images/9297953-be0cfb602e6e2575.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  4. >
  5. ---
  6. > 找到`person``selector`,在`Mach-O`中的位置
  7. >

000000010000635c adrp x8, #0x10000c000 0000000100006360 add x8, x8, #0xd40 ; @selector(person)

  1. > - 使用`adrp + add`指令,计算地址为`0x10000cd40`,赋值`x8`
  2. > 打开`Mach-O`,找到`0x10000cd40`<br />
  3. ![](https://upload-images.jianshu.io/upload_images/9297953-eec99498ed025ca7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  4. >
  5. ---
  6. > 找到`person`等字符串,在`Mach-O`中的位置<br />
  7. ![](https://upload-images.jianshu.io/upload_images/9297953-83b89873d8a6d6cf.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  8. > 打开`Mach-O`,找到`0x100006a2c`,存储了方法名称的字符串<br />
  9. ![](https://upload-images.jianshu.io/upload_images/9297953-29d810a21a3b7716.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  10. >
  11. ---
  12. > 找到`imp___stubs__objc_msgSend``Mach-O`中的位置
  13. >

0000000100006368 bl imp_stubsobjc_msgSend

  1. > `Hopper``viewDidLoad`方法中,双击`imp___stubs__objc_msgSend`
  2. >
  1. imp___stubs__objc_msgSend:

000000010000685c nop ; CODE XREF=-[ViewController viewDidLoad]+44, -[ViewController viewDidLoad]+92, -[ViewController viewDidLoad]+116 0000000100006860 ldr x16, #0x10000c028 0000000100006864 br x16 ; endp

  1. > 打开`Mach-O`,找到`0x10000685c`,存储的正是`_objc_msgSend`函数<br />
  2. ![](https://upload-images.jianshu.io/upload_images/9297953-91e61dc9cad96674.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  3. >
  4. ---
  5. > 找到`Zang`字符串,在`Mach-O`中的位置
  6. >

000000010000638c adrp x2, #0x100008000 0000000100006390 add x2, x2, #0x8 ; @”Zang”

  1. > - 使用`adrp + add`指令,计算地址为`0x100008008`,赋值`x8`
  2. > `Hopper``viewDidLoad`方法中,双击`Zang`
  3. >
  1. ; Section __cfstring
  2. ; Range: [0x100008008; 0x100008028[ (32 bytes)
  3. ; File offset : [32776; 32808[ (32 bytes)
  4. ; S_REGULAR
  5. cfstring_Zang:

0000000100008008 dq ___CFConstantStringClassReference, 0x7c8, 0x10000755d, 0x4 ; “Zang”, DATA XREF=-[ViewController viewDidLoad]+84

  1. > 打开`Mach-O`,找到`0x100008008`,存储字符串常量`Zang`的相关信息<br />
  2. ![](https://upload-images.jianshu.io/upload_images/9297953-7c949e4375719af5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  3. > 案例3
  4. > 使用`Hopper`查看代码的流程图
  5. > 打开`ViewController.m`,写入以下代码:
  6. >

import “ViewController.h”

@implementation ViewController

void test1(bool b){ if(b){ test2(b); } else{ test3(); } }

void test2(bool b){ if(b){ test3(); } else{ test4(); } }

void test3(){ NSLog(@”test3”); } void test4(){ NSLog(@”test4”); }

  • (void)viewDidLoad { // [super viewDidLoad];

    int a = 1; int b = 2;

    if(a == b){

    1. test1(YES);

    } else{

    1. test2(NO);

    } }

@end

  1. > 使用真机编译项目,将`Mach-O`文件拖到`Hopper`中,找到`viewDidLoad`函数<br />
  2. ![](https://upload-images.jianshu.io/upload_images/9297953-d49a8e2943e84d07.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  3. > `Hopper`中,点击`CFG mode`按钮<br />
  4. ![](https://upload-images.jianshu.io/upload_images/9297953-1ce847e25e12c9e4.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  5. > 切换到`viewDidLoad`函数的流程图,清晰的展示出不同的代码分支<br />
  6. ![](https://upload-images.jianshu.io/upload_images/9297953-c3c25f926fad6d1d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  7. > 双击`test1`函数,切换到`test1`函数中的代码分支<br />
  8. ![](https://upload-images.jianshu.io/upload_images/9297953-6e84f577478ec33d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  9. > 还原高级代码时,遇到复杂的逻辑分支,可以使用代码的流程图进行分析
  10. > 案例4
  11. > 使用`Hopper`查看伪码模式
  12. > `Hopper`中,点击`Pseudo-code mode`按钮<br />
  13. ![](https://upload-images.jianshu.io/upload_images/9297953-52d2acf259e5efda.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  14. > 查看`Hopper`帮我们还原的`viewDidLoad`函数的伪码
  15. >

int -ViewController viewDidLoad { r31 = r31 - 0x30; (r31 + 0x20) = r29; (r31 + 0x28) = r30; (r31 + 0x18) = r0; (r31 + 0x10) = r1; (r31 + 0xc) = zero_extend_64(0x1); (r31 + 0x8) = zero_extend_64(0x2); if ((r31 + 0xc) == (r31 + 0x8)) { r0 = _test1(zero_extend_64(0x1) & 0x1); } else { r0 = _test2(zero_extend_64(0x0) & 0x1); } return r0; }

  1. #####Block的反汇编
  2. > 日常开发中,`Block`有时作为参数传递,有时作为返回值,但大多数情况下,`Block`会作为方法的参数回调
  3. > 在逆向分析中,需要定位`Block``invoke`。只有找到`invoke`,才能找到回调方法中的代码逻辑
  4. > 案例1
  5. > 查看`GlobalBlock`的汇编代码
  6. > 打开`ViewController.m`,写入以下代码:
  7. >

import “ViewController.h”

@implementation ViewController

  • (void)viewDidLoad { // [super viewDidLoad];

    void(^block)(void) = ^() {

    1. NSLog(@"block");

    };

    block(); }

@end

  1. > 真机运行项目,来到`viewDidLoad`方法<br />
  2. ![](https://upload-images.jianshu.io/upload_images/9297953-b96e4f5d0ff03b08.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  3. > 使用`register read x0`命令,查看`x0`寄存器
  4. >

x0 = 0x00000001029d8028 002—OC`__block_literal_global

  1. > - `x0`存储的是一个`GlobalBlock`
  2. > - `Block`内部不使⽤外部变量,或者只使⽤静态变量和全局变量,会形成一个`GlobalBlock`,它位于全局区`Block`
  3. > 使用`po 0x00000001029d8028`命令,打印内存中的数据
  4. >

<__NSGlobalBlock__: 0x1029d8028> signature: “v8@?0” invoke : 0x1029d6384 (/private/var/containers/Bundle/Application/2B6AC788-34A7-46BB-A44B-2237AFD48A2A/002—OC.app/002—OC`__29-[ViewController viewDidLoad]_block_invoke)

  1. > - `NSGlobalBlock``Block``isa`
  2. > - `invoke : 0x1029d6384`是代码实现的所在位置
  3. >
  4. ---
  5. > 来到`libclosure`源码
  6. > 打开`Block_private.h`文件,找到`Block`的定义
  7. >

struct Block_layout { void isa; volatile int32_t flags; int32_t reserved; BlockInvokeFunction invoke; struct Block_descriptor_1 descriptor; };

  1. > - `Block`是一个结构体
  2. > - `isa``8字节``flags``reserved`共占`8字节`
  3. > - `16字节`之后,就是`invoke`,最后是`descriptor`描述
  4. >
  5. ---
  6. > 回到汇编代码
  7. > 使用`x/8g 0x1029d8028`,查看内存中的数据<br />
  8. ![](https://upload-images.jianshu.io/upload_images/9297953-40b6f122cae1282d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  9. > - `16字节`之后,即:`invoke`,地址和上面打印的`0x1029d6384`一致
  10. >
  11. ---
  12. > `Hopper`中,找到`Block``invoke`
  13. > 使用真机编译项目,将`Mach-O`文件拖到`Hopper`中,找到`viewDidLoad`函数<br />
  14. ![](https://upload-images.jianshu.io/upload_images/9297953-d32fc7fc91158145.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  15. > - `Hopper`直接标记出`___block_literal_global`
  16. > 双击`___block_literal_global`,找到`Block`的结构体对象<br />
  17. ![](https://upload-images.jianshu.io/upload_images/9297953-8fea2d7cc535d630.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  18. > 双击`invoke`,找到`invoke`中的代码逻辑
  19. >
  1. ___29-[ViewController viewDidLoad]_block_invoke:

0000000100006384 sub sp, sp, #0x20 ; Objective C Block defined at 0x100008028, DATA XREF=0x100008038 0000000100006388 stp x29, x30, [sp, #0x10] 000000010000638c add x29, sp, #0x10 0000000100006390 str x0, [sp, #0x8] 0000000100006394 str x0, sp 0000000100006398 adrp x0, #0x100008000 ; argument #1 for method imp_stubsNSLog 000000010000639c add x0, x0, #0x48 ; @”block” 00000001000063a0 bl imp_stubsNSLog 00000001000063a4 ldp x29, x30, [sp, #0x10] 00000001000063a8 add sp, sp, #0x20 00000001000063ac ret ; endp

  1. > 案例2
  2. > 查看`StackBlock`的汇编代码
  3. > 打开`ViewController.m`,写入以下代码:
  4. >
  • (void)viewDidLoad { // [super viewDidLoad];

    int a = 1; void(^block)(void) = ^() {

    1. NSLog(@"block:%i", a);

    };

    block(); } ```

真机运行项目,来到viewDidLoad方法
iOS逆向实战--008:反汇编 - 图1


标记的第1段代码

  1. 0x1021e62dc <+28>: add x9, sp, #0x8 ; =0x8
  2. 0x1021e62e0 <+32>: adrp x10, 2
  3. 0x1021e62e4 <+36>: ldr x10, [x10]
  4. 0x1021e62e8 <+40>: str x10, [sp, #0x8]
  • sp + #0x8的地址,赋值给x9
  • adrp指令,计算地址为0x1021e8000,赋值给x10
  • x10寻址,将值再写入x10
  • x10入栈sp + #0x8的位置,此时x9成为指针,指向x10

使用x/8g 0x1021e8000,查看内存中的数据

  1. 0x1021e8000: 0x00000001f0a50830 0x000000019b84ef94
  2. 0x1021e8010: 0x0000000000000000 0x0000000000000024
  3. 0x1021e8020: 0x00000001021e6a26 0x00000001021e6ad2
  4. 0x1021e8030: 0x00000001f101a280 0x00000000000007d0

使用po 0x00000001f0a50830命令,打印第一个8字节数据

  1. __NSStackBlock__
  • 存储的是一个StackBlock
  • 在内部使⽤局部变量或者OC属性,但是不能赋值给强引⽤或者
    Copy修饰的变量,它位于栈区Block

标记的第2段代码

  1. 0x1021e62f8 <+56>: adrp x10, 0
  2. 0x1021e62fc <+60>: add x10, x10, #0x358 ; =0x358
  • 使用adrp + add指令,计算地址为0x1021e6358,赋值x10
  • 此处疑似是Blockinvoke

使用dis -s 0x1021e6358指令,查看invoke的汇编代码

  1. 002--OC方法的本质`__29-[ViewController viewDidLoad]_block_invoke:
  2. 0x1021e6358 <+0>: sub sp, sp, #0x30 ; =0x30
  3. 0x1021e635c <+4>: stp x29, x30, [sp, #0x20]
  4. 0x1021e6360 <+8>: add x29, sp, #0x20 ; =0x20
  5. 0x1021e6364 <+12>: stur x0, [x29, #-0x8]
  6. 0x1021e6368 <+16>: str x0, [sp, #0x10]
  7. 0x1021e636c <+20>: ldr w8, [x0, #0x20]
  8. 0x1021e6370 <+24>: mov x0, x8
  9. 0x1021e6374 <+28>: adrp x9, 2

Hopper中,查看StackBlock

使用真机编译项目,将Mach-O文件拖到Hopper中,找到viewDidLoad函数
iOS逆向实战--008:反汇编 - 图2

  • Hopper直接标记出invoke的位置
  • invoke下面是descriptor

双击_block_invoke,找到invoke中的代码逻辑

  1. ___29-[ViewController viewDidLoad]_block_invoke:
  2. 0000000100006358 sub sp, sp, #0x30 ; DATA XREF=-[ViewController viewDidLoad]+60
  3. 000000010000635c stp x29, x30, [sp, #0x20]
  4. 0000000100006360 add x29, sp, #0x20
  5. 0000000100006364 stur x0, [x29, #-0x8]
  6. 0000000100006368 str x0, [sp, #0x10]
  7. 000000010000636c ldr w8, [x0, #0x20]
  8. 0000000100006370 mov x0, x8
  9. 0000000100006374 adrp x9, #0x100008000
  10. 0000000100006378 add x9, x9, #0x30 ; cfstring_b
  11. 000000010000637c str x0, [sp, #0x8]
  12. 0000000100006380 mov x0, x9
  13. 0000000100006384 mov x9, sp
  14. 0000000100006388 ldr x10, [sp, #0x8]
  15. 000000010000638c str x10, x9
  16. 0000000100006390 bl imp___stubs__NSLog
  17. 0000000100006394 ldp x29, x30, [sp, #0x20]
  18. 0000000100006398 add sp, sp, #0x30
  19. 000000010000639c ret
  20. ; endp

通过汇编代码和invoke的位置来看,StackBlock案例1中的GlobalBlock区别很大

总结

OC的反汇编

  • 出现objc_msgSend,表示调用了一个OC方法,读取x0x1便可知道调用了哪个对象的哪个方法
  • 初始化的allocinit函数,在iOS12.2及更高版本,优化为objc_alloc_init函数
  • objc_storeStrong函数,对一个strong修饰的对象retain,对老对象release。有些情况下,也会直接释放对象

工具反汇编

  • 运用Hopper工具,查看汇编代码、流程图、伪码模式
  • Hopper可以解析出方法、属性等名称,作为注释标记给开发者,本质上也是通过Mach-O读取相应数据

Block的反汇编

  • Block的类型:全局区、堆区、栈区。重点掌握全局区和栈区Block的分析
  • 在逆向分析中,需要定位Blockinvoke。只有找到invoke,才能找到回调方法中的代码逻辑

了解更多汇编指令