逆向学习笔记4——汇编的循环&选择

常用指令

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

  • BL 标号:跳转到标号处执行

  • B.LE 标号:比较结果是小于等于(less than or equal),执行标号,否则不跳转

  • B.GT 标号:比较结果是大于(greater than),执行标号,否则不跳转

  • B.GE 标号:比较结果是大于等于(greater than or equal to),执行标号,否则不跳转

  • B.EQ 标号:比较结果是等于,执行标号,否则不跳转

  • B.HI 标号:比较结果是无符号大于,执行标号,否则不跳转

  • ldrsw:[X8,X9,LSL#2]` 表示以 X8 为基地址,X9的二进制左移2位,再相加得到一个新的地址值。
    例如:A表示(X8 为基地址),B 表示(X9的二进制左移2位)
    那么 X10 = [A B];

if

void func(int a, int b){
    if(a > b){
        printf("a大于b");
    }else{
        printf("a不大于b");
    }
}

上面代码的汇编如下:

demo1`func:
    0x100f926a0 <+0>:  sub    sp, sp, #0x20             ; =0x20 //拉升栈空间
    0x100f926a4 <+4>:  stp    x29, x30, [sp, #0x10]
    0x100f926a8 <+8>:  add    x29, sp, #0x10            ; =0x10 
    0x100f926ac <+12>: stur   w0, [x29, #-0x4]
    0x100f926b0 <+16>: str    w1, [sp, #0x8]
    0x100f926b4 <+20>: ldur   w0, [x29, #-0x4]
    0x100f926b8 <+24>: ldr    w1, [sp, #0x8]
   //上面这些代码不用管,全是函数对局部变量的操作

    //比较w0和w1的值
    0x100f926bc <+28>: cmp    w0, w1
    //如果w0<=w1 跳转到地址为0x100f926d8的指令
    0x100f926c0 <+32>: b.le   0x100f926d8               ; <+56> at main.m:17 

    //下面两句代码得到的x0对应的常量值为0x100f9365c
    0x100f926c4 <+36>: adrp   x0, 1
    0x100f926c8 <+40>: add    x0, x0, #0x65c            ; =0x65c 

    //打印
    0x100f926cc <+44>: bl     0x100f92a68               ; symbol stub for: printf
    0x100f926d0 <+48>: str    w0, [sp, #0x4]

    //跳转地址为0x100f926e8指令-->相当于高级语音中的goto
    0x100f926d4 <+52>: b      0x100f926e8               ; <+72> at main.m:19

    //下面两句代码得到的x0对应的常量值为0x100f93665
    0x100f926d8 <+56>: adrp   x0, 1
    0x100f926dc <+60>: add    x0, x0, #0x665            ; =0x665 
    //打印
    0x100f926e0 <+64>: bl     0x100f92a68               ; symbol stub for: printf
    0x100f926e4 <+68>: str    w0, [sp]

    //栈平衡
    0x100f926e8 <+72>: ldp    x29, x30, [sp, #0x10]
    0x100f926ec <+76>: add    sp, sp, #0x20             ; =0x20 

    //返回
    0x100f926f0 <+80>: ret

while

先补充两个指令
Zero Register: 在大多数情况下,作为源寄存器使用时, r31读出来的值 是0; 作为目标寄存器使用时, 丢弃结果。 WZR(word zero rigiser)或者XZR(64位)

Stack Register: 当 用作load/store 的base register时, 或者 一些算术指令中, r31提供当前的stack pointer WSP或者 SP

void func(int a, int b){
    while(a < b){
        printf("a大于b");
    }
}

上面代码的汇编如下:

    0x1009ca700 <+0>:  sub    sp, sp, #0x10             ; =0x10 
    //将0寄存器的值写进内存
    0x1009ca704 <+4>:  str    wzr, [sp, #0xc]
    //将刚刚存的值取出来并放在w8寄存器中
    0x1009ca708 <+8>:  ldr    w8, [sp, #0xc]
    //比较w8 和 10的大小
    0x1009ca70c <+12>: cmp    w8, #0xa                  ; =0xa 
    如果w8 >= 10,跳转0x1009ca724
    0x1009ca710 <+16>: b.ge   0x1009ca724               ; <+36> at main.m:18
    //从内存拿出值赋值w8
    0x1009ca714 <+20>: ldr    w8, [sp, #0xc]
    //w8 += 1
    0x1009ca718 <+24>: add    w8, w8, #0x1              ; =0x1 
    //将w8值写进内存
    0x1009ca71c <+28>: str    w8, [sp, #0xc]
    //跳转到0x1009ca708
    0x1009ca720 <+32>: b      0x1009ca708               ; <+8> at main.m:15
    //栈平衡
    0x1009ca724 <+36>: add    sp, sp, #0x10             ; =0x10 
    0x1009ca728 <+40>: ret     

for / do-while基本和while类似,此处就不再赘述

Switch

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

CBZ

比较(Compare),如果结果为零(Zero)就转移(只能跳到后面的指令)

void func(int a){
    switch (a) {
        case 0:
        {
            printf("0");
        }
            break;
        case 1:
        {
            printf("1");
        }
            break;
    
            
        default:
            break;
    }
}

上面代码的汇编代码如下

demo1`func:
    0x1005ae6a0 <+0>:   sub    sp, sp, #0x30             ; =0x30 
    0x1005ae6a4 <+4>:   stp    x29, x30, [sp, #0x20]
    0x1005ae6a8 <+8>:   add    x29, sp, #0x20            ; =0x20
    0x1005ae6ac <+12>:  stur   w0, [x29, #-0x4]
    0x1005ae6b0 <+16>:  ldur   w0, [x29, #-0x4]
    0x1005ae6b4 <+20>:  mov    x8, x0
    0x1005ae6b8 <+24>:  stur   w8, [x29, #-0x8]
   //上面全是栈操作和其他无关紧要操作

   //如果传进来的w0的值为0,就跳转地址0x1005ae6d8的指令,否则继续往下执行
    0x1005ae6bc <+28>:  cbz    w0, 0x1005ae6d8           ; <+56> at main.m:17
    
     //跳转地址为0x1005ae6c4的指令
    0x1005ae6c0 <+32>:  b      0x1005ae6c4               ; <+36> at main.m
    //拿到w8=x0的值
    0x1005ae6c4 <+36>:  ldur   w8, [x29, #-0x8]
    //w9=w8 - 1
    0x1005ae6c8 <+40>:  subs   w9, w8, #0x1              ; =0x1 
    //将w9的值存入内存
    0x1005ae6cc <+44>:  stur   w9, [x29, #-0xc]
    //w9=0,就跳转地址为0x1005ae6ec的指令,否则不跳转
    0x1005ae6d0 <+48>:  b.eq   0x1005ae6ec               ; <+76> at main.m:22
     //跳转地址为0x1005ae700指令
    0x1005ae6d4 <+52>:  b      0x1005ae700               ; <+96> at main.m:28

    //下面三句代码是一个打印,printf("0");
    0x1005ae6d8 <+56>:  adrp   x0, 1
    0x1005ae6dc <+60>:  add    x0, x0, #0x670            ; =0x670 
    0x1005ae6e0 <+64>:  bl     0x1005aea7c               ; symbol stub for: printf
    0x1005ae6e4 <+68>:  str    w0, [sp, #0x10]

  //跳转汇编为0x1005ae704的指令
    0x1005ae6e8 <+72>:  b      0x1005ae704               ; <+100> at main.m:31
   //下面三句是打印
    0x1005ae6ec <+76>:  adrp   x0, 1
    0x1005ae6f0 <+80>:  add    x0, x0, #0x672            ; =0x672 
    0x1005ae6f4 <+84>:  bl     0x1005aea7c               ; symbol stub for: printf
   
    0x1005ae6f8 <+88>:  str    w0, [sp, #0xc]

      //跳转地址为0x1005ae704指令
    0x1005ae6fc <+92>:  b      0x1005ae704               ; <+100> at main.m:31
    0x1005ae700 <+96>:  b      0x1005ae704               ; <+100> at main.m:31
    0x1005ae704 <+100>: ldp    x29, x30, [sp, #0x20]
    0x1005ae708 <+104>: add    sp, sp, #0x30             ; =0x30 
    0x1005ae70c <+108>: ret    

void func(int a){
    switch (a) {
        case 0:
        {
            printf("0");
        }
            break;
        case 1:
        {
            printf("1");
        }
            break;
        case 2:
        {
            printf("2");
        }
            break;
        case 3:
        {
            printf("3");
        }
            break;
            
        default:
            break;
    }
    
}

上面代码的汇编如下

    0x1002e2658 <+0>:   sub    sp, sp, #0x40             ; =0x40 
    0x1002e265c <+4>:   stp    x29, x30, [sp, #0x30]
    0x1002e2660 <+8>:   add    x29, sp, #0x30            ; =0x30 
    0x1002e2664 <+12>:  stur   w0, [x29, #-0x4]
    0x1002e2668 <+16>:  ldur   w0, [x29, #-0x4]
    0x1002e266c <+20>:  mov    x8, x0
    0x1002e2670 <+24>:  mov    x0, x8
   //以上代码全是栈操作这里就不做分析了

   //w0 = w0 - 3;
    0x1002e2674 <+28>:  subs   w0, w0, #0x3              ; =0x3 
    //将x8的值写入内存
    0x1002e2678 <+32>:  stur   x8, [x29, #-0x10]
    //将w0写入内存
    0x1002e267c <+36>:  stur   w0, [x29, #-0x14]
    //如果w0>0就跳转到地址为0x1002e26ec的指令,否则不跳转
    0x1002e2680 <+40>:  b.hi   0x1002e26ec               ; <+148> at main.m:37
    //将常量0的地址赋值给x8  x8 = 0
    0x1002e2684 <+44>:  adrp   x8, 0
    0x1002e2688 <+48>:  add    x8, x8, #0x6fc            ; =0x6fc 

   //将上面0x1002e2678存入内存的值取出来
    0x1002e268c <+52>:  ldur   x9, [x29, #-0x10]

    //这个是x10 = x8 + x9<<2,得出系统给我们存的switch的那个表值得地址
    0x1002e2690 <+56>:  ldrsw  x10, [x8, x9, lsl #2]
    //x8 = x10 + x8
    0x1002e2694 <+60>:  add    x8, x10, x8
   //计算得到x8的值后,跳转到x8指向的地址值
    0x1002e2698 <+64>:  br     x8

   //下面3句打印
    0x1002e269c <+68>:  adrp   x0, 1
    0x1002e26a0 <+72>:  add    x0, x0, #0x66c            ; =0x66c 
    0x1002e26a4 <+76>:  bl     0x1002e2a78               ; symbol stub for: printf
    //保护w0
    0x1002e26a8 <+80>:  str    w0, [sp, #0x18]
   //跳转地址为0x1002e26f0的指令
    0x1002e26ac <+84>:  b      0x1002e26f0               ; <+152> at main.m:40
   
   //下面3句打印
    0x1002e26b0 <+88>:  adrp   x0, 1
    0x1002e26b4 <+92>:  add    x0, x0, #0x66e            ; =0x66e 
    0x1002e26b8 <+96>:  bl     0x1002e2a78               ; symbol stub for: printf
    0x1002e26bc <+100>: str    w0, [sp, #0x14]
    0x1002e26c0 <+104>: b      0x1002e26f0               ; <+152> at main.m:40

    //下面3句打印
    0x1002e26c4 <+108>: adrp   x0, 1
    0x1002e26c8 <+112>: add    x0, x0, #0x670            ; =0x670 
    0x1002e26cc <+116>: bl     0x1002e2a78               ; symbol stub for: printf
    0x1002e26d0 <+120>: str    w0, [sp, #0x10]
    0x1002e26d4 <+124>: b      0x1002e26f0               ; <+152> at main.m:40

    //下面3句打印
    0x1002e26d8 <+128>: adrp   x0, 1
    0x1002e26dc <+132>: add    x0, x0, #0x672            ; =0x672 
    0x1002e26e0 <+136>: bl     0x1002e2a78               ; symbol stub for: printf
    0x1002e26e4 <+140>: str    w0, [sp, #0xc]
    0x1002e26e8 <+144>: b      0x1002e26f0               ; <+152> at main.m:40
    0x1002e26ec <+148>: b      0x1002e26f0               ; <+152> at main.m:40
    //栈平衡
    0x1002e26f0 <+152>: ldp    x29, x30, [sp, #0x30]
    0x1002e26f4 <+156>: add    sp, sp, #0x40             ; =0x40 
    0x1002e26f8 <+160>: ret 

执行过程

    1. switch 内分支比较多的时候(超过4个),在编译的时候会生成一个表(跳转表每个地址四个字节);
    1. 先判断是否是 default 分支;
    1. 根据相应的操作计算出要跳转的地址;
      这个是x10 = x8 + x9<<2,得出系统给我们存的switch的那个表值得地址
      0x1002e2690 <+56>: ldrsw x10, [x8, x9, lsl #2]
      通过计算得到跳转的指令的地址
      0x1002e2694 <+60>: add x8, x10, x8
    1. 跳转到X8的地址;
      0x1002e2698 <+64>: br x8

注意

虽然说正常分支超过4个,编译器就会生成一张表;但是也有不生成表的,比如你一定要写出case1,case 1000,case 200,编译器存储是连续的地址,会根据1,2,3...1000,如果还按照表的形式会浪费大量的空间,编译器会在效率和存储空间做出最合理的选择,将他们做成if,else的形式表现出来。

总结

switch语句 和 if...else语句执行效率问题,通过上面汇编代码得出结论:
当 switch 分支和 if...else的条件判断小于4的时候,执行效率是一样的;
当 switch 分支和 if...else的条件判断大于等于4的时候,switch 执行效率更高。(你的case是连续常量)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342