判断

cmp:(Compare)比较指令

cmp把一个寄存器的内容和另一个寄存器的内容或立即数进行比较。但不存储结果,只是正确的更改标志

一般cmp做完判断后会进行跳转,后面通常会跟上b指令

有符号数

  • b.lt 标号:比价结果是小于(less than),执行标号,否则不跳转
  • b.le 标号:比较结果是小于等于(less than or qeual to),执行标号,否则不跳转
  • b.gt 标号:比较结果是大于(greater than),执行标号,否则不跳转
  • b.ge 标号:比较结果是大于等于(greater than or equal to),执行标号,否则不跳转
  • b.eq 标号:比较结果是等于(equal to),执行标号,否则不跳转
  • b.ne 标号:比较结果是不等于(not equal to),执行标号,否则不跳转

无符号数

  • b.lo 标号:比较结果是小于,执行标号,否则不跳转
  • b.ls 标号:比较结果是小于等于,执行标号,否则不跳转
  • b.hi 标号:比较结果是大于,执行标号,否则不跳转
  • b.hs 标号:比较结果是大于等于,执行标号,否则不跳转

案例:

编译器对if判断的识别

打开ViewController.m文件,写入以下代码:

```

import “ViewController.h”

int g = 12;

void func(int a, int b){ if (a > b) { g = a; } else { g = b; } }

@implementation ViewController

  • (void)viewDidLoad { // [super viewDidLoad]; func(1, 2); }

@end

  1. > 使用真机编译项目,将`Mach-O`文件拖到`Hopper`中,找到`func`函数<br />
  2. ![](https://upload-images.jianshu.io/upload_images/9297953-c76607359b06e102.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  3. > - `func`函数在`Hopper`中被分为`四段`
  4. > `func`函数,`第一段`
  5. >
  1. _func:

0000000100005f0c sub sp, sp, #0x10 ; CODE XREF=-[ViewController viewDidLoad]+28 0000000100005f10 str w0, [sp, #0xc] 0000000100005f14 str w1, [sp, #0x8] 0000000100005f18 ldr w8, [sp, #0xc] 0000000100005f1c ldr w9, [sp, #0x8] 0000000100005f20 cmp w8, w9 0000000100005f24 b.le loc_100005f3c

  1. > - 开辟栈空间,现场保护
  2. > - `cmp w8, w9`:比较`w8``w9`寄存器,相当于比较`参数1``参数2``cmp`指令相当于做减法,但只会影响状态寄存器,不会影响目标寄存器
  3. > - `b.le loc_100005f3c`:如果`参数1 - 参数2`的结果`<= 0`,相当于触发`else`代码块,执行标号跳转到`第三段`。否则触发`if`代码块,向下执行`第二段`
  4. > - 所以`cmp`指令之后的跳转,进入的是`else`代码分支
  5. > `func`函数,`第二段`。如果`参数1 - 参数2 > 0`执行此段代码
  6. >

0000000100005f28 ldr w8, [sp, #0xc] 0000000100005f2c adrp x9, #0x10000d000 0000000100005f30 add x9, x9, #0x490 ; _g 0000000100005f34 str w8, x9 0000000100005f38 b loc_100005f4c

  1. > - 此段代码,相当于`if`中的代码块
  2. > - `w8``参数1``x9`为全局变量`g`,将`w8`写入`x9`
  3. > - `b`指令,执行标号跳转到`第四段`
  4. > `func`函数,`第三段`。如果`参数1 - 参数2 <= 0`执行此段代码
  5. >
  1. loc_100005f3c:

0000000100005f3c ldr w8, [sp, #0x8] ; CODE XREF=_func+24 0000000100005f40 adrp x9, #0x10000d000 0000000100005f44 add x9, x9, #0x490 ; _g 0000000100005f48 str w8, x9

  1. > - 此段代码,相当于`else`中的代码块
  2. > - `w8``参数2``x9`为全局变量`g`,将`w8`写入`x9`
  3. > - 继续向下执行`第四段`
  4. > `func`函数,`第四段`
  5. >
  1. loc_100005f4c:

0000000100005f4c add sp, sp, #0x10 ; CODE XREF=_func+44 0000000100005f50 ret ; endp

  1. > - 恢复栈平衡,返回
  2. #####循环
  3. > 不同的循环方式:
  4. > - `do...while`
  5. > - `while`
  6. > - `for`
  7. > 案例1
  8. > 编译器对`do...while`循环的识别
  9. > 打开`ViewController.m`文件,写入以下代码:
  10. >

import “ViewController.h”

void funcA(){

int sum = 0; int i = 0;

do { sum += 1; i++; } while (i < 100); }

@implementation ViewController

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

@end

  1. > 使用真机编译项目,将`Mach-O`文件拖到`Hopper`中,找到`funcA`函数<br />
  2. ![](https://upload-images.jianshu.io/upload_images/9297953-4728b8bca2e20959.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  3. > - `funcA`函数在`Hopper`中被分为三段
  4. > `funcA`函数,`第一段`
  5. >
  1. _funcA:

0000000100005f24 sub sp, sp, #0x10 ; CODE XREF=-[ViewController viewDidLoad]+20 0000000100005f28 str wzr, [sp, #0xc] 0000000100005f2c str wzr, [sp, #0x8]

  1. > - 开辟栈空间,现场保护。将`wzr`寄存器的值,分别在`sp + 0xc``sp + 0x8`地址入栈
  2. > - 继续向下执行`第二段`
  3. > `funcA`函数,`第二段`
  4. >
  1. loc_100005f30:

0000000100005f30 ldr w8, [sp, #0xc] ; CODE XREF=_funcA+44 0000000100005f34 add w8, w8, #0x1 0000000100005f38 str w8, [sp, #0xc] 0000000100005f3c ldr w8, [sp, #0x8] 0000000100005f40 add w8, w8, #0x1 0000000100005f44 str w8, [sp, #0x8] 0000000100005f48 ldr w8, [sp, #0x8] 0000000100005f4c cmp w8, #0x64 0000000100005f50 b.lt loc_100005f30

  1. > - 因为使用`do...while`循环,所以`cmp``b.lt`指令在最下面。这意味着此代码块,至少也会执行一次
  2. > - 读取`sp + 0xc`地址的值,写入`w8`,进行`w8 += 1`操作,将结果再次入栈
  3. > - 读取`sp + 0x8`地址的值,写入`w8`,进行`w8 += 1`操作,将结果再次入栈
  4. > - 再次将`sp + 0x8`地址的值,写入`w8`
  5. > - `cmp`指令,将`w8`的值和`#0x64`,即`10进制``100`进行比较
  6. > - `b.lt`指令,如果`w8 - 100 < 0`,执行标号跳转到`第二段`,相当于递归执行。否则向下执行`第三段`,相当于跳出循环
  7. > `funcA`函数,`第三段`
  8. >

0000000100005f54 add sp, sp, #0x10 0000000100005f58 ret ; endp

  1. > - 恢复栈平衡,返回
  2. > 案例2
  3. > 编译器对`while`循环的识别
  4. > 打开`ViewController.m`文件,写入以下代码:
  5. >

import “ViewController.h”

void funcA(){

int sum = 0; int i = 0;

while (i < 100) { sum += 1; i++; } }

@implementation ViewController

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

@end

  1. > 使用真机编译项目,将`Mach-O`文件拖到`Hopper`中,找到`funcA`函数<br />
  2. ![](https://upload-images.jianshu.io/upload_images/9297953-d0e48761c4954e74.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  3. > - `funcA`函数在`Hopper`中被分为`四段`
  4. > `funcA`函数,`第一段`
  5. >
  1. _funcA:

0000000100005f20 sub sp, sp, #0x10 ; CODE XREF=-[ViewController viewDidLoad]+20 0000000100005f24 str wzr, [sp, #0xc] 0000000100005f28 str wzr, [sp, #0x8]

  1. > - 开辟栈空间,现场保护。将`wzr`寄存器的值,分别在`sp + 0xc``sp + 0x8`地址入栈
  2. > - 继续向下执行`第二段`
  3. > `funcA`函数,`第二段`
  4. >
  1. loc_100005f2c:

0000000100005f2c ldr w8, [sp, #0x8] ; CODE XREF=_funcA+48 0000000100005f30 cmp w8, #0x64 0000000100005f34 b.ge loc_100005f54

  1. > - 读取`sp + 0x8`地址的值,写入`w8`
  2. > - `cmp`指令,将`w8`的值和`#0x64`,即`10进制``100`进行比较
  3. > - `b.lt`指令,如果`w8 - 100 >= 0`,执行标号跳转到`第四段`,相当于跳出循环。否则向下执行`第三段`,执行循环内部的代码块
  4. > `funcA`函数,`第三段`
  5. >

0000000100005f38 ldr w8, [sp, #0xc] 0000000100005f3c add w8, w8, #0x1 0000000100005f40 str w8, [sp, #0xc] 0000000100005f44 ldr w8, [sp, #0x8] 0000000100005f48 add w8, w8, #0x1 0000000100005f4c str w8, [sp, #0x8] 0000000100005f50 b loc_100005f2c

  1. > - 读取`sp + 0xc`地址的值,写入`w8`,进行`w8 += 1`操作,将结果再次入栈
  2. > - 读取`sp + 0x8`地址的值,写入`w8`,进行`w8 += 1`操作,将结果再次入栈
  3. > - `b`指令,执行标号跳转到`第二段`,继续验证进入循环的条件限制,决定之后的代码流程
  4. > `funcA`函数,`第四段`
  5. >
  1. loc_100005f54:

0000000100005f54 add sp, sp, #0x10 ; CODE XREF=_funcA+20 0000000100005f58 ret ; endp

  1. > - 恢复栈平衡,返回
  2. > 案例3
  3. > 编译器对`for`循环的识别
  4. > 打开`ViewController.m`文件,写入以下代码:
  5. >

import “ViewController.h”

void funcA(){

int sum = 0;

for (int i = 0; i < 100; i++) { sum += 1; } }

@implementation ViewController

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

@end

  1. > 使用真机编译项目,将`Mach-O`文件拖到`Hopper`中,找到`funcA`函数<br />
  2. ![](https://upload-images.jianshu.io/upload_images/9297953-80946ff6fde72f09.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  3. > - `funcA`函数在`Hopper`中被分为四段
  4. > - 代码流程和`while`循环完全一致
  5. #####选择
  6. > `switch`语句:当满足条件时,执行某些操作。可以使用`if-else`来实现,也可以使用`switch`来实现
  7. > - 假设`switch`语句的分支比较少的时候,例如:少于`4个`的时候,没有必要使用此结构,此时`switch`的执行方式相当于`if-else`
  8. > - 在分支比较多的时候,编译时会生成一张数据表。表中每个偏移值占`四字节`,通过查表找到指定代码的标号地址,无需遍历。以空间换取时间,提高效率
  9. > - 各个分支常量的差值较大的时候,编译器会在效率和内存中进行取舍,这个时候编译器有可能会编译成类似于`if-else`的结构
  10. > 案例1
  11. > `switch`语句的分支比较少的时候,编译器会如何处理?
  12. > 打开`ViewController.m`文件,写入以下代码:
  13. >

import “ViewController.h”

void funcA(int a){ switch (a) { case 1: printf(“打坐”); break; case 2: printf(“加红”); break; case 3: printf(“加蓝”); break; default: printf(“待命”); break; } }

@implementation ViewController

  • (void)viewDidLoad { // [super viewDidLoad]; funcA(1); }

@end

  1. > 使用真机编译项目,将`Mach-O`文件拖到`Hopper`中,找到`funcA`函数<br />
  2. ![](https://upload-images.jianshu.io/upload_images/9297953-90cb5f172f8e50b7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  3. > - `funcA`函数在`Hopper`中被分为`十一段`
  4. > `funcA`函数,`第一段`
  5. >
  1. _funcA:

0000000100005e9c sub sp, sp, #0x20 ; CODE XREF=-[ViewController viewDidLoad]+24 0000000100005ea0 stp x29, x30, [sp, #0x10] 0000000100005ea4 add x29, sp, #0x10 0000000100005ea8 stur w0, [x29, #-0x4] 0000000100005eac ldur w8, [x29, #-0x4] 0000000100005eb0 cmp w8, #0x1 0000000100005eb4 str w8, [sp, #0x8] 0000000100005eb8 b.eq loc_100005ee0

  1. > - 开辟栈空间,现场保护
  2. > - `w0`寄存器的值,在`x29 - 0x4`地址入栈
  3. > - 读取`x29 - 0x4`地址的值,写入`w8`
  4. > - `cmp`指令,将`w8`的值和`#0x1`,即`10进制``1`进行比较
  5. > - `w8`寄存器的值,在`sp + 0x8`地址入栈
  6. > - `b.eq`指令,如果`w8 - 1 == 0`,执行标号跳转到`第七段`,相当于进入`case 1`的代码块。否则向下执行`第二段`
  7. > `funcA`函数,`第二段`
  8. >

0000000100005ebc b loc_100005ec0

  1. > - `b`指令,执行标号跳转到`第三段`
  2. > `funcA`函数,`第三段`
  3. >
  1. loc_100005ec0:

0000000100005ec0 ldr w8, [sp, #0x8] ; CODE XREF=_funcA+32 0000000100005ec4 cmp w8, #0x2 0000000100005ec8 b.eq loc_100005ef0

  1. > - 读取`sp + 0x8`地址的值,写入`w8`
  2. > - `cmp`指令,将`w8`的值和`#0x2`,即`10进制``2`进行比较
  3. > - `b.eq`指令,如果`w8 - 2 == 0`,执行标号跳转到`第八段`,相当于进入`case 2`的代码块。否则向下执行`第四段`
  4. > `funcA`函数,`第四段`
  5. >

0000000100005ecc b loc_100005ed0

  1. > - `b`指令,执行标号跳转到`第五段`
  2. > `funcA`函数,`第五段`
  3. >
  1. loc_100005ed0:

0000000100005ed0 ldr w8, [sp, #0x8] ; CODE >XREF=_funcA+48 0000000100005ed4 cmp w8, #0x3 0000000100005ed8 b.eq loc_100005f00

  1. > - 读取`sp + 0x8`地址的值,写入`w8`
  2. > - `cmp`指令,将`w8`的值和`#0x3`,即`10进制``3`进行比较
  3. > - `b.eq`指令,如果`w8 - 3 == 0`,执行标号跳转到`第九段`,相当于进入`case 3`的代码块。否则向下执行`第六段`
  4. > `funcA`函数,`第六段`
  5. >

0000000100005edc b loc_100005f10

  1. > - `b`指令,执行标号跳转到`第十段`,相当于进入`default`的代码块
  2. > `funcA`函数,`第七段`。即:`case 1`的代码块
  3. >
  1. loc_100005ee0:

0000000100005ee0 adrp x0, #0x100006000 ; argument #1 for method imp_stubsprintf, CODE XREF=_funcA+28 0000000100005ee4 add x0, x0, #0x634 ; “\xE6\x89\x93\xE5\x9D\x90” 0000000100005ee8 bl imp_stubsprintf 0000000100005eec b loc_100005f1c

  1. > - 在常量区拿到字符串`打坐`,写入`x0`
  2. > - 调用`printf`函数
  3. > - `b`指令,执行标号跳转到`第十一段`。即:`funcA`函数的结尾处
  4. > `funcA`函数,`第八段`。即:`case 2`的代码块
  5. >
  1. loc_100005ef0:

0000000100005ef0 adrp x0, #0x100006000 ; argument #1 for method imp_stubsprintf, CODE XREF=_funcA+44 0000000100005ef4 add x0, x0, #0x63b ; “\xE5\x8A\xA0\xE7\xBA\xA2” 0000000100005ef8 bl imp_stubsprintf 0000000100005efc b loc_100005f1c

  1. > - 在常量区拿到字符串`加红`,写入`x0`
  2. > - 调用`printf`函数
  3. > - `b`指令,执行标号跳转到`第十一段`。即:`funcA`函数的结尾处
  4. > `funcA`函数,`第九段`。即:`case 3`的代码块
  5. >
  1. loc_100005f00:

0000000100005f00 adrp x0, #0x100006000 ; argument #1 for method imp_stubsprintf, CODE XREF=_funcA+60 0000000100005f04 add x0, x0, #0x642 ; “\xE5\x8A\xA0\xE8\x93\x9D” 0000000100005f08 bl imp_stubsprintf 0000000100005f0c b loc_100005f1c

  1. > - 在常量区拿到字符串`加蓝`,写入`x0`
  2. > - 调用`printf`函数
  3. > - `b`指令,执行标号跳转到`第十一段`。即:`funcA`函数的结尾处
  4. > `funcA`函数,`第十段`。即:`default`的代码块
  5. >
  1. loc_100005f10:

0000000100005f10 adrp x0, #0x100006000 ; argument #1 for method imp_stubsprintf, CODE XREF=_funcA+64 0000000100005f14 add x0, x0, #0x649 ; “\xE5\xBE\x85\xE5\x91\xBD” 0000000100005f18 bl imp_stubsprintf

  1. > - 在常量区拿到字符串`待命`,写入`x0`
  2. > - 调用`printf`函数
  3. > - 继续向下执行`第十一段`
  4. > `funcA`函数,`第十一段`
  5. >
  1. loc_100005f1c:

0000000100005f1c ldp x29, x30, [sp, #0x10] ; CODE XREF=_funcA+80, _funcA+96, _funcA+112 0000000100005f20 add sp, sp, #0x20 0000000100005f24 ret ; endp

  1. > - 还原`x29``x30`的值
  2. > - 恢复栈平衡
  3. > - 返回
  4. > 上述案例中,每个`case`都会检查一次,当所有`case`都不满足时,跳转`default`。从代码流程上,感觉和使用`if-else`实现没有区别
  5. > `funcA`函数,使用`if-else`实现:
  6. >

void funcA(int a){ if(a == 1){ printf(“打坐”); } else if (a == 2){ printf(“加红”); } else if (a == 3){ printf(“加蓝”); } else{ printf(“待命”); } }

  1. > 使用真机编译项目,将`Mach-O`文件拖到`Hopper`中,找到`funcA`函数<br />
  2. ![](https://upload-images.jianshu.io/upload_images/9297953-724a6a1de8f822fb.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  3. > - 从流程上看,比`switch`语句少了一些b指令的中间跳转,但也是每个条件分支都会检查一次,当所有`if`都不满足时,跳转`else`
  4. > 结论:当`switch`语句的分支比较少的时候,例如:少于`4个`的时候,没有必要使用此结构,此时`switch`的执行方式相当于`if-else`
  5. > 案例2
  6. > 在分支比较多的时候,如果`case`的值是连续的,或者间隔较小,编译时会生成一张数据表
  7. > 打开`ViewController.m`文件,写入以下代码:
  8. >

import “ViewController.h”

void funcA(int a){ switch (a) { case 1: printf(“打坐”); break; case 2: printf(“加红”); break; case 3: printf(“加蓝”); break; case 4: printf(“打怪”); break; default: printf(“待命”); break; } }

@implementation ViewController

  • (void)viewDidLoad { // [super viewDidLoad]; funcA(4); }

@end

  1. > 真机运行项目,来到`func`方法<br />
  2. ![](https://upload-images.jianshu.io/upload_images/9297953-f25bf451e71f386a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  3. > - `funcA`函数的汇编代码分为`八段`
  4. > `funcA`函数,`第一段`
  5. >

0x102269ec4 <+0>: sub sp, sp, #0x20 ; =0x20 0x102269ec8 <+4>: stp x29, x30, [sp, #0x10] 0x102269ecc <+8>: add x29, sp, #0x10 ; =0x10 0x102269ed0 <+12>: stur w0, [x29, #-0x4] 0x102269ed4 <+16>: ldur w8, [x29, #-0x4] 0x102269ed8 <+20>: subs w8, w8, #0x1 ; =0x1 0x102269edc <+24>: mov x9, x8 0x102269ee0 <+28>: ubfx x9, x9, #0, #32 0x102269ee4 <+32>: cmp x9, #0x3 ; =0x3 0x102269ee8 <+36>: str x9, [sp] 0x102269eec <+40>: b.hi 0x102269f48 ; <+132> at ViewController.m

  1. > - 开辟栈空间,现场保护
  2. > - `w0``x29 - 0x4`地址入栈,然后再寻址写入`w8`
  3. > - `subs w8, w8, #0x1``w8`为当前变量的值,减去`#0x1`,写入`w8`寄存器<br />
  4. `w8``viewDidLoad`方法中,调用`funcA`函数,传入的参数为`4`<br />
  5. `#0x1`:最小`case`的值,转为`10进制``1`<br />
  6. `w8 -= 1`,即:`4 - 1` ,得到`参数区间值``3`<br />
  7. 使用`subs`指令,影响目标寄存器,同时影响状态寄存器
  8. > - `x8`写入`x9`
  9. > - `ubfx x9, x9, #0, #32`:保留`x9`寄存器的`低32位`,剩余高位用`0`填充<br />
  10. `ubfx`指令:从`x9`寄存器的`第0位`开始,提取`32位``x9`寄存器,剩余高位用`0`填充<br />
  11. 例如:`x9``0x0123456789abcdef`,此指令执行后,`x9``0x0000000089abcdef`
  12. > - `cmp x9, #0x3`:将`x9`的值和`#0x3`进行比较<br />
  13. `x9``参数区间值`<br />
  14. `#0x3`:最大`case`值减去最小`case`值,得到`case区间值``3`
  15. > - `x9``sp`所在位置入栈
  16. > - `b.hi 0x104105f48`:如果`参数区间值 - case区间值 > 0`,执行标号跳转到`第七段`,相当于进入`default`的代码块。否则向下执行`第二段`
  17. > `funcA`函数,`第二段`。通过查表,获取跳转到指定`case``default`的代码标号
  18. >

0x102269ef0 <+44>: adrp x8, 0 0x102269ef4 <+48>: add x8, x8, #0xf60 ; =0xf60 0x102269ef8 <+52>: ldr x11, [sp] 0x102269efc <+56>: ldrsw x10, [x8, x11, lsl #2] 0x102269f00 <+60>: add x9, x8, x10 0x102269f04 <+64>: br x9

  1. > - `adrp x8, 0` ~ `add x8, x8, #0xf60`:将`0x102269f60`地址,写入`x8`<br />
  2. `0x102269f60`:数据表的首地址,存储在`funcA`函数地址之后<br />
  3. `funcA`函数最后指令地址:`0x102269f5c`,向后偏移`4字节``0x102269f60`<br />
  4. ![](https://upload-images.jianshu.io/upload_images/9297953-8583e61a4149709f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  5. > - 读取`sp`地址的值,写入`x11`
  6. > - `ldrsw x10, [x8, x11, lsl #2]`:<br />
  7. `ldrsw`指令:通过`x8 + x11左移2位`计算地址,读取该地址的值,赋值给`x10`<br />
  8. `x8`:值为`0x102269f60`。数据表首地址<br />
  9. `x11`:值为`3``参数 - 最小case值`,得到的`参数区间值`<br />
  10. `x11`左移`2位``3 << 2 = 12`,即:`16进制``0xc`<br />
  11. 数据表首地址加上偏移地址:`0x102269f60 + 0xc = 0x102269f6c`<br />
  12. 读取`0x102269f6c`地址的值`0xffffffffffffffd8`,写入`x10`<br />
  13. ![](https://upload-images.jianshu.io/upload_images/9297953-b065f8161262bd56.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)<br />
  14. ![](https://upload-images.jianshu.io/upload_images/9297953-8d33f9e67c1d4138.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240#alt=)
  15. > - `add x9, x8, x10`:将`x8``x10`相加,写入`x9`<br />
  16. `x8`:值为`0x102269f60`。数据表首地址<br />
  17. `x10`:值为`0xffffffffffffffd8`,即:`-40``16进制``0x-28`。从数据表中获取距代码标号的偏移值<br />
  18. `0x102269f60 + 0x-28 = 0x102269f38`,写入`x9`
  19. > - `br x9`:执行`x9`寄存器所存储的标号<br />
  20. `x9`:值为`0x102269f38`。执行标号跳转到`第六段`,相当于进入`case 4`的代码块
  21. > `funcA`函数,`第三段`。即:`case 1`的代码块
  22. >

0x102269f08 <+68>: adrp x0, 1 0x102269f0c <+72>: add x0, x0, #0x634 ; =0x634 0x102269f10 <+76>: bl 0x10226a598 ; symbol stub for: printf 0x102269f14 <+80>: b 0x102269f54 ; <+144> at ViewController.m:28:1

  1. > - 在常量区拿到字符串`打坐`,写入`x0`
  2. > - 调用`printf`函数
  3. > - `b`指令,执行标号跳转到`第八段`。即:`funcA`函数的结尾处
  4. > `funcA`函数,`第四段`。即:`case 2`的代码块
  5. >

0x102269f18 <+84>: adrp x0, 1 0x102269f1c <+88>: add x0, x0, #0x63b ; =0x63b 0x102269f20 <+92>: bl 0x10226a598 ; symbol stub for: printf 0x102269f24 <+96>: b 0x102269f54 ; <+144> at ViewController.m:28:1

  1. > - 在常量区拿到字符串`加红`,写入`x0`
  2. > - 调用`printf`函数
  3. > - `b`指令,执行标号跳转到`第八段`。即:`funcA`函数的结尾处
  4. > `funcA`函数,`第五段`。即:`case 3`的代码块
  5. >

0x102269f28 <+100>: adrp x0, 1 0x102269f2c <+104>: add x0, x0, #0x642 ; =0x642 0x102269f30 <+108>: bl 0x10226a598 ; symbol stub for: printf 0x102269f34 <+112>: b 0x102269f54 ; <+144> at ViewController.m:28:1

  1. > - 在常量区拿到字符串`加蓝`,写入`x0`
  2. > - 调用`printf`函数
  3. > - `b`指令,执行标号跳转到`第八段`。即:`funcA`函数的结尾处
  4. > `funcA`函数,`第六段`。即:`case 4`的代码块
  5. >

0x102269f38 <+116>: adrp x0, 1 0x102269f3c <+120>: add x0, x0, #0x649 ; =0x649 0x102269f40 <+124>: bl 0x10226a598 ; symbol stub for: printf 0x102269f44 <+128>: b 0x102269f54 ; <+144> at ViewController.m:28:1

  1. > - 在常量区拿到字符串`打怪`,写入`x0`
  2. > - 调用`printf`函数
  3. > - `b`指令,执行标号跳转到`第八段`。即:`funcA`函数的结尾处
  4. > `funcA`函数,`第七段`。即:`default`的代码块
  5. >

0x102269f48 <+132>: adrp x0, 1 0x102269f4c <+136>: add x0, x0, #0x650 ; =0x650 0x102269f50 <+140>: bl 0x10226a598 ; symbol stub for: printf

  1. > - 在常量区拿到字符串`待命`,写入`x0`
  2. > - 调用`printf`函数
  3. > - 继续向下执行`第八段`
  4. > `funcA`函数,`第八段`
  5. >

0x102269f54 <+144>: ldp x29, x30, [sp, #0x10] 0x102269f58 <+148>: add sp, sp, #0x20 ; =0x20 0x102269f5c <+152>: ret

  1. > - 还原`x29``x30`的值
  2. > - 恢复栈平衡
  3. > - 返回
  4. > 上述案例中,`switch`语句生成的汇编代码逻辑
  5. > 【第一步】:判断参数值是否在`case`值范围之内
  6. > - 通过`参数值 - 最小case值`,得到`参数区间值`
  7. > - 通过`最大case值 - 最小case值`,得到`case区间值`
  8. > - `参数区间值``case区间值`进行比较
  9. > - 如果`参数区间值 > case区间值`,表示超出`case`范围,直接跳转到`default`代码标号。否则进入`【第二步】`
  10. > 【第二步】:通过查表,获取跳转到指定`case``default`的代码标号
  11. > - 获取`数据表首地址`
  12. > - 计算`数据表首地址`加上`参数区间值`偏移`x`位后的地址
  13. > - 通过计算后地址,从数据表中获取距代码标号的偏移值
  14. > - 通过`数据表首地址 + 16进制(偏移值)`,计算出指定`case``default`的代码标号
  15. > - 虽然参数在`case`值范围之内,也有可能进入`default`分支,因为`case`值不一定是连续的
  16. > - 表中存储的数据量,是`case区间值 + default`的总数。所以`case`的值间隔越大,需要存储的数据量就会越多
  17. > - 表中存储的数据,以表头地址为参照,计算出跳转到各分支代码的偏移值。它们在编译时已经被编译器计算好并存储在表中,因为`switch`的条件分支代码是连续的,因此可以这样计算
  18. > - 数据表中每`四字节`存储一个偏移值,所以在`ldrsw`指令中有`左移2位`的计算
  19. > - 由于`ASLR`技术(`Address Spce Layout Randomization`),翻译过来就是地址空间布局随机化。数据表中不直接存储地址,而是存储偏移值
  20. > 结论:在`switch`语句分支比较多的时候,如果`case`的值是连续的,或间隔较小,编译器会建立一张数据表。通过查表直接获取跳转到指定代码的标号地址,无需遍历每一个`case`分支,以空间换取时间
  21. > 案例3
  22. > `case`常量的差值较大时,编译器会如何处理?
  23. > 打开`ViewController.m`文件,写入以下代码:
  24. >

void funcA(int a){ switch (a) { case 1: printf(“打坐”); break; case 20: printf(“加红”); break; case 40: printf(“加蓝”); break; case 80: printf(“打怪”); break; default: printf(“待命”); break; } } ```

使用真机编译项目,将Mach-O文件拖到Hopper中,找到funcA函数
iOS逆向实战--005:判断-%26-循环-%26-选择 - 图1

  • 如果case值的间隔较大,编译器会权衡是否还要以空间换取时间。上述案例中,如果建立数据表需要存储大量数据,耗费大量空间,此时编译器会选择使用if-else的执行方式

结论:各个分支常量的差值较大的时候,编译器会在效率和内存中进行取舍,这个时候编译器有可能会编译成类似于if-else的结构

总结

判断(if)

  • cmp:比较指令,其实在做减法。不影响目标寄存器,只影响状态寄存器

循环

  • do…while循环:判断条件在后面,满足循环条件往外跳
  • while循环:判断条件在前面,满足循环条件往里跳
  • for循环:和while循环逻辑一致

选择

  • 分支少于4个switch底层代码和if-else一样
  • 在分支比较多的时候,编译时会生成一张数据表,通过查表进行代码调转,无需遍历。以空间换取时间,提高效率
  • 各个分支常量差值较大,底层代码可能使用数据表,也可能使用if-else。因为编译器会在效率和内存中进行取舍