汇编三

汇编常用指令

  • sub 拉伸栈空间
sub sp, sp, #0x20
sp 是栈顶,由于栈是由高地址到低地址,
一次性拉伸32个字节就需要将栈顶地址减去0x20(栈是16字节对齐的)
  • str (store register) 将数据从寄存器中读出来,存到内存中.
  • stp 是str的延伸,可以同时存储两个
str x0,[sp,#0x10]
将x0的值存在以sp+0x10为地址的存储器中(栈空间内存)
stp x29,x30,[sp,#0x20]
sp往上加32(20 = 2 * 16)个字节,存放x29 和 x30
  • ldr(load register)读取指令
    将数据从内存中读取出来, 存到寄存器中,LDR:通常都是作加载指令的,但是它也可以作伪指令

  • ldp是ldr的衍生, 可以同时读两个寄存器

 LDR r0,[r1]        //将R1中的值存到r0中
 LDR r1,[r2,#16]     //将(r2+16)地址中的内容存到r1中
 LDR r1,[r2],#4      //将r2地址中的内容存到r1中,同时r2=r2+4

sub sp, sp, #0x20 ; 拉伸栈空间32(20 = 2*16)个字节
stp x0 , x1, [sp, #0x10] ; sp往上加16(10 = 1 * 16)个字节,存放x0 和 x1
ldp x1 , x0, [sp, #0x10] ; 将sp偏移16个字节的值取出来,放入x1 和 x0
  • adrp 通过基地址 + 偏移 获得一个字符串(全局变量)
原理 adrp操作步骤adrp x0, 1

1. 将1的值,左移12位 1 0000 0000 0000 == 0x1000

2.将PC寄存器的低12位清零 0x1045228b0  ==> 0x104522000

3.将将1 和 2 的结果相加  给 X0 寄存器!! ==>0x104523000

4.add x1,x0,#0xfd8 即将第三布得出的结果加上0xfd8 ==>0x104523fd8 就可以得出全局变量或者常量所在的地址

注:地址为pc寄存器左边的地址,adrp是找出要获取参数的地址范围,然后下个pc寄存器执行的代码会定位到准确的物理地址。

  • cbz :【cbz 寄存器,地址 】指令的意思是 如果寄存器的值==0,则跳转到地址,如果不等于0,则继续执行代码
  • subs : subs其实就是sub指令,但是这个指令操作结束后,会影响到标志寄存器

cmp(Compare)比较指令

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

  • bl 标号:跳转到标号处执行
  • b.gt 标号:比较结果是大于(greater than),执行标号,否则不跳转
  • b.ge 标号:比较结果是大于等于(greater than or equal to),执行标号,否则不跳转
  • b.eq 标号:比较结果是等于,执行标号,否则不跳转
  • b.hi 标号:比较结果是无符号大于,执行标号,否则不跳转
  • b.le 标号:判断上面cmp的值是小于等于 执行标号,否则直接往下走
  • b.lt 判断上面cmp的值是 小于 执行后面的地址中的方法 否则直接往下走
CPY 把一个寄存器的值拷贝(COPY)到另一个寄存器中
EOR 近位异或
LSL 逻辑左移(Logic Shift Left)
LSR 逻辑右移(Logic Shift Right)
MOV 寄存器加载数据,既能用于寄存器间的传输,也能用于加载立即数
MUL 乘法(Multiplication)
MVN 加载一个数的 NOT值(取到逻辑反的值)
NEG 取二进制补码
ORR 按位或
ROR 循环右移
SBC 带借位的减法
SUB 减法(Subtraction)
TST 测试(Test,执行按位与操作,并且根据结果更新Z)
REV 在一个32位寄存器中反转(Reverse)字节序
REVH 把一个32位寄存器分成两个(Half)16位数,在每个16位数中反转字节序
REVSH 把一个32位寄存器的低16位半字进行字节反转,然后带符号扩展到32位
SXTB 带符号(Signed)扩展一个字节(Byte)到 32位
SXTH 带符号(Signed)扩展一个半字(Half)到 32位
UXTB 无符号(Unsigned)扩展一个字节(Byte)到 32位
UXTH 无符号(Unsigned)扩展一个半字(Half)到 32位

Switch

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

void __switch_1__(){
    int value = 5;
    switch (value) {
        case 0:
            printf("1");
            break;
        case 1:
            printf("2");
            break;
        case 2:
            printf("3");
            break;
        default:
            printf("else");
            break;
    }
}

汇编代码如下:

01-汇编-IF-SWITCH`__switch_1__:
    0x100c96644 <+0>:   sub    sp, sp, #0x30             ; =0x30 
    0x100c96648 <+4>:   stp    x29, x30, [sp, #0x20]
    0x100c9664c <+8>:   add    x29, sp, #0x20            ; =0x20 
->  0x100c96650 <+12>:  mov    w8, #0x5                  ; w8 = 5
    0x100c96654 <+16>:  stur   w8, [x29, #-0x4]
    0x100c96658 <+20>:  ldur   w8, [x29, #-0x4]          ; value = w8
    0x100c9665c <+24>:  mov    x9, x8
    0x100c96660 <+28>:  stur   w9, [x29, #-0x8]
    0x100c96664 <+32>:  cbz    w8, 0x100c96694           ; cbz 指令的意思是 如果w8==0,则跳转到 0x100c96694,如果不等于0,则继续执行代码
    0x100c96668 <+36>:  b      0x100c9666c               ; <+40> at main.m:26
    0x100c9666c <+40>:  ldur   w8, [x29, #-0x8]
    0x100c96670 <+44>:  subs   w9, w8, #0x1              ; w9=w8-1
    0x100c96674 <+48>:  stur   w9, [x29, #-0xc]
    0x100c96678 <+52>:  b.eq   0x100c966a8               ; 如果w9==0,则跳转到 0x100c966a8
    0x100c9667c <+56>:  b      0x100c96680               ; <+60> at main.m:26
    0x100c96680 <+60>:  ldur   w8, [x29, #-0x8]
    0x100c96684 <+64>:  subs   w9, w8, #0x2              ; w9=w8-2
    0x100c96688 <+68>:  str    w9, [sp, #0x10]
    0x100c9668c <+72>:  b.eq   0x100c966bc               ; 如果w9==0,则跳转到  0x100c966bc
    0x100c96690 <+76>:  b      0x100c966d0               ; 如果都不满足即default,则跳转到 0x100c966d0
    0x100c96694 <+80>:  adrp   x0, 1
    0x100c96698 <+84>:  add    x0, x0, #0xf29            ; =0xf29 
    0x100c9669c <+88>:  bl     0x100c96c04               ; printf("1");
    0x100c966a0 <+92>:  str    w0, [sp, #0xc]
    0x100c966a4 <+96>:  b      0x100c966e0               ; <+156> at main.m:40
    0x100c966a8 <+100>: adrp   x0, 1
    0x100c966ac <+104>: add    x0, x0, #0xf2b            ; =0xf2b 
    0x100c966b0 <+108>: bl     0x100c96c04               ; printf("2");
    0x100c966b4 <+112>: str    w0, [sp, #0x8]
    0x100c966b8 <+116>: b      0x100c966e0               ; <+156> at main.m:40
    0x100c966bc <+120>: adrp   x0, 1
    0x100c966c0 <+124>: add    x0, x0, #0xf2d            ; =0xf2d 
    0x100c966c4 <+128>: bl     0x100c96c04               ; printf("3");
    0x100c966c8 <+132>: str    w0, [sp, #0x4]
    0x100c966cc <+136>: b      0x100c966e0               ; <+156> at main.m:40
    0x100c966d0 <+140>: adrp   x0, 1
    0x100c966d4 <+144>: add    x0, x0, #0xf24            ; =0xf24 
    0x100c966d8 <+148>: bl     0x100c96c04               ; printf("else");
    0x100c966dc <+152>: str    w0, [sp]
    0x100c966e0 <+156>: ldp    x29, x30, [sp, #0x20]
    0x100c966e4 <+160>: add    sp, sp, #0x30             ; =0x30 
    0x100c966e8 <+164>: ret 

由上可见,当case为3个时,汇编执行的代码为if else语句 而当增加到4个case时,汇编代码发生了相关变化

  0x100be66f8 <+48>:  adrp   x8, 0
    0x100be66fc <+52>:  add    x8, x8, #0x790            ; x8 = 0x0000000100be6790 
    0x100be6700 <+56>:  ldur   x9, [x29, #-0x10]         ; x9 就是前面存进来的值 也就是3
    0x100be6704 <+60>:  ldrsw  x10, [x8, x9, lsl #2]     ; 取出[0x0000000100be6790 + 03 << 2],给x10
    0x100be6708 <+64>:  add    x8, x10, x8               ; 计算出要调转的位置
    0x100be670c <+68>:  br     x8                        ; 跳转
  • ldrsw ldrsw x10, [x8, x9, lsl #2] lsl就是将后面的值左移 []里面的意思是将x9+ 左移后的值 赋值给x8,然后通过[]取出x8寄存器所对应的值赋值给x10

  • br (brew register) 意思与b ’地址‘ 一样,就是跳转后面的地址

  这时分析汇编代码就可得知:当超过3个case并且case值里面尽可能的连续时,编译器会通过表来存储相关的偏移值(由于本身编译的时候会增加ASLR一个随机值,故而通过pc寄存器与偏移值之间的加减就可以直接得出相关值存在的地址),通过br指令跳转对应的地址执行下面的相关代码。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容