汇编基础(九)switch汇编分析

汇编基础(九)switch汇编分析_第1张图片

引言

最近工作比较忙,没怎么去研究汇编的内容,这么多天,感觉有点生疏,有的时候累死累活很晚才回来,还要在学习别的东西,在写一些总结,力不从心,就算是强迫自己去写去学习,我认为效率也是低下的。这种情况,既影响了第二天的工作,学习的时候也不会太过专注,只为一味的应付了事,学习是一个枯燥、兴趣使向、有目的性的

一些概念上的内容,其实光看光听、光说不练,基本上也就相当于拍脑袋做事情,正儿八经的研究应该是:构思->过程->验证 往往复复每次都会有新的体会

switch 注意事项

1、假设switch语句的分支比较少的时候(例如3,少于4的时候没有意义)没有必要使用此结构,相当于if。
2、各个分支常量的差值较大的时候,编译器会在效率还是内存进行取舍,这个时候编译器还是会编译成类似于if,else的结构。
3、在分支比较多的时候:在编译的时候会生成一个表(跳转表每个地址四个字节)。

switch高级代码 <4选项

void funcC(int a ){
    switch (a) {
        case 0:
            printf("yuanying");  //元婴
            break;
        case 1:
            printf("fenshen");  //分身
            break;
        case 2:
            printf("dujie");   //渡劫
            break;
        default:
            printf("qiongsi");    //穷死
            break;
    }
}
int main(int argc, char * argv[]) {
    funcC(2);
    return 0;
}

汇编代码

汇编Switch`funcC:
    0x10475a82c <+0>:   sub    sp, sp, #0x30             ; =0x30 
    0x10475a830 <+4>:   stp    x29, x30, [sp, #0x20]
    0x10475a834 <+8>:   add    x29, sp, #0x20            ; =0x20 
    0x10475a838 <+12>:  stur   w0, [x29, #-0x4]
->  0x10475a83c <+16>:  ldur   w0, [x29, #-0x4]
    0x10475a840 <+20>:  mov    x8, x0
    0x10475a844 <+24>:  stur   w8, [x29, #-0x8]
    0x10475a848 <+28>:  cbz    w0, 0x10475a878           ; <+76> at main.m:17
    0x10475a84c <+32>:  b      0x10475a850               ; <+36> at main.m
    0x10475a850 <+36>:  ldur   w8, [x29, #-0x8]
    0x10475a854 <+40>:  subs   w9, w8, #0x1              ; =0x1 
    0x10475a858 <+44>:  stur   w9, [x29, #-0xc]
    0x10475a85c <+48>:  b.eq   0x10475a88c               ; <+96> at main.m:20
    0x10475a860 <+52>:  b      0x10475a864               ; <+56> at main.m
    0x10475a864 <+56>:  ldur   w8, [x29, #-0x8]
    0x10475a868 <+60>:  subs   w9, w8, #0x2              ; =0x2 
    0x10475a86c <+64>:  str    w9, [sp, #0x10]
    0x10475a870 <+68>:  b.eq   0x10475a8a0               ; <+116> at main.m:23
    0x10475a874 <+72>:  b      0x10475a8b4               ; <+136> at main.m:26
    0x10475a878 <+76>:  adrp   x0, 1
    0x10475a87c <+80>:  add    x0, x0, #0xf10            ; =0xf10 
    0x10475a880 <+84>:  bl     0x10475abe0               ; symbol stub for: printf
    0x10475a884 <+88>:  str    w0, [sp, #0xc]
    0x10475a888 <+92>:  b      0x10475a8c4               ; <+152> at main.m:29
    0x10475a88c <+96>:  adrp   x0, 1
    0x10475a890 <+100>: add    x0, x0, #0xf19            ; =0xf19 
    0x10475a894 <+104>: bl     0x10475abe0               ; symbol stub for: printf
    0x10475a898 <+108>: str    w0, [sp, #0x8]
    0x10475a89c <+112>: b      0x10475a8c4               ; <+152> at main.m:29
    0x10475a8a0 <+116>: adrp   x0, 1
    0x10475a8a4 <+120>: add    x0, x0, #0xf21            ; =0xf21 
    0x10475a8a8 <+124>: bl     0x10475abe0               ; symbol stub for: printf
    0x10475a8ac <+128>: str    w0, [sp, #0x4]
    0x10475a8b0 <+132>: b      0x10475a8c4               ; <+152> at main.m:29
    0x10475a8b4 <+136>: adrp   x0, 1
    0x10475a8b8 <+140>: add    x0, x0, #0xf27            ; =0xf27 
    0x10475a8bc <+144>: bl     0x10475abe0               ; symbol stub for: printf
    0x10475a8c0 <+148>: str    w0, [sp]
    0x10475a8c4 <+152>: ldp    x29, x30, [sp, #0x20]
    0x10475a8c8 <+156>: add    sp, sp, #0x30             ; =0x30 
    0x10475a8cc <+160>: ret    

代码分析

  1. 0x10475a82c 拉伸栈空间
  2. 0x10475a830 保存fp(栈底)、LR(回main函数,funcC函数调用完成以后执行的代码)
  3. 0x10475a834 x29 = sp + #0x20
  4. 0x10475a838 w0 存入到 【x29 - 0x4】
  5. 0x10475a83c 将【x29 - 0x4】的值读入到w0
  6. 0x10475a840 x8 = x0
  7. 0x10475a844 w8存入进【x29 - 0x4】
  8. 0x10475a848 判断w0是否为0 如果为0 直接跳转执行0x10475a878中的方法,不跳转直接向下执行 因为结果是2
    • 0x10475a84c 跳转0x10475a850的方法 0x10475a848 中没有跳转
    • 0x10475a850 w8 = 【x29 - 0x8】
    • 0x10475a854 w9 = w8 - #0x1 修改标记位
    • 0x10475a858【x29- 0x8】 = w9
    • 0x10475a85c 上面的subs是否为0 ,就是等于的意思,等于 执行标记地址的方法的方法0x10475a88c
      • 如果等于执行0x10475a88c adrp x0, 1
      • 0x10475a890 add x0, x0, #0xf19
      • 0x10475a894 bl 0x10475abe0 执行printf函数打印
      • 0x10475a898 保存w0 [sp, #0x8] = w0
      • 0x10475a89c b 0x10475a8c4 跳转(准备返回跳出该函数)
    • 0x10475a860 向下执行0x10475a864标记的方法
    • 0x10475a864 w8 = [x29, #-0x8]
    • 0x10475a868 w9 = w8 - #0x2 修改标记位
    • 0x10475a86c 【sp + 0x10】 = w9
    • 0x10475a870 w9 = w8 - #0x2 是否为0 为0 执行标记地址中的方法 0x10475a8a0 不为0直接往下走
      • 0x10475a874 上面的结果没有跳转,执行标记地址(0x10475a8b4)中的方法
      • 下面的内容
      • 0x10475a8b4 <+136>: adrp x0, 1
      • 0x10475a8b8 <+140>: add x0, x0, #0xf27 ; =0xf27
      • 0x10475a8bc <+144>: bl 0x10475abe0 ; symbol stub for: printf
        以上是找到一个常量、并打印
        以下是0x10475a870 执行的标记地址中的方法
      • 0x10475a8a0 <+116>: adrp x0, 1
      • 0x10475a8a4 <+120>: add x0, x0, #0xf21 ; =0xf21
      • 0x10475a8a8 <+124>: bl 0x10475abe0
        以上是找到常量或局部变量
      • 0x10475a8ac [sp, #0x4] = w0
      • 0x10475a8b0 执行标记地址中的方法0x10475a8c4
  9. 0x10475a878 <+76>: adrp x0, 1
  10. 0x10475a87c <+80>: add x0, x0, #0xf10 ; =0xf10
    以上找到常量或者局部变量
  11. 0x10475a880 printf打印
  12. 0x10475a884 [sp, #0xc] = w0
  13. 0x10475a888 跳转到0x10475a8c4 直接返回之前函数准备
  • 0x10475a8c0 <+148>: str w0, [sp] 保存 [sp] = w0
  • 0x10475a8c4 <+152>: ldp x29, x30, [sp, #0x20] 恢复 fp lr
  • 0x10475a8c8 <+156>: add sp, sp, #0x30 ; =0x30 恢复栈空间
  • 0x10475a8cc <+160>: ret 返回

Switch超过4个选项汇编高级代码还原

高级代码

- (void)TheSelector:(NSInteger )Index{
    switch (Index) {
        case 0:
        {
        }
            break;
        case 1:
        {
        }
            break;
        case 2:
        {
        }
            break;
        case 3:
        {
        }
            break;
        case 4:
        {
        }
            break;
        case 5:
        {
        }
            break;
        default:
            break;
    }
}

高级代码还原`-[ViewController TheSelector:]:
   0x10427e69c <+0>:  sub    sp, sp, #0x30             ; =0x30 
   0x10427e6a0 <+4>:  str    x0, [sp, #0x28]
   0x10427e6a4 <+8>:  str    x1, [sp, #0x20]
   0x10427e6a8 <+12>: str    x2, [sp, #0x18]
->  0x10427e6ac <+16>: ldr    x0, [sp, #0x18]
   0x10427e6b0 <+20>: mov    x1, x0
   0x10427e6b4 <+24>: subs   x0, x0, #0x5              ; =0x5 
   0x10427e6b8 <+28>: str    x1, [sp, #0x10]
   0x10427e6bc <+32>: str    x0, [sp, #0x8]
   0x10427e6c0 <+36>: b.hi   0x10427e6f4               ; <+88> at ViewController.m:51
   0x10427e6c4 <+40>: adrp   x8, 0
   0x10427e6c8 <+44>: add    x8, x8, #0x700            ; =0x700 
   0x10427e6cc <+48>: ldr    x9, [sp, #0x10]
   0x10427e6d0 <+52>: ldrsw  x10, [x8, x9, lsl #2]
   0x10427e6d4 <+56>: add    x8, x10, x8
   0x10427e6d8 <+60>: br     x8
   0x10427e6dc <+64>: b      0x10427e6f8               ; <+92> at ViewController.m:54
   0x10427e6e0 <+68>: b      0x10427e6f8               ; <+92> at ViewController.m:54
   0x10427e6e4 <+72>: b      0x10427e6f8               ; <+92> at ViewController.m:54
   0x10427e6e8 <+76>: b      0x10427e6f8               ; <+92> at ViewController.m:54
   0x10427e6ec <+80>: b      0x10427e6f8               ; <+92> at ViewController.m:54
   0x10427e6f0 <+84>: b      0x10427e6f8               ; <+92> at ViewController.m:54
   0x10427e6f4 <+88>: b      0x10427e6f8               ; <+92> at ViewController.m:54
   0x10427e6f8 <+92>: add    sp, sp, #0x30             ; =0x30 
   0x10427e6fc <+96>: ret  

分开分析

  • 0x10427e69c <+0>: sub sp, sp, #0x30 ; =0x30
    拉伸栈空间
  • 0x10427e6a0 <+4>: str x0, [sp, #0x28]
    0x10427e6a4 <+8>: str x1, [sp, #0x20]
    0x10427e6a8 <+12>: str x2, [sp, #0x18]
    0x10427e6ac <+16>: ldr x0, [sp, #0x18]

    这三句的代码,将x0、x1、x2存入到栈中,然后在把x2存入栈中的值,读出来存到x0,这个过程的含义,大概可以这么理解,因为在OC当中调用方法,是调用一个方法_objc_msgSend,它的三个参数分别是Self、_TheSelector、以及传过来的值,所以最后要读出来x2的值在存入到x0

  • 0x10427e6b0 <+20>: mov x1, x0

    将将x0的值赋值给x1

  • 0x10427e6b4 <+24>: subs x0, x0, #0x5 ; =0x5

x0 = x0 - #0x5 得出的结果将会修改标记位

  • 0x10427e6b8 <+28>: str x1, [sp, #0x10]
    0x10427e6bc <+32>: str x0, [sp, #0x8]

    x1 、x0存入到栈中

  • 0x10427e6c0 <+36>: b.hi 0x10427e6f4

    如果标记位也就是ubs x0, x0, #0x5 x0>#0x5 直接跳转到标记中的地址,当然根据调试我们发现并没有往下走,而且根据我们写的高级代码来说 3肯定没有5大

  • 0x10427e6c4 <+40>: adrp x8, 0
    0x10427e6c8 <+44>: add x8, x8, #0x700 ; =0x700
    ldr x9, [sp, #0x10]

    获取一个全局变量、或者一个常量
    x8得到的结果是-36 x8 中的地址是 猛的一看就是负数


    汇编基础(九)switch汇编分析_第2张图片

    0x10427e6cc <+48>: ldr x9, [sp, #0x10]

    0x10427e6b8 <+28>: str x1, [sp, #0x10]
    0x10427e6cc <+48>: ldr x9, [sp, #0x10] 因为x1是参数3,x9 = 3


    汇编基础(九)switch汇编分析_第3张图片

    0x10427e6d0 <+52>: ldrsw x10, [x8, x9, lsl #2]
    0x10427e6d4 <+56>: add x8, x10, x8

x10 = [x8, x9, lsl #2]内存地址中的值
[x8, x9, lsl #2] 以x8为基准值 + (x9的值左移2位 )
以x8地址为基准值 + (3的值左移2位 )
以x8地址为基准值 + (3的值左移2位 )0011 变成 1100 8+4 = 12
以x8地址为基准值 + 12
x10 = 0xffffffe8


汇编基础(九)switch汇编分析_第4张图片

汇编基础(九)switch汇编分析_第5张图片

直接跳转到指定的case中


汇编基础(九)switch汇编分析_第6张图片

结论

  • 其实核心在于
    0x10427e6c4 <+40>: adrp x8, 0
    0x10427e6c8 <+44>: add x8, x8, #0x700
    中拿到的x8,0x100d4e700
    汇编基础(九)switch汇编分析_第7张图片

**switch中的选项>=4个的话,中间间隔不是很长的话,会直接连续的在栈中创建一个表,每4个字节代表一个数值,比如:case 1、2、3
内存创建的表抽象的对应这些地址:xx FF FF FF = 0 、xx FF FF FF = 1 、xx FF FF FF = 2 、xx FF FF FF = 3,当然最后你需要计算出真正的偏移的位置 **

你可能感兴趣的:(汇编基础(九)switch汇编分析)