初识汇编

319 阅读6分钟

CPU 与寄存器

定义

CPU 运算速度非常快,为了性能CPU会在内部开辟一块临时存储区域, 并在运算时先将数据从内存中复制到这一块区域命,运算时在这一小块临时区域进行,我们称这一块临时存储区域为寄存器.

对于arm的CPU来说,如果寄存器以x开头表明是一个64位寄存器,以w开头是32位寄存器

通用寄存器

arm64中有32个通用寄存器 x0 - x28 x29:fp x30: LR x31: SP

SP FP LR 寄存器
  • SP 栈顶寄存器,任何时候都指向栈顶
  • FP 栈底指针,保存栈底地址
  • LR 保存函数返回地址,ret指令其实是找到LR 寄存器地址,解决函数嵌套调用,找不到函数地址问题 注意:arm64里边栈是16字节对齐的

bl ret指令

cpu从哪里执行指令是又pc寄存器决定的,可以通过改变pc寄存器内容来控制CPU执行目标指令 arm中提供了mov指令,可以修改大部分寄存器的值,

  1. mov x0,#0xa1 将0xa0保存到x0寄存器中 '#'代表后边是一个常数

但是mov不能改变pc寄存器的值, 但是可以利用跳转指令来改变pc寄存器的值,比如'bl'指令

bl ret 寄存器
  • bl: 将下一条指令地址放到lr(x30)寄存器,并且跳转到该指令运行
  • ret: 默认使用lr寄存器中的值,指示CPU将lr中指令为下条运行指令

str ldr 内存读写指令

str(store register) 将数据从寄存器中读取出来保存到内存中

ldr(load register) 将数据从内存中读取出来保存在寄存器中

_A:
    mov x0,#0xa0      //将0xa0 保存在x0寄存器中 此时x0寄存器值为0xa0
    mov x1,#0x00    // 将0x00 保存在x1寄存器中,x1寄存器值为0x00
    add x1, x0, #0x14 //将x0寄存器值,加上0x14, 并保存在x1中 相当于 x = y +1
    mov x0,x1 //x1值,保存在x0中 此时x0 = 0xa0 + 0x14
    bl _B      //跳转B 这是lr中保存的是下条指令的地址 也就是 mov x0,#0x0 的地址
    mov x0,#0x0
    ret  

_B:
    add x0, x0, #0x10
    ret  //ret执行之后会执行lr中保存的指定

[sp, #-0x10]! sp地址 -0x10 之后取到的地址赋值给sp []取址 !赋值

LDR Rt, [Rn], #offset  ;Rt = *Rn; Rn = Rn + offset

LDR Rt, [Rn, #offset]!  ; Rt = *(Rn + offset); Rn = Rn + offset

STR Rt, [Rn], #offset  ;*Rn = Rt; Rn = Rn + offset

STR Rt, [Rn, #offset]!  ;*(Rn + offset) = Rt; Rn = Rn + offset(地址回写)

函数嵌套如何做到能够返回到正确的指令
int a() {
    int a = b();
    return a;
}
int b() {
    int b = c();
    return b;
}
int c() {
    printf("c 执行了");
    return 10;
}

比如调用a方法,c,跟b 执行之后如何继续执行a中的 reutrn 操作呢?

利用栈来保存x30(lr)寄存器的值

text

.global _A,_B,_sums

_A:

    sub sp,sp,#0x10    // 拉伸栈
    str x30,[sp]      //将x30中的值写入sp指向地址中,对x30 进行保护存储
    mov x0,#0xaa

    bl _B

    mov x0,#0x30

    ldr x30,[sp]     //将保存的x30的值重新从内存中加载到x30寄存器中

    add sp,sp,#0x10

    ret        //ret 操作找的是lr中保存的指令

\


_B:

    mov x0,#0xbb

    ret

A 函数简写

_A:
    str x30,[sp,#-0x10]!      //先将x30寄存器的值存储在sp-0x10的位置,然后将sp = sp - 0x10

    mov x0,#0xaa

    bl _B

    mov x0,#0x30

    ldr x30,[sp],#0x10  //将sp中保存的值重新加载到x30寄存器中,然后将 sp = sp + 0x10

    ret        //ret 操作找的是lr中保存的指令

函数的参数和返回值

arm64中函数的参数是 x0-x7 寄存器中,超过8个将会存放在栈中 函数的返回值是放在x0中的

adrp add

NSLog(@"aaa");

nslog 的汇编如下

0x104f3df34 <+268>: adrp   x0, 3
0x104f3df38 <+272>: add    x0, x0, #0x28             ; =0x28

adrp (address page) 以页寻址

adrp x0,3 意思解析

  1. 将3左移12位 0000 0011 左移12位 0011 0000 0000 0000 16进制:0x3000

  2. 然后将0x104f3df34 地址 后12位清零 0x104f3d000

  3. 然后将 0x104f3d000 + 0x3000 = 0x104f4000

add    x0, x0, #0x28 这时x0寄存器中地址为 0x104f4028 ,也就是我们打印的aaa

(lldb) register read x0**
 x0 = 0x0000000104f40028  @"aaa"

快速计算 将16进制地址后三位置零 然后加上0x3000 比如

0x104f40a34 <+268>: adrp   x0, 2    //那么就是 0x104f40000 + 0x2000 = 0x104f42000

adrp add 用来获取常量和全局变量

零寄存器
  • wzr 32位0 寄存器,用来给int清零
  • xzr 63位0 寄存器,用来给long清零

状态寄存器 CPSR(current program status register)

CPSR 是一个32位寄存器

  • CPSR 低8位, I,F,T,M[4 - 0] 是控制位,程序无法修改,除非CPU运行于特权模式下,程序才能修改控制为
  • N, Z, C, V 均为条件控制位,他们内容可被运算或者逻辑运算结果改变, 并且可以决定某条指令是否被执行

N 标志 (Nagative)

N 位于CPSR 的第31位,符号标志位,他记录了相关指令执行后的结果是否位负, 如果为负 N = 1, 如果为非负数(包括0) N = 0

  • arm64指令集中,有些指令执行时是影响状态寄存器的,比如 add/sub/or 等运算指令或者逻辑指令

Z标志(Zero)

Z位于CPSR第30位,0标志位,记录了相关指令执行后,其结果是否为0, 如果位0 ,Z = 1,如果不为0 Z = 0

如果Z = 1 时, 表示结果位0, 那么是一个非负数, 这个时候 N = 0 也就是说, Z = 1时,N必定为0

C标志 Carry

C位于CPSR 的第29位,进位标志位, 进行无符号的运算

  • 加法运算,如果运算结果产生了进位(无符号溢出), C=1, 否则C = 0
  • 减法运算,如果运算结果产生了借位(无符号数溢出), C=0, 否则C=1

便于理解,可以把C位想象成一个,无符号数最高有效位的前一位,比如C 1111 1111 该数+1 则会发生进位,超过数据宽度,发生溢出,这个时候C=1, 又比如C 0000 0001 减去 0000 0010,发生溢出,这个时候C=0

循环与判断

cmp 比较指令

判断 cmp 其实做的是减法运算

  • b.le XXXX 小于等于就跳转到xxxx
  • b.lt XXXX 小于跳转到XXXX
  • b.gt xxxx 大于就跳转到xxxx
  • b.ge xxxx 大于等于
  • b.eq xxxx 等于
  • b.ne 不等于
  • b.hi 无符号大于