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指令,可以修改大部分寄存器的值,
- 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 意思解析
-
将3左移12位 0000 0011 左移12位 0011 0000 0000 0000 16进制:0x3000
-
然后将0x104f3df34 地址 后12位清零 0x104f3d000
-
然后将 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 无符号大于