04-汇编初学之if/switch分支&循环

0. CMP汇编指令&B指令扩展

  • CMP指令
    CMP w0, w1 CMP 把一个寄存器的内容和另一个寄存器的内容或立即数进行比较。但不存储结果,只是正确的更改标志(更改cpsr标识寄存器相应位的值)。
    一般CMP做完判断后会进行跳转,后面通常会跟上B指令!

  • B指令扩展
    BL 标号:跳转到标号处,同时更新lr寄存器的值
    B.GT 标号:比较结果是大于(greater than),执行标号,否则不跳转 B.GE 标号:比较结果是大于等于(greater than or equal to),执行标号,否则不跳转
    B.EQ 标号:比较结果是等于,执行标号,否则不跳转
    B.LE 标号:比较结果是小于等于,执行标号,否则不跳转 B.HI 标号:比较结果是无符号大于,执行标号,否则不跳转

    ... 类似有一些指令,后面都是根据cpsr的标记位进行判断是否跳转

1. if选择分支语句

  • 1.1 ida汇编示例

  • 1.2 汇编逆向

    // 在main汇编中判断,函数名+参数个数,参数类型不太确定,暂定int
    
    int _global = 20; // 类型与值无法确定的(需要lldb),这里大概估计
    
    void func (int a, int b) {
        // 汇编:var_8 var_4
        int var_8, var_4; // 和参数类型保持一致
        
        // 汇编:STR W0, [SP,#0x10+var_4],函数参数
        var_4 = a;
        // 汇编:STR W1, [SP,#0x10+var_8]
        var_8 = b;
        
        // 汇编:LDR W0, [SP,#0x10+var_4]
        int w0 = var_4;
        // 汇编:LDR W1, [SP,#0x10+var_8]
        int w1 = var_8;
        
        // 汇编:CMP W0, W1
        // 汇编:B.LE loc_1000047B0
        // cmp比较两个数值,b.le 小于等于跳转,转成高级代码反过来, 大于的情况会执行后面的汇编代码
        if (a > b) {
            // 47bc汇编:ADRP X8, #__global@PAGE
            //          ADD X8, X8, #__global@PAGEOFF
            // x8放的是 _global 的地址,_global全局变量
            int * x8 = &_global;
            
            // 47a4汇编:LDR W9, [SP,#0x10+var_4]
            int w9 = var_4;
            
            // 47a8汇编:STR W9, [X8]
            *x8 = w9;
        } else { // b.le跳转代码
            // 47b0汇编:ADRP X8, #__global@PAGE
            //          ADD X8, X8, #__global@PAGEOFF
            int * x8 = &_global;
            
            // 47b8汇编:LDR W9, [SP,#0x10+var_8]
            int w9 = var_8;
            
            // 47bc汇编:STR W9, [X8]
            *x8 = w9;
        }
        
        // 47c0汇编:ADD SP, SP, #0x10
        //          RET
        // 函数返回,没有mov x0, x8类似的代码,表示没有返回值
        // 函数返回值存储在x0
        return;
    }
    复制代码

    整理后的代码

    int _global = 20; // 类型与值??操作global是用w9,int 可以满足,但是不确定
    
    void func(int a, int b) { // 参数类型??w0、w1存储参数,所以int可以满足
        
        if (a > b) {
            _global = a;
        } else {
            _global = b;
        }
        
        return;
    }
    复制代码
  • 1.3 小结

    if-else语句的汇编是通过 先 CMP 然后 B.LE 之类的跳转指令跳转到某条汇编指令地址执行,多个if-elseif也是这样。

2. 循环语句

  • 2.1 do-while

    int main(int argc, char * argv[]) {
        int i = 0;
        do {
            printf("hello");
            i++;
        } while ( i < 100);
        return 0;
    }
    复制代码

  • 2.2 while

    int main(int argc, char * argv[]) {
        int i = 0;
        while (i<100) {
            printf("hello");
            i++;
        }
    return 0;
    }
    复制代码

  • 2.3 for

    int main(int argc, char * argv[]) {
        for (int i = 0; i < 100; i++) {
            printf("hello");
        }
        return 0;
    }
    复制代码

  • 2.4 小结:

    熟悉do-while循环的汇编样式:CMP x1,x2; B.LT loc_1000047B0 比较完成后,根据结果向前跳转
    while与for循环一样,CMP x1,x2 B.GE loc_1000047d8, 比较完成后,根据结果跳出循环; 一次循环完成后,B loc_1000047AC向前跳转,无需判断

    注意:上面的汇编截图是ida工具汇编静态分析界面,xcode汇编是不存在loc_1000047d8这类东西的,需要我们自己去判断循环的起始指令位置,这就是ida的方便之处

3. switch分支语句

  • 3.1 switch汇编如何查找分支(分支数<=3)

    void funcB (int a) {
        switch (a) {
            case 3:
                printf("33");
                break;
            case 2:
                printf("22");
                break;
            case 4:
                printf("44");
                break;
            default:
                printf("default");
                break;
        }
        return;
    }
    复制代码

    汇编3.1
    汇编3.1续

    小结:case分支数 <=3 (default另算), 此时,汇编使用b.eq对case值一个一个进行判断

  • 3.2 switch汇编如何查找分支(分支数>3 && case值连续)

    void funcB (int a) {
        switch (a) {
            case 2:
                printf("22");
                break;
            case 3:
                printf("33");
                break;
            case 4:
                printf("44");
                break;
            case 5:
                printf("55");
                break;
            default:
                printf("default");
                break;
        }
        return;
    }
    复制代码

    汇编3.2
    汇编3.2续

    汇编分析

    • 第一块(2~12行)

      • 第7行:与 最小的case值0x2 进行差值比较
      • 第8行:存储比较的差值(后面用来做 索引 )
      • 第9行:#0x3 是 最大的case值与最小case值之间的差值
      • 第12行:判断比较结果 higher 那么跳转到 后面去执行 default的代码
      • 主要做:计算传入的case值的索引(相对于最小的case值),判断出入的case值是否在default,是的话直接跳转执行,不是的话通过索引查表然后计算跳转的指令地址
      • 举例:如果传入a=4,那么x8存入的索引值就是2,这个索引值在后面非常有用
    • 第二块(13~18行)

      • 第13~14行:计算索引表的地址(memory read 地址:查看存储的数据, 或者debug-debug flow-view memory查看)
        计算过程: 0x1000b0718低12位置0,得0x1000b0000, 加0x79c,得0x1000b079c
        我们通过计算可以发现地址刚刚好是ret指令后的地址,也就是0x1000b0798开始4个字节存储ret指令,后面就是我们switch的索引表
        索引表是什么???(通过后续汇编理解,现在先打印存储内容)
      lldb: memory read 0x1000b079c
      0x1000b079c: 94 ff ff ff a8 ff ff ff bc ff ff ff d0 ff ff ff  ................
      0x1000b07ac: ff 83 00 d1 fd 7b 01 a9 fd 43 00 91 e8 07 1f 32  .....{...C.....2
      复制代码
      • 第15行:取出之前存储的索引值,同样,如果a=4,取出来的就是2
      • 第16行:ldrsw x10, [x8, x9, lsl #2] x9寄存器的值左移2位,然后与x8寄存器的值相加,得到的结果当成地址值,从这个地址值开始,读取1word的数据存入寄存器x10
        地址值计算:之前 x8=0x1000b079c 如果a=4,x9为索引值2,得到地址是0x1000b079c+2*4
        取出数据:利用上面索引表的打印,我们可以得到里面的数据是0xffffffbc(当做有符号数:-1-(0xffffffff-0xffffffbc)=-0x44)
        为什么左移2位?why?? 每一个地址偏移量是4Byte,因此左移2位(左移动2位相当于*4)
      • 第17行:计算要跳转的case指令地址:0x1000b079c-0x44=0x1000b0758==>即为当传入a=4时候,要跳转执行的case指令地址
      • 第18行:br x8 将x8寄存器中的值当做地址进行跳转
    • 第三块(19~38行)

      • 第19~23行:switch代码中第1个case执行,取出常量的地址0x1000b3cd7,lldb调试 p (char*)0x1000b3cd7, 即可打印printf要输出的常量值,进行验证是哪个case,是否判断一致;b 指令跳转,如果代码不加break,不会生成b指令,因此造成case穿透效果。。。
      • 第24~28行:switch代码中第2个case
      • 第29~33行:switch代码中第3个case
      • 第34~38行:switch代码中第4个case
    • 第四块(39~41)
      switch代码中的default分支

    小结

    • switch(a) 首先计算相对于最小case值的差值(或者偏移,在查表时候用到),然后判断是否超出一个范围 (最大case值与最小case值的范围),超出就执行default分支,未超出就去查表,计算相应的case的指令执行地址,进行跳转执行
    • 针对switch代码,编译器会生成一张地址偏移表,内部存储的是switch中每个case指令的执行地址相对于表的开始地址的偏移量(例如:case2 执行地址:0x1000b0730, 表的地址:函数ret地址+4=0x1000b079c, 所以第一个偏移量是-0x6c,转成二进制存储0xffffff94)
    • 编译器会对case值进行从小到大的排序,switch代码中case值最小的在表的最前面(例如:如果代码是从case5~case2,但是在生成表的时候还是和case2~case5一样的表,可以验证),但是case汇编地址还是按照switch中case的先后生成。====> 逆序写case的情况
    • switch相对于if效率高的原因:以空间换时间。。不在是逐一比较
  • 3.3 switch汇编如何查找分支(分支数>3 && case 值不连续)

    • 情况1(case值不连续,case的差值不是很大)
    void funcB (int a) {
        switch (a) {
            case 2:
                printf("22");
                break;
            case 3:
                printf("33");
                break;
            case 4:
                printf("44");
                break;
            case 6:
                printf("66");
                break;
            default:
                printf("default");
                break;
        }
        return;
    }
    复制代码

    汇编分析1
    汇编分析2
    偏移索引表分析

    • 情况2(case值不连续,case的差值很大)
    void funcB (int a) {
        switch (a) {
            case 2:
                printf("22");
                break;
            case 3:
                printf("33");
                break;
            case 4:
                printf("44");
                break;
            case 100:
                printf("100");
                break;
            default:
                printf("default");
                break;
        }
        return;
    }
    复制代码

    多个case之间差值过大,switch在进行分支判断的时候,使用b.eq逐一判断,和if-else没有区别

  • 3.4 总结( switch使用注意)

    1. 使用switch,分支数大于3才有意义
    2. 使用switch,分支case值的差值不要过大,否则没有意义(无法提高效率,汇编层次提高效率的思路利用空间换取时间)
    3. 使用switch,case值尽量连续(断续会造成索引表空间的浪费)
    4. switch中,case执行频率高的,尽量放在default分支去(省去查表计算的时间)

    switch相对于if-else效率高是有条件的,如果switch代码书写不当,并没有提高效率

4. Xcode编译器的优化

之前我们看到的汇编,有很多指令是没有必要的,例如:stur w0, [x29, #-0x4] ldur w0, [x29, #-0x4]。 原因是我们使用的是debug模式编译生成汇编

举例1

int sum(int a, int b) {
    return a + b;
}
int main(int argc, char * argv[]) {
    NSLog(@"haha"); // 加log是为了能够打断点
    sum(1,2);
    return 0;
}
复制代码

函数直接被优化掉了,没有调用

举例2

int sum(int a, int b) {
    return a + b;
}
int main(int argc, char * argv[]) {
    int c = sum(1,2);
    NSLog(@"%d", c);
    return 0;
}
复制代码

直接将sum函数优化掉,结果给c

注意:c函数在编译的时候可以确定,但OC方法是动态发送消息的,因此方法调用编译器是无法优化的

转载于:https://juejin.im/post/5ae96b73f265da0ba17c515a

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值